用特征来实现混入(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. ShortestPath:Layout(POJ 3169)(差分约束的应用)

                布局 题目大意:有N头牛,编号1-N,按编号排成一排准备吃东西,有些牛的关系比较好,所以希望他们不超过一定的距离,也有一些牛的关系很不好,所以希望彼此之间要满足某个关系,牛可以 ...

  2. springMVC的一些工具类

    springMVC的一些工具类,主要有转换器,读取器 读取文件: package cn.edu.hbcf.common.springmvc; import java.util.HashMap; imp ...

  3. 理解Fragment生命周期

    官网帮助文档链接:  http://developer.android.com/guide/components/fragments.html 主要看两张图,和跑代码 一,Fragment的生命周 二 ...

  4. Power Strings(poj 2406)

    题意:求字符串中循环节出现的次数 KMP!!! #include<cstdio> #include<iostream> #include<cstring> #def ...

  5. 队列——解密QQ号

    队列——解密QQ号 --转自啊哈磊[坐在马桶上看算法]算法4:队列——解密QQ号 新学期开始了,小哈是小哼的新同桌(小哈是个小美女哦~),小哼向小哈询问QQ号,小哈当然不会直接告诉小哼啦,原因嘛你懂的 ...

  6. makefile_1(初识make)

    Makefile有三个非常有用的变量.分别是$@,$^,$<代表的意义分别是: $@--目标文件,$^--所有的依赖文件,$<--第一个依赖文件. LIBS = -lmCFLAGS = - ...

  7. Java创建Web项目

    首先下载Tomcat服务,用来运行JAVA程序,跟windows中的IIS类似 下载地址:tomcat.apache.org ,最好下载ZIP压缩版的,解压后就可以直接用.如下图: 检查Tomcat是 ...

  8. SQL的IN, SOME,ANY,IN

    表dbo.Student有12条数据 name 123123123123123123123123123123123123大雄1阿华浩然菊花大姐123123 1.some,any用法一样(不知道有没有其 ...

  9. Sql server之路 (三)添加本地数据库SDF文件

    12月25日 今天搞了半天 添加本地数据库Sdf文件到项目里.总是出现问题. 安装环境 Vs2008 没有安装的环境 1.Vs2008 sp1 2. 适用于 Windows 桌面的 Microsoft ...

  10. Wcf for wp8 调试Wcf服务程序(四)

    1.要以管理员身份进行登录vs2012 否则会提示: 2.在wcf 服务程序上设为启动项 3.编译 运行你的wcf服务程序 点击 就出现wcftestclient.exe 客户端调试程序 双击点击Re ...