转载自: http://developer.51cto.com/art/200912/166840.htm
本文选自 Scala 讲座的第四篇的内容, 介绍用特征来实现混入 (mix-in) 式的多重继承, 用特征来方便地实现面向方面的编程, Scala 的类型体系, 希望大家喜欢.
虽然混入继承看起来很麻烦, 但是如果真的应用上, 还是可以解决大问题滴.
本文节选自最近在日本十分流行的 Scala 讲座系列的第四篇, 由 JavaEye 的 fineqtbull 翻译. 本系列的作者牛尾刚在日本写过不少有关 Java 和 Ruby 的书籍, 相当受欢迎.
序言
上一次对比 Java 说明了 Scala 的特点和类定义的方法. 这一次将更加深入一点, 还是以迷你旅行的方式探险一下 Scala 的语法特点.
接下来介绍一下用代替 Java 接口的特征 (Trait) 来实现的混入 (mix-in) 多重继承, 类型层次和集合.
用特征来实现混入 (mix-in) 式的多重继承
Scala 里相当于 Java 接口的是特征 (Trait).Trait 的英文意思是特质和性状 (本文称其为特征), 实际上他比接口还功能强大. 与接口不同的是, 它还可以定义属性和方法的实现. Scala 中特征被用于服务于单一目的功能模块的模块化中. 通过混合这种特征(模块) 群来实现各种应用程序的功能要求, Scala 也是按照这个构想来设计的.
一般情况下 Scala 的类只能够继承单一父类, 但是如果是特征的话就可以继承多个, 从结果来看就是实现了多重继承. 这可以被认为是同 Ruby 模块基本相同的功能. 就看一下下面的例子吧. 为了辨认方便, 此后的特征名称前都加上前缀字母 T. 特征既可以继承类也可以继承其他特征.
class Person ; // 实验用的空类, 如果使用了上一次的 Person 类, 则下面的 //PianoplayingTeacher 类就需要构造参数了 trait TTeacher extends Person { def teach // 虚方法, 没有实现 } trait TPianoPlayer extends Person { def playPiano = {println("I'm playing piano. ")} // 实方法, 已实现 } class PianoplayingTeacher extends Person with TTeacher with TPianoPlayer { def teach = {println("I'm teaching students.")} // 定义虚方法的实现 }
如上所示, 可以连着多个 with 语句来混合多个特征到一个类中. 第一个被继承源用 extends, 第二个以后的就用 with 语句. 正如大家所知道的, 可以生成实例的是非抽象 (abstract) 的类. 另外请注意一下从特征是不可以直接创建实例的.
那么就实际运行一下吧.
scala > val t1 = new PianoplayingTeacher t1: PianoplayingTeacher = PianoplayingTeacher@170a650scala > t1.playPiano I'm playing piano. scala > t1.teach I'm teaching students.
实际上如下所示, 可以在创建对象时才将特征各自的特点赋予对象.
scala > val tanakaTaro = new Person with TTeacher with TPianoPlayer { | def teach = {
println("I'm teaching students.")
}
} tanakaTaro: Person with TTeacher with TPianoPlayer = $anon$1@5bcd91scala > tanakaTaro playPiano I 'm playing piano. scala > tanakaTaro teach I'm teaching students. ''
用特征来方便地实现面向方面的编程
充分利用特征的功能之后, 就能方便地实现现今流行的面向方面编程 (AOP) 了.
首先, 用特征来声明表示基本动作方法的模块 Taction.
trait TAction { def doAction
}
接着作为被加入的方面, 定义一下加入了前置处理和后置处理的特征 TBeforeAfter.
trait TBeforeAfter extends TAction { abstract override def doAction { println("/entry before-action") //doAction 的前置处理 super.doAction // 调用原来的处理 println("/exit after-action") //doAction 的后置处理 } }
通过上面的 abstract override def doAction {} 语句来覆盖虚方法. 具体来说这当中的 super.doAction 是关键, 他调用了 TAction 的 doAction 方法. 其原理是, 由于 doAction 是虚方法, 所以实际被执行的是被调用的实体类中所定义的方法.
那么将实际执行的实体类 RealAction 作为 TAction 的子类来实现吧.
class RealAction extends TAction { def doAction = { println("** real action done!! **")
}
}
接着就执行一下.
scala > val act1 = new RealAction with TBeforeAfter act1: RealAction with TBeforeAfter = $anon$1@3bce70scala > act1.doAction / entry before - action * * real action done !! * * / exit after - action
仅仅这样还不好玩, 接着为他定义一下别的方面, 然后将这些方面加入到同一对象的方法中. 接着定义一个将源方法执行两遍的方面.
trait TTwiceAction extends TAction { abstract override def doAction {
for ( i < - 1 to 2 ) {
// 循环执行源方法的方面
super.doAction
// 调用源方法
doAction println( "==> No." + i ) } } }
下面, 将 TBeforeAfter 和 TtwiceAction 混合在一起后执行一下.
scala > val act2 = new RealAction with TBeforeAfter with TTwiceAction act2: RealAction with TBeforeAfter with TTwiceAction = $anon$1@1fcbac1scala > act2.doAction / entry before - action * * real action done !! * * / exit after - action == > No.1 / entry before - action * * real action done !! * * / exit after - action == > No.2
伴随着原来方法的 before/after 动作一起各自执行了两次. 接着将混入顺序颠倒后再试一下.
scala > val act3 = new RealAction with TTwiceAction with TBeforeAfter act3: RealAction with TTwiceAction with TBeforeAfter = $anon$1@6af790scala > act3.doAction / entry before - action * * real action done !! * * == > No.1 * * real action done !! * * == > No.2 / exit after - action
这样执行后, 原来的实现方法被循环执行了两次, 但是 before/after 则在循环以外整体只执行了一次. 这是根据 with 语句定义的顺序来执行的, 知道了这原理之后也就没有什么奇怪的了. Scala 特性的如此混入顺序是和 AspectJ 的方面以及 Spring 的 interceptor 相同的.
这样不仅是 before 和 after 动作, 只要更改了特征的实现就可以将各种方面动态地加入到原来的对象中去了, 读者自己也可以尝试一下各种其他情况.
在 Java 中通过 Decorator 或 Template Method 模式来想尽办法实现的功能, 在 Scala 中只要通过特征就可以轻松到手了. 从这还可以延展开来, 通过在原来的方法中插入挂钩的方法, 即所谓的拦截者式面向方面的方法, 就可以轻松地将各个方面通过特征来组件化了.
请读者如果想起 Scala 是怎样的强类型和静态化语言的话, 那么就能够明白通过特征来加入新功能的特
点给他带来了多大的灵活性.
Scala 的类型体系(基本类型)
Scala 中可使用的基本数据都以类的形式被定义了, 所以基本类型与用户定义类型可以认为是没有区别的. 虽然这么说, Scala 还是提供了与 Java 的数据类型相对应的类定义群(图 4-1). 这绝不是包装类, 在编译后他们将被映射为 Java 的字节码, 所以性能上是绝对没有问题的.
图 4-1与 Scala 基本类型相对应的类群
如下例程序所示, 对于整数对象 7 可以响应各种消息(方法). 既可以执行 toString 方法来转换成字符串, 又可以使用 to 这个执行 Int => Range 的方法. 附带说一下, 7 to 20 相当于 7.to(20), 该方法的执行结果是 Range(7,8, 9, ... 19, 20). 对于该范围对象适用了 foreach((i)=>print(i) ),print _则与一个参数的匿名函数 (i) => print(i) 相当.
scala > 7.toString res2: java.lang.String = 7scala > 7 to 20 foreach( print _ ) 7891011121314151617181920
实际上, Scala 在编译器自动引入的 Predef 单例对象中定义了为了兼容 Java 基础类型所存在的类型别名. 例如 boolean, char, byte, short, int, long, float, double 被定义了, 这些别名实际上是引用了 Scala.Boolean,Scala.Char,Scala.Byte 等 Scala 的类. 可能的话, 为了提高 "Scala 中说所有数据都是对象" 这种意识, 建议尽量一开始就使用 Int,Boolean,Float 等原来的类名.
不过, 在 Scala 种并没有类型转换操作符, 而是在所有类的基类 Any 中定义了具有同等功能的方法 asInstanceOf[X]. 用这方法就可以把类型转换为 X 了. Any 类中同时还定义了相当于 instanceof 操作符的 isInstanceOf[X] 方法.
图 4-2Scala 类层次的基本结构
特别是该类层次中 Iterable 下的集类型在函数式编程中大显身手. 其中的可变 (mutable) 与非可变 (immutable) 两大系列的类层次基本上呈现出镜像关系, 可以充分发挥出函数式语言功能的当然就是非可变集类型群了.
来源: http://lib.csdn.net/article/scala/36866