7.3 以trait进行装饰
Trait可用于装饰①对象,使其具备一些能力。假设我们要对申请者进行不同的检查——信贷、犯罪记录、雇用记录等。我们并不总是对所有的检查项感兴趣。公寓申请人需要检查信贷和犯罪记录,而就业申请人则需要检查犯罪记录和之前的雇用记录。如果依靠创建特定的类对这些组合进行检查的话,最终,会为所需检查的各种排列组合都创建一个类。而且,如果决定进行额外的检查,就不得不改变处理这组检查的类。不,我们要避免这种类的激增。我们可以更具成效一些,对每种情况,只混入特定的检查。
①参见装饰模式,Gamma等的《设计模式:可复用面向对象软件的基础》[GHJV95]。
接下来,会介绍一个抽象类Check,用它可以对申请的细节进行通用的检查:
TraitsAndTypeConversions/Decorator.scala
abstract class Check {
def check() : String = "Checked Application Details..."
}
对不同类型的检查,比如信贷、犯罪记录和雇用记录,我们都会创建像下面这样的trait:
TraitsAndTypeConversions/Decorator.scala
trait CreditCheck extends Check {
override def check() : String = "Checked Credit..." + super.check()
}
trait EmploymentCheck extends Check {
override def check() : String = "Checked Employment..." + super.check()
}
trait CriminalRecordCheck extends Check {
override def check() : String = "Check Criminal Records..." + super.check()
}
这些trait都继承自Check
,因为我们只想把它们混入继承自Check
的类。继承这个类给予了我们两个能力。首先,这些trait只能混入继承自Check
的类。其次,在这些trait里可以使用Check
的方法。
我们感兴趣的是增强或是修饰check()
方法的实现,所以,需要将其标记为override
。这里的check()
实现调用了super.check()
。在trait里,通过super
调用的方法会经历一个延迟绑定的过程。这个调用并不是对基类的调用,而是对其左边混入的trait的调用——如果这个trait已经是混入的最左trait,那么这个调用就会解析成混入这个trait的类的方法。完成下面的例子,我们就会了解这个行为了。
目前为止,在这个例子里有一个抽象类,三个trait,没有任何具体类——因为根本不需要。检查公寓申请时,可以用一个实例把上面的trait和类放在一起:
TraitsAndTypeConversions/Decorator.scala
val apartmentApplication = new Check with CreditCheck with
CriminalRecordCheck
println(apartmentApplication check)
另一方面,可以这样检查雇用关系:
TraitsAndTypeConversions/Decorator.scala
val emplomentApplication = new Check with CriminalRecordCheck with
EmploymentCheck
println(emplomentApplication check)
如果想按照不同组合进行检查的话,只要按照希望的方式将trait混在一起即可。上面两段代码的效果如下:
Check Criminal Records...Checked Credit...Checked Application Details...
Checked Employment...Check Criminal Records...Checked Application Details...
最右的trait开始调用check()
。然后,顺着super.check()
,将调用传递到其左边的trait。最左的trait调用的是真正实例的check()
。
在Scala里,trait是一个强有力的工具,可以用它混入横切关注点。使用它们可以以较低的成本创建出高度可扩展的代码。无需创建一个拥有大量类和接口的层次结构,就可以快速地把必要的代码投入使用。