6.       合成(composition)

解释了Scala的类型抽象体系之后,本节主要描述类的合成方式(译注:class composition似乎也没有固定的译法,此处翻译成“合成”)。Scala的基于混入的类合成(mixin class composition)体系是Brach[6]中的面向对象的线性混入合成(linear mixin compostion)和[14、25]中提出的更加对称的混入模块(mixin modules),以及traits[42]这三者的融合。(注:mixin有些人翻译成混合,有些人翻译成混入)我们先看一个例子,如下这个迭代器的抽象描述:

trait AbsIterator[T] {
def hasNext: boolean
def next: T
}

注意上面出现的关键字trait。Trait是一种特殊的抽象类,他的构造子没有任何值参数。Traits可以出现任何抽象类可以出现的地方,但反之不然,只有traits可以用于混入。

下面,我们用一个trait继承自AbsIterator,并增加一个方法foreach,用于将一个函数作用于该迭代子返回的每一个元素上。

trait RichIterator[T] extends AbsIterator[T] {
def foreach(f: T => unit): unit =
while (hasNext) f(next)
}

下面是一个具体的迭代子类定义,用于连续返回一个字符串的每一个字符:

class StringIterator(s: String) extends AbsIterator[char] {
private var i = 0
def hasNext = i < s.length
def next = { val x = s charAt i; i = i + 1; x }
}

混入式类合成(Mixin-class composition)下面我们将RichIterator和StringIterator的功能合并在一个类中。只有单根继承和接口的情况下这是不可能的,因为这两个类都有具体的实现代码。因此,Scala提供了混入式类合成的机制,使程序设计者可以重用一个类的增量内容,也就是非继承的内容。这种机制使人可以将RichIterator和StringIterator合并,在如下所示的例子将一个字符串的所有字母打成一列。

object Test {
def main(args: Array[String]): unit = {
class Iter extends StringIterator(args(0))
with RichIterator[char]
val iter = new Iter
iter foreach System.out.println
}
}

Iter类通过RichIterator和StringIterator这两个父类型混入合成,第一个父类型仍然称为超类(superclass),第二个父类型则称为混入(mixin)。

 

类的全序化(Class Linearization)

混入式类合成是多重继承的一种形式,因此也会面临单继承所没有的问题。最典型的就是:如果多个父类型定义了同名的成员,哪一个成员被继承?调用父类方法时那一个成员被引用?如果一个类从多个路径被继承了怎么办?在Scala中,解决这些问题的基础构造就是类的全序化(class linearization)。(注:linearization可以翻成线性化或者全序化,在计算机领域一般取后者。另外,后面大部分情况下用全序来替代,主要是为了读起来不那么别扭)

一个类C所直接继承的类形成的可递闭包当中所有类称为C的基类(base classes)。由于有混入类,一个类与它的基类之间的继承关系,构成一个有向无环图(directed acyclic graph)。C的全序化L(C)是C的所有基类的一个全序(total order),根据如下规则构成:假设C的定义为:

class C extends B0 with . . . with Bn { . . . } .

这个全序以C的基类B0的全序为最后一部分,前面是B1的全序(排除掉已经包含在B0的全序当中的类),再前面是B2,…Bn,同样排除掉前面已经出现过的类。最前面的是类C本身,作为这个全序的头一个类。例如,Iter类的全序化是:

{Iter, RichIterator, StringIterator, AbsIterator, AnyRef, Any }

类的全序对于类的继承关系而言是一种改进:如果一个类C是D的子类,则在任何同时继承C、D的类的全序中,C永远出现在D之前。全序化还满足另一个性质:一个类的全序永远包括其基类的全序作为后缀。例如,StringIterator的全序化:{ StringIterator, AbsIterator, AnyRef, Any }就是其子类Iter的全序的后缀。不过对于混入类,这个性质并不成立,一个混入类的全序当中的类,在其子类的全序当中可能以不同的顺序出现,也就是说,Scala中全序化不是单调(monotonic[1])的。