用特征来实现混入(mix-in)式的多重继承

Scala里相当于Java接口的是特征(Trait)。Trait的英文意思是特质和性状(本文称其为特征),实际上他比接口还功能强大。与接口不同的是,它还可以定义属性和方法的实现。Scala中特征被用于服务于单一目的功能模块的模块化中。通过混合这种特征(模块)群来实现各种应用程序的功能要求,Scala也是按照这个构想来设计的。

一般情况下Scala的类只能够继承单一父类,但是如果是特征的话就可以继承多个,从结果来看就是实现了多重继承。这可以被认为是同Ruby模块基本相同的功能。就看一下下面的例子吧。为了辨认方便,此后的特征名称前都加上前缀字母T。特征既可以继承类也可以继承其他特征。

  1. class Person ; //实验用的空类,如果使用了上一次的Person类,则下面的
  2. //PianoplayingTeacher类就需要构造参数了
  3. trait TTeacher extends Person {
  4. def teach //虚方法,没有实现
  5. }
  6. trait TPianoPlayer extends Person {
  7. def playPiano = {println("I’m playing piano. ")} //实方法,已实现
  8. }
  9. class PianoplayingTeacher extends Person with TTeacher with TPianoPlayer {
  10. def teach = {println("I’m teaching students. ")} //定义虚方法的实现
  11. }

如上所示,可以连着多个with语句来混合多个特征到一个类中。第一个被继承源用extends,第二个以后的就用with语句。正如大家所知道的,可以生成实例的是非抽象(abstract)的类。另外请注意一下从特征是不可以直接创建实例的。

那么就实际运行一下吧。

  1. scala> val t1 = new PianoplayingTeacher
  2. t1: PianoplayingTeacher = PianoplayingTeacher@170a650
  3. scala> t1.playPiano
  4. I’m playing piano.
  5. scala> t1.teach
  6. I’m teaching students.

实际上如下所示,可以在创建对象时才将特征各自的特点赋予对象。

  1. scala> val tanakaTaro = new Person with TTeacher with TPianoPlayer {
  2. | def teach = {println("I'm teaching students.")} }
  3. tanakaTaro: Person with TTeacher with TPianoPlayer = $anon$1@5bcd91
  4. scala> tanakaTaro playPiano
  5. I’m playing piano.
  6. scala> tanakaTaro teach
  7. I'm teaching students.

用特征来方便地实现面向方面的编程

充分利用特征的功能之后,就能方便地实现现今流行的面向方面编程(AOP)了。

首先,用特征来声明表示基本动作方法的模块Taction。

  1. trait TAction {
  2. def doAction
  3. }

接着作为被加入的方面,定义一下加入了前置处理和后置处理的特征TBeforeAfter。

  1. trait TBeforeAfter extends TAction {
  2. abstract override def doAction {
  3. println("/entry before-action") //doAction的前置处理
  4. super.doAction // 调用原来的处理
  5. println("/exit after-action") //doAction的后置处理
  6. }
  7. }

通过上面的abstract override def doAction {}语句来覆盖虚方法。具体来说这当中的super.doAction是关键,他调用了TAction的doAction方法。其原理是,由于doAction是虚方法,所以实际被执行的是被调用的实体类中所定义的方法。

那么将实际执行的实体类RealAction作为TAction的子类来实现吧。

  1. class RealAction extends TAction {
  2. def doAction = { println("** real action done!! **") }
  3. }

接着就执行一下。

  1. scala> val act1 = new RealAction with TBeforeAfter
  2. act1: RealAction with TBeforeAfter = $anon$1@3bce70
  3. scala> act1.doAction
  4. /entry before-action
  5. ** real action done!! **
  6. /exit after-action

仅仅这样还不好玩,接着为他定义一下别的方面,然后将这些方面加入到同一对象的方法中。接着定义一个将源方法执行两遍的方面。

  1. trait TTwiceAction extends TAction {
  2. abstract override def doAction {
  3. for ( i <- 1 to 2 ) { // 循环执行源方法的方面
  4. super.doAction // 调用源方法doAction
  5. println( " ==> No." + i )
  6. }
  7. }
  8. }

下面,将TBeforeAfter和TtwiceAction混合在一起后执行一下。

  1. scala> val act2 = new RealAction with TBeforeAfter with TTwiceAction
  2. act2: RealAction with TBeforeAfter with TTwiceAction = $anon$1@1fcbac1
  3. scala> act2.doAction
  4. /entry before-action
  5. ** real action done!! **
  6. /exit after-action
  7. ==> No.1
  8. /entry before-action
  9. ** real action done!! **
  10. /exit after-action
  11. ==> No.2

伴随着原来方法的before/after动作一起各自执行了两次。接着将混入顺序颠倒后再试一下。

  1. scala> val act3 = new RealAction with TTwiceAction with TBeforeAfter
  2. act3: RealAction with TTwiceAction with TBeforeAfter = $anon$1@6af790
  3. scala> act3.doAction
  4. /entry before-action
  5. ** real action done!! **
  6. ==> No.1
  7. ** real action done!! **
  8. ==> No.2
  9. /exit after-action

这样执行后,原来的实现方法被循环执行了两次,但是before/after则在循环以外整体只执行了一次。这是根据with语句定义的顺序来执行的,知道了这原理之后也就没有什么奇怪的了。Scala特性的如此混入顺序是和AspectJ的方面以及Spring的interceptor相同的。

这样不仅是before和after动作,只要更改了特征的实现就可以将各种方面动态地加入到原来的对象中去了,读者自己也可以尝试一下各种其他情况。

在Java中通过Decorator或Template Method模式来想尽办法实现的功能,在Scala中只要通过特征就可以轻松到手了。从这还可以延展开来,通过在原来的方法中插入挂钩的方法,即所谓的拦截者式面向方面的方法,就可以轻松地将各个方面通过特征来组件化了。

请读者如果想起Scala是怎样的强类型和静态化语言的话,那么就能够明白通过特征来加入新功能的特

点给他带来了多大的灵活性。

用特征来实现混入(mix-in)式的多重继承的更多相关文章

  1. JavaScript高级 面向对象(5)--最简单的继承方式,混入mix

    说明(2017.3.30): 1. 最简单的继承方式,混入mix <!DOCTYPE html> <html lang="en"> <head> ...

  2. 《Entity Framework 6 Recipes》中文翻译系列 (28) ------ 第五章 加载实体和导航属性之测试实体是否加载与显式加载关联实体

    翻译的初衷以及为什么选择<Entity Framework 6 Recipes>来学习,请看本系列开篇 5-11  测试实体引用或实体集合是否加载 问题 你想测试关联实体或实体集合是否已经 ...

  3. 设计模式 Mixin (混入类)

    混入(mix-in)类代表类之间的另一种关系.在C++中,混入类的语法类似于多重继承,但是语义完全不同.混入类回答"这个类还可以做什么"这个问题,答案经常以"-able& ...

  4. PCA与特征选取

    一.什么是PCA PCA,即PrincipalComponents Analysis,也就是主成份分析: 通俗的讲,就是寻找一系列的投影方向,高维数据按照这些方向投影后其方差最大化(方差最大的即是第一 ...

  5. DDos攻击,使用深度学习中 栈式自编码的算法

    转自:http://www.airghc.top/2016/11/10/Dection-DDos/ 最近研究了一篇论文,关于检测DDos攻击,使用了深度学习中 栈式自编码的算法,现在简要介绍一下内容论 ...

  6. mixins混入

    mixins混入:定义类,多重继承 使用方法见附件: mixins混入.zip    

  7. Effective Scala

    Effective Scala Marius Eriksen, Twitter Inc.marius@twitter.com (@marius)[translated by hongjiang(@ho ...

  8. 【Todo】【读书笔记】大数据Spark企业级实战版 & Scala学习

    下了这本<大数据Spark企业级实战版>, 另外还有一本<Spark大数据处理:技术.应用与性能优化(全)> 先看前一篇. 根据书里的前言里面,对于阅读顺序的建议.先看最后的S ...

  9. Scala学习笔记--特质trait

    http://outofmemory.cn/scala/scala-trait-introduce-and-example 与Java相似之处 Scala类型系统的基础部分是与Java非常相像的.Sc ...

随机推荐

  1. 转mysql复制主从集群搭建

    最近搭了个主从复制,中间出了点小问题,排查搞定,记录下来 1环境:虚拟机:OS:centos6.5Linux host2 2.6.32-431.el6.x86_64 #1 SMP Fri Nov 22 ...

  2. Java for LeetCode 035 Search Insert Position

    Given a sorted array and a target value, return the index if the target is found. If not, return the ...

  3. windows 说“我爱你”

    CreateObject("SAPI.SpVoice").Speak "I love YOU" 保存vbs

  4. p235习题1

  5. js call apply caller callee bind

    call apply bind作用类似.即调用一个对象的一个方法,以另一个对象替换当前对象. call 语法:call([thisObj[,arg1[, arg2[,   [,.argN]]]]]) ...

  6. Linux下Gcc生成和使用静态库和动态库详解

    参考文章:http://blog.chinaunix.net/uid-23592843-id-223539.html 一.基本概念 1.1什么是库 在windows平台和linux平台下都大量存在着库 ...

  7. Linux查看硬件配置信息

    转自:http://blog.163.com/yang_jianli/blog/static/1619900062010391127338/ 一:查看cpu more /proc/cpuinfo | ...

  8. 怎么判定一个mac地址是multicast还是unicast.

    MAC地址是以太网二层使用的一个48bit(6字节十六进制数)的地址,用来标识设备位置.MAC地址分成两部分,前24位是组织唯一标识符(OUI, Organizationally unique ide ...

  9. spring classpath & classpath*

    classpath-找到系统类路径下的第一个匹配的配置文件 classpath*-找到系统类路径下的所有符合要求的配置文件 参考资料:http://www.micmiu.com/j2ee/spring ...

  10. android自动更新程序,安装完以后就什么都没有了,没有出现安装成功的界面的问题

    转载自: http://blog.csdn.net/lovexieyuan520/article/details/9250099 在android软件开发中,总是需要更新版本,所以当有新版本开发的时候 ...