文档章节

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

Barudisshu
 Barudisshu
发布于 2015/05/25 16:34
字数 1618
阅读 2874
收藏 4

在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")
}

© 著作权归作者所有

Barudisshu
粉丝 27
博文 62
码字总数 70723
作品 0
茂名
程序员
私信 提问
加载中

评论(1)

Daqu
Daqu
厉害啊
Dart的语法详解系列篇(三)-- mixin入门详解

版权声明:本文为博主原创文章,未经博主允许不得转载。https://www.jianshu.com/p/405ba04ffbdc 转载请标明出处: https://www.jianshu.com/p/405ba04ffbdc 本文出自 AWeiLoveAndroid的博客...

AWeiLoveAndroid
2018/12/30
0
0
C++-对象继承中的内存布局

关于单继承和多继承的简单概念可参考此文章(以下编译环境均为WIN32+VS2015) 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。...

sssssuuuuu666
2017/07/26
0
0
scala class和object,trait的区别

Scala类 class Counter { private var value = 0 // 必须初始化字段 def increment() { value += 1 } // 方法默认公有 def current = value // 调用必须是myCounter.current这种风格}class ......

蓝狐乐队
2016/01/19
4.8K
0
java 8 default关键字

java 8当中引入了一些新的特性,最近我们会陆续介绍这些特性,从default开始。 一直以来,我们有一个认识,interface当中是不能有方法实现的,但是从java 8开始,这个事变了,因为有了defau...

大胖和二胖
2016/10/12
809
0
关于规定那点事——喷子不是想当就能当的

http://www.oschina.net/question/127301_71215 一个语言引入新特性肯定会出问题,拿运算符重载来说,定义两个函数 double max(double a, double b)和float max(float a, float b),在某个函...

小郭一号
2012/09/23
546
12

没有更多内容

加载失败,请刷新页面

加载更多

python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
12分钟前
1
0
OSChina 周日乱弹 —— 我,小小编辑,食人族酋长

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @宇辰OSC :分享娃娃的单曲《飘洋过海来看你》: #今日歌曲推荐# 《飘洋过海来看你》- 娃娃 手机党少年们想听歌,请使劲儿戳(这里) @宇辰OSC...

小小编辑
今天
743
10
MongoDB系列-- SpringBoot 中对 MongoDB 的 基本操作

SpringBoot 中对 MongoDB 的 基本操作 Database 库的创建 首先 在MongoDB 操作客户端 Robo 3T 中 创建数据库: 增加用户User: 创建 Collections 集合(类似mysql 中的 表): 后面我们大部分都...

TcWong
今天
40
0
spring cloud

一、从面试题入手 1.1、什么事微服务 1.2、微服务之间如何独立通讯的 1.3、springCloud和Dubbo有哪些区别 1.通信机制:DUbbo基于RPC远程过程调用;微服务cloud基于http restFUL API 1.4、spr...

榴莲黑芝麻糊
今天
26
0
Executor线程池原理与源码解读

线程池为线程生命周期的开销和资源不足问题提供了解决方 案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。 线程实现方式 Thread、Runnable、Callable //实现Runnable接口的...

小强的进阶之路
昨天
79
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部