@amoszhou
2014-05-15T02:23:17.000000Z
字数 2499
阅读 1569
scala
我们都知道在c++中,有多重继承,Java中没有,scala中其实也没有。那么主要为了防止菱形继承出现,具体说,就是B,C类继承至A类,D类又继承至B,C类,这样就构成了一个菱形。那么,如果B,C都重写了A类的某个方法method1
,那么D中method1
到底来自那个类列?这样就导致了整个问题的复杂性。
但是在很多场合,多重继承又能帮我们很方便解决问题,比如继承至2个毫不相干的类,如果只能单继承就会麻烦很多,因此就有了“特质”Trait
在scala中,没有接口,只有trait。trait可以同时拥有抽象方法和具体方法,而一个类可以实现多个trait。
所以一个trait的写法如下:
trait Logger {
def log(msg : String)
}
class PrintLogger extends Logger{ //用extends
override def log(msg: String): Unit = println(msg) //不需要用override和抽象类的抽象方法一样
}
我们会说Logger的功能被"混入" PrintLogger
一个类可以混入多个trait,用with拼起来。
class PrintLogger extends Logger with Cloneable with Serializable
。编译器解读顺序是,先解读:
Logger with Cloneable with Serializable
这是一个整体,然后才是用extends
trait也可以带有具体实现,比如
trait ConsoleLogger{
def log(msg :String):Unit = println(msg)
}
特质拥有具体行为存在一个弊端:当特质的行为发生改变时,所有混入了该物质的类都必须重新编译。这个主要是与trait的实现有关系,大家可以想想如果让你用Java来实trait类似的东西,你会怎么做。
在前面我们介绍了trait,并演示了如何在class中进行"混入"。不光如此,我们还可以在具体对象中,进行混入。比如:
val p = new Person with ConsoleLogger
这样相当于我们可以在运行时,来改变具体某个对象的行为。同样,我们可以在定义(实例化)对象的时候,给对象混入多个trait。
当有多个trait被混入的时候,trait的处理顺序是从最后一个开始(即最右边的一个)往前。
trait Check{
def check()="Checking Application Details..."
}
trait CreditCheck extends Check{
override def check()="checking Credit....." + super.check() + "...1"
}
trait EmployeeCheck extends Check{
override def check()="Checking Employee....." + super.check()+ "...2"
}
trait CrimnaCheck extends Check{
override def check()="Checking CrimnaCheck....." + super.check()+ "...3"
}
object TraitTest extends App {
val check = new Check with CreditCheck with EmployeeCheck with CrimnaCheck
println(check.check())
}
打印结果:Checking CrimnaCheck.....Checking Employee.....checking Credit.....Checking Application Details......1...2...3
。是不是装饰器模式就实现了!
从上面这个例子,我们可以得出如下结论:
super.check()
。在trait中,super.method()
调用的不是超类的方法,而是混入层级中的下一个特质。override
,那怕是抽象方法,也需要。这与类重写trait方法_不需要_override是不同的在trait重写方法的时候有个需要注意的地方:
trait Test{
def test()
}
trait MyTest extends Test{
override def test() = super.test()
}
编写的时候没错,但是一编译,运行就报错。提示说MyTest里面test方法是个抽象的,要么换成具体实现,要么就标记为abstract。
对于抽象字段,则必须要一个具体使用类中提供该抽象字段。不必使用override.
也只有当混入的多个trait中某几个(至少2个)继承至同一个class或者trait的时候,调用super.method
才会有意义。
如果你想直接指定具体那个特质的方法被调用,可以用super[指定的特质类].method
在实例对象中能混入trait,大大提升了代码的灵活性!
trait看上去类似于Java中的接口,用起来又像抽象类。
在Java中,如果把我们方法都抽象到接口中,那么则每个实现类都要实现这些方法,很烦锁。如果我们定义一个默认的实现类,具体子类继承子类,那么子类如果又想要扩展其它方法,就会变得比较麻烦,因为Java是单继承。 那么trait的出现,就是为了解决这种不灵活的问题。如果你某个类需要继承这些方法,混入即可,甚至你可以在某些对象实例中混入。
其实在实现的时候,如果trait只有方法,就会被“翻译”成Jvm接口,如果有具体方法,即会创建一“符合某种规则”的类,比如类名 = 接口名+某个特殊后缀。
在学完trait的使用时,我想了一下如果让我实现这样的功能,我会怎么做,结果与scala的做法不谋而合。