关于Scala多重继承的菱形问题

原创
2015/05/25 16:34
阅读数 7.5K

在Scala中的trait中引入了混入的概念,即Mixin of trait。

什么是混入(mixin)

    可能翻译不准确,有人也称之为混入类(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)。
    在介绍Scala混入机制之前,先说明多重继承的菱形问题。

多重继承的钻石问题

    又叫菱形问题(有时叫做“致命的死钻石”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中几个概念。

JVM上的trait类

    编程语言性能的瓶颈关键在编译器,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处理菱形问题的机制。

Scala多重继承机制

    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")
}
展开阅读全文
打赏
0
4 收藏
分享
加载中
厉害啊
2015/07/31 12:50
回复
举报
更多评论
打赏
1 评论
4 收藏
0
分享
返回顶部
顶部