函数式队列是一种具有以下三种操作方式的数据结构:
head 返回队列的第一个元素
tail 返回除第一个元素之外的队列
append 返回尾部添加了指定元素的新队列
如果Queue是一个不变队列,也就是函数式队列。在添加元素的时候不会改变其内容,而是返回包含了这个元素的新队列。
如果Queue是可变类型的,那么append操作将改变队列的内容。
纯函数式队列和List具有相似性,都支持head和tail操作。可以通过List来实现函数式队列。
关于时间开销问题,append操作应该在常量时间内完成。
为了做到这一点,我们使用两个List,分别称为leading和trailing,来表达队列。leading包含前段元素,而trailing包含了反向排列的后段元素。队列在任何时刻的所有内容都可以表示为 leading ::: trailing.reverse
。
代码如下:
classQueue[T] (privateval leading: List[T], privateval trailing: List[T]) { privatedefmirror = if(leading.isEmpty) newQueue(trailing.reverse, Nil) else this defhead = mirror.leading.head deftail = { val q = mirror newQueue(q.leading.tail, q.trailing) } defappend(element: T) = newQueue(leading, element :: trailing) }
上面实现的Queue以暴露本不该暴露的实现细节为代价,全局可访问Queue构造器,带有两个列表参数,不能作为直观表达队列的形式。
私有构造器和私有成员是隐藏类的初始化代码和表达代码的一种方式。
可以通过把private修饰符添加在类参数列表的前面把主构造器隐藏起来。
classQueue[T]private(privatevalleading: List[T], privatevaltrailing: List[T])
构造器是私有的,它只能被类本身及伴生对象访问。我们可以添加可以用初始元素序列创建队列的工厂方法。定义与类同名的Queue对象及apply方法:
object Queue { def apply[T](xs: T*) = new Queue[T](xs.toList, Nil) }
使用私有类的方法可以更彻底的把类本身隐藏掉,仅提供能够暴露类公共接口的特质。
traitQueue[T]{ defhead: T deftail: Queue[T] defappend(x: T): Queue[T] } objectQueue{ defapply[T](xs: T*): Queue[T] = newQueueImpl[T](xs.toList, Nil) privateclassQueueImpl[T]( private val leading: List[T], private val trailing: List[T] ) extendsQueue[T] { defmirror = if(leading.isEmpty) newQueueImpl(trailing.reverse, Nil) else this defhead: T = mirror.leading.head deftail: QueueImpl[T] = { valq = mirror newQueueImpl(q.leading.tail, q.trailing) } defappend(x: T) = newQueueImpl(leading, x:: trailing) } }
代码中定义了特质Queue,声明了方法head、tail和append。这三个方法都实现在子类QueueImpl中,而它本身是对象Queue的内部类。这个方案暴露给客户的信息与前面相同,但使用了不同的技术。代之以逐个隐藏构造器与方法,这个版本隐藏全部实现类。
使用Queue[+T]方式对Queue实现协变,然而在append的实现中,T参数出现在了逆变的位置。可以通过把append变为多态以使其泛型化(即提供给append方法类型参数)并使用它的类型参数的下界。
class Queue[+T] private ( private[this] var leading: List[T], private[this] var trailing: List[T] ) { def append[U >: T](x: U) = new Queue[U](leading, x::trailing) }
通过 U >: T
定义了T为U的下界。结果U必须是T的超类型。
假设存在类Fruit和两个子类,Apple和Orange。通过Queue类的定义,可以吧Orange对象加入到Queue[Apple],结果返回Queue[Fruit]类型。
到目前为止,Queue类仍有一些问题。如果head被一遍遍的调用很多次,而leading列表为空,那么mirror操作可能会重复的把trailing复制到leading列表。
可以将leading和trailing指定为可以重新复制的变量,而mirror从trailing反向复制到leading的操作是在当前队列上的副作用,而不再返回新的队列。
通过将leading和trailing用private[this]修饰,声明为对象私有变量,使得这种副作用纯粹是Queue操作的内部实现,从而使它对于Queue的客户不可见。
代码如下:
classQueue[+T]private( private[this] var leading: List[T], private[this] var trailing: List[T] ) { privatedefmirror() = if(leading.isEmpty) { while(!trailing.isEmpty) { leading = trailing.head :: leading trailing = trailing.tail } } defhead: T = { mirror() leading.head } deftail: Queue[T] = { mirror() newQueue(leading.tail, trailing) } defappend[U >: T](x: U) = newQueue[U](leading, x::trailing) }
说明:被定义在同一个对象内访问对象私有变量不会引起与变化型有关的问题。Scala的变化型检查规则包含了关于对象私有定义的特例。当检查到带有+/-号的类型参数只出现在具有相同变化型分类的位置上时,这种定义将被忽略。
转载请注明作者Jason Ding及其出处
jasonding.top
Github博客主页(http://blog.jasonding.top/)
CSDN博客(http://blog.csdn.net/jasonding1354)
简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)
Google搜索jasonding1354进入我的博客主页