对空对象模式的误解

时间:2014-12-10 16:07:16

标签: scala

我正在尝试使用null对象模式来实现链表,并且它开始失控。在使用普通null时,我可以检查节点是否为空,然后决定在其上调用什么。使用null对象模式,必须为每个类型(在本例中为具体节点和具体的null节点)定义我需要在节点上执行的任何操作。当它只有2次操作时,那很好,但是我发现我正在加入,而且它开始变得凌乱:

package tutorial

trait AbNode[+A] {
    def getNext[B >: A]: AbNode[B]
    def hasNext: Boolean
    def replaceTail[B >: A](newNext:AbNode[B]): AbNode[B]
    def foreach[B >: A, C](f: B => C): AbNode[C]
    def findLast[B >: A]: AbNode[B]
}

case class Node[+A](cargo:A,next:AbNode[A] = NullNode) extends AbNode[A] {
    override def getNext[B >: A]: AbNode[B] = next

    override def hasNext: Boolean = next != NullNode

    override def replaceTail[B >: A](newTail:AbNode[B]): AbNode[B] =
        this.copy(next = newTail)

    override def foreach[B >: A, C](f: B => C): AbNode[C] = 
        Node( f(cargo) , next.foreach(f) )

    override def findLast[B >: A]: AbNode[B] = next.findLast
}

case object NullNode extends AbNode[Nothing] {
    override def getNext[B >: Nothing]: AbNode[Nothing] = NullNode

    override def hasNext: Boolean = false

    override def replaceTail[B >: Nothing](newCargo:AbNode[B]): AbNode[Nothing] = NullNode

    override def foreach[B >: Nothing, C](f: B => C): AbNode[Nothing] = NullNode

    override def findLast[B >: Nothing]: AbNode[Nothing] = NullNode
}

//map _ [] = []
//map f (x:xs) = f x : map f xs  


class LinkedList[A] {
    var head: AbNode[A] = NullNode
    var last: AbNode[A] = head
    var size: Int = 0

    def this(head:AbNode[A], last:AbNode[A]) = {
        this
        this.head = head
        this.last = last
    }

    def length: Int = size
    def maxIndex: Int = size - 1

    def pushFront(cargo:A) = {
        val oldHead = head
        head = Node(cargo).replaceTail(oldHead)
        size += 1
    }

    def pushBack(cargo:A) = {
        last.replaceTail(Node(cargo))
        last = last.getNext
        size += 1
    }

    def foreach[B](f: A => B): LinkedList[B] = {
        val newHead = head.foreach(f)
        new LinkedList(newHead, newHead.findLast)
    }

    def get(index:Int): A = {
        var curNode = head
        for (i <- 0 to maxIndex) {
            curNode = curNode.getNext
        }
        curNode.cargo // <- Whoops. Now I need to declare getCargo in the AbNode trait, in case curNode is a NullNode
    }
}

我使用不正确吗?将东西添加到特征中似乎很荒谬。

1 个答案:

答案 0 :(得分:0)

接受所有评论,这就是我所拥有的。之一:

  • 在AbNode中定义当前抽象的方法以使用模式匹配:

    trait AbNode[+A] {
        def replaceTail[B >: A](newTail:AbNode[B]): AbNode[B] = this match {
            case Node(c,n)  => Node(c,newTail)
            case NullNode   => newTail
        }
    }
    
  • 使用伴侣对象,并将它们从抽象类中取出:

    object Node {
        def replaceTail[A](node:AbNode[A], newTail:AbNode[A]): AbNode[A] = node match {
            case Node(x,n)  => Node(x,newTail)
            case NullNode   => newTail
        }
    }
    

无论哪种方式,模式匹配似乎都是不那么痛苦的解决方案。这种方法的问题是,如果我需要添加一种新类型的节点,我需要更新每个方法(我认为这是@lmm提到的权衡)。我可以看到自己添加更多方法然后输入类型,所以我会坚持这个。

另一个缺点是,据我所知,如果忽略一个案例,这不会产生编译时错误。以我原来的方式,它会在编译时失败,因为我没有完成这个特性。在这里,它会编译,但如果我忘记了一个案例,可能会因匹配错误而失败。

谢谢你们