7.1 Trait
Trait是指可以混入或融入一个类层次结构的行为。比如说,先对Friend
建模,然后将其混入任何类:Man
、Woman
、Dog
等,而不用让它们都从一个公共的基类继承下来。
假定我们已经建模出Human
,现在,想让它成为朋友。朋友是能够倾听你说话的人。所以,我们要给Human
类增加一个listen
方法,下面就是:
class Human(val name: String) {
def listen() = println("Your friend " + name + " is listening")
}
class Man(override val name: String) extends Human(name)
class Woman(override val name: String) extends Human(name)
上面代码的一个不足之处在于,朋友这方面的特性不太突出,而且它被并到Human
类里。另外,开发几周后,我们意识到我们忘记了人类最好的朋友——狗是伟大的朋友——当我们有太多的无法释怀时,它们会安静地听我们叙述。但是,怎样才能让狗成为我们的朋友呢?我们不能为此就让Dog
从Human
继承下来。Java解决这个问题的方式是创建一个接口Friend
,让Human
和Dog
都实现这个接口。我们不得不在这两个类里提供不同的实现,不管实现是不是真的不同。
这就是Scala的trait介入的地方了。Trait像一个拥有部分实现的接口。trait里定义和初始化的val
和var
会在混入trait的类的内部得到实现。定义过而未初始化的val
和var
则认为是抽象的,需要由混入这些trait的类实现。下面将Friend
这个概念重新实现为trait:
TraitsAndTypeConversions/Friend.scala
trait Friend {
val name: String
def listen() = println("Your friend " + name + " is listening")
}
这里,把Friend
定义为trait。它有一个名叫name
的val
,被当作abstract对待。此外还有一个listen()
方法。name
实际的定义或实现由混入这个trait的类提供。下面来看一下混入上面这个trait的方式:
TraitsAndTypeConversions/Human.scala
class Human(val name: String) extends Friend
TraitsAndTypeConversions/Man.scala
class Man(override val name: String) extends Human(name)
TraitsAndTypeConversions/Woman.scala
class Woman(override val name: String) extends Human(name)
Human
类混入了Friend trait
。如果类并不继承其他任何类的话,那么可以使用extends
关键字混入trait。Human
类及其派生类Man
和Woman
简单的使用了trait提供的listen()
方法的实现。如果需要的话,也可以改写这个实现,很快就会看到。
混入trait的数量可以是任意的。用关键字with
就可以混入更多的trait。如果类已经继承了另一个类,就像下面这个例子里的Dog
,还可以用关键字with
混入第一个trait。除了混入trait之外,下面还在Dog
里改写了listen()
方法。
TraitsAndTypeConversions/Animal.scala
class Animal
TraitsAndTypeConversions/Dog.scala
class Dog(val name: String) extends Animal with Friend {
//optionally override method here.
override def listen = println(name + "'s listening quietly")
}
一个类被混入trait之后,通过它的实例可以调用到trait的方法,也可以把它的引用当做trait的引用。
TraitsAndTypeConversions/UseFriend.scala
val john = new Man("John")
val sara = new Woman("Sara")
val comet = new Dog("Comet")
john.listen
sara.listen
comet.listen
val mansBestFriend : Friend = comet
mansBestFriend.listen
def helpAsFriend(friend: Friend) = friend listen
helpAsFriend(sara)
helpAsFriend(comet)
上面代码的输出如下:
Your friend John is listening
Your friend Sara is listening
Comet's listening quietly
Comet's listening quietly
Your friend Sara is listening
Comet's listening quietly
trait看上去很像类,但是还有一些很大的差别。首先,它们需要混入类去实现那些已声明的而未初始化的(即抽象的)变量和值。其次,它们的构造器不能有任何参数。trait会编译成Java的接口,还有对应的实现类,里面包含了trait实现的方法。
多重继承通常会带来方法冲突的问题,trait并不会为这个问题所扰。通过延迟绑定混入类的方法,它们有效的回避了这一点。如此一来,在trait里调用super
可能解析成另一个trait的方法,也可能会解析成混入类的方法,我们很快就会看到这一点。