1.特质如何工作:特质是Scala代码复用的基础单元。特质将方法和字段封装起来,然后通过将它们混入类的方式来实现复用。特质的定义跟类定义很像,除了关键字trait:
trait Philosophical{
def philosophize()={
println("I consume memory,therefore I am!")
}
}
该特质名为Philosophical。它并没有声明一个超类,因此跟类一样,有一个默认的超类AnyRef。它定义了一个名为philosophize的方法,这个方法是具体的。我们可以用extends或with关键字将它混入到类中。混入特质跟多重继承有重要的区别。如:
class Frog extends Philosophical{
override def toString="green"
}
可以用extends关键字来混入特质,在这种情况下隐式继承了特质的超类。特质同时也定义了一个类型。如:
scala> val phil:Philosophical=new Frog
phil:Philosophical=green
scala> phil.philosophize()
I consume memory,therefore I am!
变量phil可以由任何混入了Philosophical的类的对象初始化。Frog也可以重写philosophize。重写的语法跟重写超类中声明的方法看上去一样。如:
class Animal
class Frog extends Animal with Philosophical{
override def toString="green"
override def philosophize={
println("It ain't easy being"+toString+"!")
}
}
在特质中可以做任何在类定义中做的事,语法也完全相同,除了以下两种情况:
class Point(x:Int,y:Int)
trait NoPoint(x:Int,y:Int)//不能编译
首先,特质不能有任何“类”参数(即那些传入类的主构造方法的参数)。另一个类和特质的区别在于类中的super是静态绑定的,而在特质中super是动态绑定的。如果在类中编写“super.toString”这样的代码,你会确切地知道实际调用的是哪一个实现。在你定义特质的时候并没有被定义。具体是哪个实现被调用,在每次该特质被混入到某个具体的类时,都会重新判定。这里super看上去有些奇特的行为是特质能实现可叠加修改的关键。
2.矩形对象:用我的话说,特质就像角色扮演游戏里人物的装备,如:
class Point(val x:Int,val y:Int)
trait Rectangular{
def topLeft:Point
def bottomRight:Point
def left=topLeft.x
def right=bottomRight.x
def width=right-left
//以及更多几何方法
}
Rectangular特质像一对护手装备,能装备它的前提是你得有双手(topLeft和bottomRight):
class Rectangle(val topLeft:Point,val bottomRight:Point)
extends Rectangular{
//其他方法...
)
这样你就可以享受Rectangular这件装备的加成了:
scala> val rect=new Rectangle(new Point(1,1),new Point(10,10))
rect:Rectangle=Rectangle@5f5da68c
scala>rect.left
res2:Int=1
scala>rect.right
res3:Int=10
scala>rect.width
res4:Int=9
你可以穿戴无数件装备,谁知道你定义的是个什么样的怪物,或许他每条触手都可以穿戴装备呢。
3.Ordered特质:可以提供让你比较大小的装备,你得实现compare方法提供比较大小的依据。Ordered要求你在混入时传入一个类型参数。如,用第六章的例子:
class Rational(n: Int, d: Int) extends Ordered[Rational]{
......
def compare(that: Rational): Int ={
val thisSum=numer*that.denom
val thatSum=denom*that.numer
if (thisSum>thatSum) 1
else if (thatSum==thisSum) 0
else -1
}
......
}
这样你就可以比较Rational类的大小了,如果this<=that,compare方法返回的是0或-1,其它以此类推。
4.作为可叠加修改的特质:特质让你修改类的方法,而它们的实现方式允许你将这些修改叠加起来:
abstract class superTest {
def prt( ele: String)
}
trait Outer extends superTest {
abstract override def prt(ele:String)=super.prt("("+ele+"Outer)")
}
trait Among extends superTest {
abstract override def prt(ele:String)=super.prt("("+ele+"Among)")
}
class Inside extends superTest {
override def prt(ele: String)=println("("+ele+"Inside)")
}
class Begin extends Inside with Among with Outer
由于特质中的super调用是动态绑定的,只要在给出了该方法具体定义的特质或类之后混入,super调用就可以正常工作。为了告诉编译器你是特意这样做的,必须将这样的方法标记为abstract override。这样的修饰符组合只允许用在特质的成员上,它的含义是该特质必须混入某个拥有该方法具体定义的类中(一种情况是继承同一个超类的方法)。
可以去测试当一个Begin对象调用prt方法的时候会打印出什么:
val d:Inside=new Begin
d.prt("|")
打印:
(((|Outer)Among)Inside)
混入特质的顺序是很重要的,粗略的讲,越靠右出现的特质越先起作用。在特质中的super调用的方法取决于类和混入该类的特质的线性化。当你用new实例化一个类时,Scala会将类及它所有继承的类和特质都拿出来,将它们线性地排列起来。如上面例子的线性化后执行顺序为:
Begin->Outer->superTest->Among->superTest->Inside->superTest->AnyRef->Any
移除重复的superTest保留最后一个后:
Begin->Outer->Among->Inside->superTest->AnyRef->Any
“class Begin extends Inside with Among with Outer”这条语句除了Begin外从后向前依次加入混入的特质及其超类(其实每个都继承AnyRef和Any,写出来很混乱,只保留最后一个)。之所以打印出那样的结果,prt方法调用顺序为:
Begin没有定义prt方法,调用Outer的prt方法,Outer的方法没有给出具体实现,而是继续向上传递去调用Among的prt方法,Among继续向上传递到Inside,Inside给出具体的打印,打印就是从Outer到Inside叠加起来的结果。可以去尝试,比如:提前给出具体方法,或者Begin中定义具体的方法。
5.要特质还是不要特质?
当你实现某个可复用的行为集合时,都需要决定是用特质还是抽象类。给出一些可以考虑的指导:如果某个行为不会被复用,用具体的类。如果某个行为可能被用于多个不相关的类,用特质。如果想要从Java代码中继承某个行为,用抽象类。
我的理解是:特质就像装备,抽象和类就像衣服。装备可以同时全部脱下来,这种设计图纸出的东西只要能都可以穿戴上,反正只要有给力的属性加成就可以了。衣服呢,需要一件一件的来,一种类型的衣服不适合所有人。所以看你喜欢设计什么咯,衣服是肯定要的(额,我不确定...)。
如果某个Scala特质只有抽象方法,它会被直接翻译成Java的接口。当某个特质增加或减少成员时,任何继承(不是混入么?)自该特质的类都需要被重新编译,哪怕它们并没有改变。如果外部的使用方只是调用到这个行为,而不是继承,那么使用特质也是可以的。
如果你考虑了上述所有问题之后,仍然没有答案,那么就从特质开始吧。