在上一节看到了scala的在实例一级的选择性混入就不得不感叹scala在语法上的扩展性。就通过这样一个特性scala简化了很多在java中的编程概念和设计模式。
比如说在java中常用的组合,以及装饰模式。下面看个书中的例子,详细说说如何使用trait进行装饰。
假设我们要对一个人进行检查,包括信用记录、收支记录、犯罪记录和工作记录等。但是我们并不会总是都会用到所有的检查,比如要买房时会检查信用记录和收支记录,申请出境时会检查犯罪记录和工作记录。
想想该怎么做:最简单的思路是为每种检查创建一个方法,然后在需要的时候将这些方法组合起来。更常用的方案是创建一个检查接口,为每种检查创建一个类,并实现检查方法。为基础的检查,比如信用记录、收支记录创建检查等创建一个基础类还是可行的,但是如果要为每一项具体的检查创建一个类就会面临一个问题:类膨胀。scala可以让我们避免这种问题,使用trait混入实例可以实现面对不同的检查提供不同的处理方案这样的灵活性。
首先我们需要定义一个抽象类Checker,实现一些在检查中的通用的行为:
abstract class Check { def check(): String = "Checked Application Details..." }
然后为每一类基础性的检查创建一个trait
trait CreditCheck extends Check { override def check(): String = "Checked Credit..." + super.check() } trait BalanceCheck extends Check{ override def check(): String = "Checked Balance..." + 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的方法。
在每个trait里,都重写了check方法,在重写的同时也引用了Check类的方法,这也可以说是对Check类的修饰或增强。
trait里,通过super调用的方法会经历一个延迟绑定的过程。这个调用并不是对基类的调用,而是对其左边混入的trait的调用——如果这个trait已经是混入的最左trait,那么这个调用就会解析成混入这个trait的类的方法。具体如何可以看一下下面的例子是如何实现的。
来看一个买房申请:
val apartmentApplication = new Check with CreditCheck with BalanceCheck println(apartmentApplication check)
在创建Check实例的同时混入了两个trait:CreditCheck和BalanceCheck。同样的也可以这样实现出境申请:
val exitApplication = new Check with CriminalRecordCheck with EmploymentCheck println(exitApplication check)
可以看出,如果想要按照不同的组合进行检查的话,只需要按照希望的组合将trait混入即可。
上面两段代码的执行结果如下:
最右的trait开始调用check()。然后,顺着super.check(),将调用传递到其左边的trait。最左的trait调用的是真正实例的check()。
在Scala里,trait是一个强有力的工具,可以用它混入横切关注点。使用它们可以以较低的成本创建出高度可扩展的代码。无需创建一个拥有大量类和接口的层次结构,就可以快速地把必要的代码投入使用。
###########