[关闭]
@amoszhou 2014-05-15T02:23:17.000000Z 字数 2499 阅读 1569

特质Trait

scala


1.为什么没有多重继承

我们都知道在c++中,有多重继承,Java中没有,scala中其实也没有。那么主要为了防止菱形继承出现,具体说,就是B,C类继承至A类,D类又继承至B,C类,这样就构成了一个菱形。那么,如果B,C都重写了A类的某个方法method1,那么D中method1到底来自那个类列?这样就导致了整个问题的复杂性。

但是在很多场合,多重继承又能帮我们很方便解决问题,比如继承至2个毫不相干的类,如果只能单继承就会麻烦很多,因此就有了“特质”Trait

2.特质介绍

在scala中,没有接口,只有trait。trait可以同时拥有抽象方法和具体方法,而一个类可以实现多个trait。
所以一个trait的写法如下:

  1. trait Logger {
  2. def log(msg : String)
  3. }
  4. class PrintLogger extends Logger{ //用extends
  5. override def log(msg: String): Unit = println(msg) //不需要用override和抽象类的抽象方法一样
  6. }

我们会说Logger的功能被"混入" PrintLogger

一个类可以混入多个trait,用with拼起来。
class PrintLogger extends Logger with Cloneable with Serializable。编译器解读顺序是,先解读:
Logger with Cloneable with Serializable这是一个整体,然后才是用extends

trait也可以带有具体实现,比如

  1. trait ConsoleLogger{
  2. def log(msg :String):Unit = println(msg)
  3. }

特质拥有具体行为存在一个弊端:当特质的行为发生改变时,所有混入了该物质的类都必须重新编译。这个主要是与trait的实现有关系,大家可以想想如果让你用Java来实trait类似的东西,你会怎么做。

3.在对象中混入

在前面我们介绍了trait,并演示了如何在class中进行"混入"。不光如此,我们还可以在具体对象中,进行混入。比如:

  1. val p = new Person with ConsoleLogger

这样相当于我们可以在运行时,来改变具体某个对象的行为。同样,我们可以在定义(实例化)对象的时候,给对象混入多个trait。
当有多个trait被混入的时候,trait的处理顺序是从最后一个开始(即最右边的一个)往前。

4.用叠加混入实现装饰器模式

  1. trait Check{
  2. def check()="Checking Application Details..."
  3. }
  4. trait CreditCheck extends Check{
  5. override def check()="checking Credit....." + super.check() + "...1"
  6. }
  7. trait EmployeeCheck extends Check{
  8. override def check()="Checking Employee....." + super.check()+ "...2"
  9. }
  10. trait CrimnaCheck extends Check{
  11. override def check()="Checking CrimnaCheck....." + super.check()+ "...3"
  12. }
  13. object TraitTest extends App {
  14. val check = new Check with CreditCheck with EmployeeCheck with CrimnaCheck
  15. println(check.check())
  16. }

打印结果:Checking CrimnaCheck.....Checking Employee.....checking Credit.....Checking Application Details......1...2...3。是不是装饰器模式就实现了!

从上面这个例子,我们可以得出如下结论:

在trait重写方法的时候有个需要注意的地方:

  1. trait Test{
  2. def test()
  3. }
  4. trait MyTest extends Test{
  5. override def test() = super.test()
  6. }

编写的时候没错,但是一编译,运行就报错。提示说MyTest里面test方法是个抽象的,要么换成具体实现,要么就标记为abstract。

对于抽象字段,则必须要一个具体使用类中提供该抽象字段。不必使用override.


也只有当混入的多个trait中某几个(至少2个)继承至同一个class或者trait的时候,调用super.method才会有意义。

如果你想直接指定具体那个特质的方法被调用,可以用super[指定的特质类].method

在实例对象中能混入trait,大大提升了代码的灵活性!


5.总结与对比

trait看上去类似于Java中的接口,用起来又像抽象类。
在Java中,如果把我们方法都抽象到接口中,那么则每个实现类都要实现这些方法,很烦锁。如果我们定义一个默认的实现类,具体子类继承子类,那么子类如果又想要扩展其它方法,就会变得比较麻烦,因为Java是单继承。 那么trait的出现,就是为了解决这种不灵活的问题。如果你某个类需要继承这些方法,混入即可,甚至你可以在某些对象实例中混入。

其实在实现的时候,如果trait只有方法,就会被“翻译”成Jvm接口,如果有具体方法,即会创建一“符合某种规则”的类,比如类名 = 接口名+某个特殊后缀。

在学完trait的使用时,我想了一下如果让我实现这样的功能,我会怎么做,结果与scala的做法不谋而合。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注