标签:
在Scala中的trait中引入了混入的概念,即Mixin of trait。
可能翻译不准确,有人也称之为混入类(mixins),混入是一种组合的抽象类,主要用于多继承上下文中为一个类添加多个服务,多重继承将多个 mixin 组合成一个类。例如,如果你有一个类表示“马”,你可以实例化这个类来创建一个“马”的实例,然后通过继承像“车库”和“花园”来扩展它,使用 Scala 的写法就是:
val myHouse = new House with Garage with Garden从 mixin 继承并不是一个特定的规范,这只是用来将各种功能添加到已有类的方法。在 OOP 中,有了mixin,你就有通过它来提升类的可读性。
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)。
又叫菱形问题(有时叫做“致命的死钻石”deadly diamond of death),描述的是B和C继承自A,D继承自B和C,如果A有一个方法被B和C重载,而D不对其重载,那么D应该实现谁的方法,B还是C?
A
↗ ↖
B C
↖ ↗
D
在C++中是通过虚基类virtual实现,并按照深度优先,从左到右的顺序遍历调用,虽然解决了菱形问题,但由于C++包含指针,继承关系上容易造成结构混乱的情况。
那么,Scala是如何处理这个菱形问题的。在这之前,首先了解Scala中几个概念。
编程语言性能的瓶颈关键在编译器,Scala也不例外,Scala编译器是直接将Scala编译成.class文件的。而生成的class文件则取决于你如何定义。当你定义一个只包含方法声明而不包含方法体的trait类,他会编译成一个Java接口。你可以使用javap –c <class file name>查看。例如,trait Empty{def e:Int}会产生如下的类:
public interface Empty{ public abstract int e(); }如果trait声明了具体的方法或代码,Scala会生成两个类:一个接口类和一个包含代码的新类。当一个类继承这个trait时,trait中声明的变量将被复制到这个类文件中,而定义在trait中的方法作为这个继承类的外观模式的方法。这个类调用这个方法时,将调用新类中的对应方法。
Scala 的基于混入的类构成(mixin class composition)体系是线性混入构成(linearmixin compostion)和对称的混入模块(mixin modules),以及traits这三者的融合。
Scala是通过类的全序化(Class Linearization),或称作类的线性化。线性化指出一个类的祖先类是一条线性路径的,包括超类(superclass)和特性(traits)。它通过两步来处理方法调用的问题:
① 使用右孩子优先的深度优先遍历搜索(right-first,depth-first search)算法进行搜索。
② 遍历得到的结构层次中,保留最后一个元素,其余删除。
线性混入,即是指使用有孩子悠闲的深度悠闲遍历搜索算法,列出层次结构(Scala class hierarchy),因此Scala多重继承的混入类中,如果包含有混入类(Mixins,或称为混入组合),则多重继承中总是选择最右边的(right-mostly)的实现方法。分析如下代码:
package net.scala.chapter3.test import *** /** * @author Barudisshu */ @FixMethodOrder(MethodSorters.JVM) class TestMixin extends AssertionsForJUnit with LazyLogging { @Test def test() { val mixin = Mixin("jijiang") mixin.foo("jijiang: ") } } trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = println(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = println(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") } object Mixin { // 如果包含菱形问题,则只执行最右边的 def apply(msg: String) = new Mixin(msg) with papa with mama }
这里,实际输出结果为mama: jijiang: ,说明,trait papa并没有执行,trait mama符合最右(最后)的深度优先遍历结果。
惰性求值可以说是函数式语言中不可避免的事实,庆幸的是,Scala是一门强静态的语言,在执行效率上比动态语言要高效些。
在上述例子中,println方法不是jijiang、papa、mama任何一方的成员,因此,面对惰性求值问题时总是会执行,但是,如果改为下面这样:
trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = super.foo(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = super.foo(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") } object Mixin { // 如果包含菱形问题,则只执行最右边的 def apply(msg: String) = new Mixin(msg) with papa with mama // 由于Scala的惰性求值问题,包含多重继承的父类中的成员变量名称应该不一样,否则造成编译错误 }
输入结果是什么?答案是papa: mama: jijiang: 可能会奇怪,难道使用了super关键字就可以避免菱形问题?不是,输出结果顺序仍然是按照深度优先遍历的顺序,但有所不同。因为,在Scala中,super关键字是动态调用的,这意味着super中的方法并不是马上执行,而是在真正被调用时执行,即惰性求值。所以上述代码中,会按照jijiang<-papa<-mama的顺序组装字符串,即str2+msg<-str1.concat(msg),然后打印输出pirntln(str1.concat(str2+msg)),因此,在Scala中就可以达到屏蔽菱形问题的作用。
因为在new Mixin(msg) with papa with mama中,mama的super是papa,所以,下面代码是等价的:
trait jijiang { def foo(msg: String) = println(msg) } trait mama extends jijiang { val str1 = "mama: " override def foo(msg: String) = super.foo(str1.concat(msg)) } trait papa extends jijiang { val str2 = "papa: " override def foo(msg: String) = println(str2 + msg) } class Mixin private(msg: String) extends jijiang { def this() = this("mixin") }
标签:
原文地址:http://my.oschina.net/Barudisshu/blog/419678