重编辑于:2022/8/12,只保留了精简的 Trait 机制介绍和用法。
声明特质
Java 使用 interface
声明一个接口,使用 implements
表示实现一个接口。Java 类允许对接口进行多重继承。在 JDK 9 之后,Java 的接口已经可以定义默认方法,私有方法,静态声明。
在 Scala 中,和接口类似的概念是 trait
,称之特质。这里只使用 extends
关键字表示 实现 一个或多个特质。当一个类继承了多个特质时,使用 with
关键字衔接:
class C extends Trait1 with Trait2
类同时还继承父类,则先将父类放在 extends
关键字后面。
class C extends CC with Trait1 with Trait2
特质之间也存在着继承关系,同时可以定义伴生对象。
混入特质
还可以在构造对象时 混入 特质,这会使编译器在不更改被拓展类的前提下合成出一个新的类型。比如:
trait Quack {
def quack(): Unit = println("quack")
}
class Animal
type Duck = Animal with Quack
val duck : Duck = new Animal() with Quack
duck.quack()
动态混入特质不会影响到 Animal
自身的定义,我们可以利用这一点类 ( 不能是 final
的 ) 实现 OCP 拓展。在上文的例子中,duck
是 Animal with Quack
类型,它独立于 Animal
或是 Quack
类型。
叠加特质
当混入多个存在继承关系的特质时,Scala 依次从左到右初始化特质,但按照从右到左的顺序寻找方法。比如:
trait Quack {
def quack(): Unit = println("quack")
}
trait QuackQuack extends Quack {
override def quack() : Unit = println("quack quack")
}
trait QuackQuackQuack extends Quack {
override def quack() : Unit = super.quack()
}
val noisyDuck = new Animal() with QuackQuack with QuackQuackQuack
noisyDuck.quack()
上述代码内的 super.quack()
方法事实上会 优先 指向左侧的 QuackQuack
而非 Quack
,因此程序的输出将是 quack quack
。有一种声明可以令 super
直接指向 Quack
:
trait QuackQuackQuack extends Quack {
override def quack() : Unit = super[Quack].quack()
}
这样,程序的输出将是 quack
。
拓展类的特质
特质可以继承最多一个类,以表示对这个类的拓展。这里有一个前提:目标类必须是非 final
的。比如:
class B
trait Mod1
trait Mod2
trait BExtension extends B with Mod1 with Mod2{
def f() : Unit = println("f")
}
// 对类型 A 而言,它相当于继承了被拓展后的类型 B。
class A extends BExtension
new A().f()
在 Scala 中对类实现 OCP 拓展还可以借助隐式类型来实现,且不受 final
的限制。
限定目标类的特质
首先介绍自身引用别名 ( 又称之自身类型,self Type ) 的概念。它常用于存在嵌套定义 ( 比如内部类 ) 的类当中,通过一个别名指代外部类自身。
class A {
// self 它指代外部类的 A()
// 你可以起任何其它的代称, it, item, ...
self =>
def f() : Unit = println("fA")
class B {
def f() : Unit = println("fB")
def g1() : Unit = this.f() // 这里的 this 指代 B
def g2() : Unit = self.f() // 通过别名指向外部类 A
}
}
特质可以利用引用别名限定它只能被哪些类接入。比如:
class Animal
trait Quack {
def quack() : Unit = println("quack")
}
trait Swim {
self : Animal with Quack =>
def swim() : Unit = println("swim")
}
val swimmingDuck = new Animal() with Quack with Swim
swimmingDuck.swim()
// 编译不通过
val animal = new Animal() with Swim