Scala trait 的线性化规则详解 -2

接上文,Scala 为了解决多重继承的问题,使用了线性化。那么线性化的规则是什么?我们什么时候需要了解 Scala 的线性化规则?

我们先来看看以下问题(来自 Programming in Scala 3rd,并进行加强):

在上面的代码中,打印出来的结果是什么?想1分钟,然后看答案。

答案是:

如果你能够正确推导出答案,那就不必往下读了。如果你推导不正确,或者无从下手,那么请接着往下读,我会尽我可能讲明白的。

线性化的规则是什么?

Scala 线性化的规则在 Scala 的语言规范 中有讲(56页, 5.1.2 Class Linearization)。但篇幅很少,较难理解,我将用自己的理解来进行接下来的说明。

线性化解决的是将 A extends B with C with D... 展开成单条继承链的问题。

线性化的规则理解有以下几点:

  1. extendswith 的区别: extends 是继承, with 是继续。
  2. with 的优先级比 extends
  3. 单条继承链中,类不能有重复

以上三条就是线性化的规则。读者可能一下子不太明白。接下来,我将使用这三条规则,结合上面提到的问题进行展开。

我们重新看下 Cat 的定义: class Cat extends Animal with Furry with FourLegged, 开始推导其线性化。为了能够讲明白,我只做了几张图,结合图片来说明可能会更有效。

首先,我们看 Cat 的定义:

Cat定义

根据规则2, extendswith 的优先级低,因此先展开上图中虚线框住的部分。即展开 Animal with Furry with FourLegged。此时,Cat 的线性化结果还是空的,我们用 result = List() 表示。

接着开始展开 Animal。此时我们进入 Animal,将 Animal 进行线性展开(对,这个线性展开的过程是递归的)。如图:

因为在 Scala 中,AnyRef 和 Any 都是基本的类,所以就先忽略掉它们。因此, Animal 的线性展开就是 Animal。此时,我们的结果就是 result = List("Animal")。同时, Animal 也已经展开完成。

接下来开始线性化 FurryFurry 的定义如下图:

很容易看出, Furry 的线性展开结果是 Furry, Animal

于是我们将 Furry, Animal 放到 result = List("Animal") 中,结果是 result = List("Furry", "Animal", "Animal")。为什么放在 result的前面?因为根据规则1, with 不是继承,而是继续。

但根据规则3,类不能有重复,于是 Animal必须去掉一个。去掉哪一个呢?因为之前的 result中已经有一个 Animal 占坑了,所以后加入的 Animal只能被去掉,因此结果现在为 result = List("Furry", "Animal")

到目前为止,我们已经进展到下图了:

很好,已经完成了 AnimalFurry 了,而目前的线性化阶段性结果是 result = List("Furry", "Animal")。接下来该进行 FourLegged的线性展开了。

这里可以看到, FourLegged 是继承了 HasLegs的,而 HasLegs 又是继承了 Animal。因此, FourLegged 的线性化展开结果是 FourLegged, HasLegs, Animal。再和总体的 result = List("Furry", "Animal") 进行结合,结果是 result = ("FourLegged", "HasLegs", "Furry", "Animal")。 是的, Animal 又重复了,又被去掉了。 所以,我们现在的情况是:

已经把相关 with的进行了线性展开,结果是 result = ("FourLegged", "HasLegs", "Furry", "Animal"),这时候再把 Cat 加上,结果便是 result = ("Cat","FourLegged", "HasLegs", "Furry", "Animal")。线性化展开到此结束。

因此,上面程序的打印结果 Cat FourLegged HasLegs Furry Animal 也就很自然了。

我们什么时候需要了解 Scala 的线性化规则

trait 的地方,就需要了解线性化的规则。这使你明白每个 super 所代表的含义。

同时,我们也需明白, trait 的线性化可以玩得很复杂,即使我们能明白其中的秘密,用户却不一定能够明白。如果我们玩得太复杂,用户就不会买账。

因此,这也要求我们在设计的时候,一定要避免这样混乱的继承关系,避免让使用者混乱。能够一目了然的线性关系,对使用者是最好的体验。

最后,请尝试使用程序描述 Scala 的线性化算法。下一篇我也会提供相应的代码。

一网友评论"Scala trait 的线性化规则详解 -2"

1 坪 /引用 为 "Scala trait 的线性化规则详解 -2"

发表评论

电子邮件地址不会被公开。 必填项已用*标注