Scala编程(第12章:特质)

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的接口。当某个特质增加或减少成员时,任何继承(不是混入么?)自该特质的类都需要被重新编译,哪怕它们并没有改变。如果外部的使用方只是调用到这个行为,而不是继承,那么使用特质也是可以的。

      如果你考虑了上述所有问题之后,仍然没有答案,那么就从特质开始吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值