Scala 可动态混入的特质 Trait

1,371 阅读3分钟

重编辑于: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 Animaltype Duck = Animal with Quack
val duck : Duck = new Animal() with Quack
​
duck.quack()

动态混入特质不会影响到 Animal 自身的定义,我们可以利用这一点类 ( 不能是 final 的 ) 实现 OCP 拓展。在上文的例子中,duckAnimal 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 Btrait Mod1
trait Mod2
trait BExtension extends B with Mod1 with Mod2{
  def f() : Unit = println("f")
}
​
// 对类型 A 而言,它相当于继承了被拓展后的类型 B。
class A extends BExtensionnew 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