Scala 专题教程-参数化类型(2): 信息隐藏

jerry Scala 2015年11月25日 收藏

上篇的Queue的实现在效率上来说还是比较高效的,但是却给类的使用者暴露一些不必要的实现细节,比如Queue的构造函数使用了两个List对象,其中一个还是个倒序的列表。本篇介绍如何隐藏这些不必要的信息。
在Java中,你可以使用私有的构造函数来隐藏构造函数,在Scala中的主构造器缺省包含在类定义中,但你还是可以使用private来修改其访问属性。
例如:

class Queue[T] private(
  private val leading:List[T],
  private val trailing:List[T]
 )

在类名和参数之间的private修饰符表明该构造器是私有的,只能在类或其伙伴对象之中使用。而类本身还是可以公开访问的:

scala> new Queue(List(1,2),List(3))
<console>:9: error: constructor Queue in class Queue cannot be accessed in object $iw
              new Queue(List(1,2),List(3))
              ^

由于主构造器变成私有的,因此我们需要另外的方式来构造Queue的实例,一种方法是使用辅助构造器,比如如下的辅助构造函数:

def this() =this(Nil,Nil)

def this(elem: T*) = this (elems.toList,Nil)

注意,参数类 T* 代表的不定长参数类型。

另外一种方法是使用Factory 方法来构造一个队列。 一个比较简洁的方法是使用伙伴对象 ,并定义apply方法,比如:

object Queue {
   def apply[T](xs: T*) = new Queue[T](xs.toList,Nil)
}

我们使用apply定义了构造Queue的factory方法,因此调用时可以使用Queue(1,2,3)的形式来构造一个实例。 Queue(1,2,3)实际为Queue.apply(1,2,3)调用,对调用者来说看起来好像定义了一个可以全局访问的factory构造方法。而其实对于Scala来说,所有的方法都必须包含着某个类或对象中。

除了使用私有方法来隐含实现细节外,还有一种方法可以实现信息的隐藏。我们可以使用trait定义类的接口,而把实现细节全部隐藏起来(使用私有类),代码实现如下:

trait Queue[T]{
  def head: T
  def tail: Queue[T]
  def enqueue(x:T): Queue[T]
}
object Queue {
  def apply[T](xs: T*):Queue[T] = new QueueImpl[T](xs.toList, Nil)

  private class QueueImpl[T](
        private val leading: List[T],
        private val trailing: List[T]
      ) extends Queue[T]{
    private def mirror =
      if (leading.isEmpty)
        new QueueImpl(trailing.reverse, Nil)
      else
        this

    def head = mirror.leading.head

    def tail = {
      val q = mirror
      new QueueImpl(q.leading.tail, q.trailing)
    }

    def enqueue(x: T) =
      new QueueImpl(leading, x :: trailing)
  }
}

这个实现定义一个Public的Trait方法,而隐藏了所有的实现细节。