特质 - Trait

Scala做了一些面向对象的创新,其中之一就是特质(Trait)。trait类似于带有部分实现的抽象类。

一个特质(trait)代表一个接口,由相关类的层级所支持。它是一个抽象机制,帮助开发模块化、可重用和可扩展的代码。

从概念上来说,接口是通过一系列方法所定义的。Scala trait 与Java 的接口类似。不过Java 中的接口只包括方法签名,而一个Scala 的trait 可以包含方法的实现。此外,一个Scala trait 也可以包含字段。一个类可以重用在trait 中实现的这些字段和方法。

一个trait 看上去与一个抽象类相似。都包含字段和方法。关键的区别在于一个类只能从一个类继承,但它可以从任意数量的trait 继承。

下面是一个trait 的例子:

trait Shape{
    def area(): Int
}

class Square(length:Int) extends Shape{
    def area = length * length;
}

class Rectangle(length:Int, width:Int) extends Shape{
    def area = length * width;
}

val square = new Square(10);
val area = square.area;

在下面的代码中,定义了两个trait,并定义了实现这两个trait的类Batmobile:

// Trait(特质,类似于Java的接口)
trait flying{
    def fly() = println("flying")
}

trait gliding{
    def float() = println("gliding")
}

// 实现了Trait接口的类
class Batmobile(speed:Int) extends Vehicle(speed) with flying with gliding{
    override val mph:Int = speed
    override def race() = println("Racing Batmobile")
    override def fly() = println("Flying Batmobile")
    override def float() = println("Gliding Batmobile")
}

// 对象
val vehicle3 = new Batmobile(300)
vehicle3.fly
vehicle3.float

val vehicleList = List(vehicle1, vehicle2, vehicle3)
val fastestVehicle = vehicleList.maxBy(_.mph)

Scala特质说明:

  • Scala中同样像Java一样不能实现多继承,只能通过多实现来弥补,同时多实现,要比多继承灵活的多。
  • 在Java中的实现,我们称之为接口interface,使用关键字implement;在Scala中,我们称之为特质trait,使用关键字extends。
  • 如果在Java中实现的是多个接口,接口之间使用","隔开;如果在Scala中继承的是多个特质,特质之间使用with隔开
  • trait是一个比较特殊的结构,既可以有抽象方法,也可以有非抽象方法。如果trait中的方法都是抽象的,我们就可以将其看作Java的接口。
  • 当我们扩展/继承的多个特质,拥有相同的方法的时候,默认只会调用最右面一个特质的方法。

下面的代码中,定义了一个特质log,它有一个抽象的方法log:

trait Log {
  def log(msg:String): Unit = {
  }
}

class ConsoleLog extends Log {
  override def log(msg: String): Unit = {
    println(s"将 ${msg} 输出到Console")
  }
}

trait FileLog extends Log {
  override def log(msg: String): Unit = {
    println(s"将 ${msg} 输出到File")
    super.log(msg)
  }
}

trait FlumeLog extends Log {
  override def log(msg: String): Unit = {
    println(s"将 ${msg} 采集到Flume")
    super.log(msg)
  }
}

class MainLog extends FileLog with FlumeLog {
  override def log(msg: String): Unit = {
    super.log(msg)
  }
}

// ======================================================================
val cLog:Log = new ConsoleLog
cLog.log("日志信息")
println("==================")

val mLog:MainLog = new MainLog
mLog.log("info")

下面是另一个简单的例子:

trait Animal
trait FurryAnimal extends Animal
case class Dog(name: String) extends Animal
case class Cat(name: String) extends Animal

val x = Array(Dog("Fido"), Cat("Felix"))
val x = Array[Animal](Dog("Fido"), Cat("Felix"))

【示例】简单trait示例。

// 定义一个trait,提供DAO层的方法签名
trait DonutShoppingCartDao{
  def add(donutName: String): Long
  def update(donutName: String): Boolean
  def search(donutName: String): String
  def delete(donutName: String): Boolean
}

// 创建一个DonutShoppingCart类,它扩展上面的trait并实现其方法
class DonutShoppingCart extends DonutShoppingCartDao {

  override def add(donutName: String): Long = {
    println(s"DonutShoppingCart -> add method -> donutName: $donutName")
    1
  }

  override def update(donutName: String): Boolean = {
    println(s"DonutShoppingCart -> update method -> donutName: $donutName")
    true
  }

  override def search(donutName: String): String = {
    println(s"DonutShoppingCart -> search method -> donutName: $donutName")
    donutName
  }

  override def delete(donutName: String): Boolean = {
    println(s"DonutShoppingCart -> delete method -> donutName: $donutName")
    true
  }
}

// 主类
object TraitDemo2 {
  def main(args: Array[String]): Unit = {
    // 现在我们可以创建DonutShoppingCart的实例
    val donutShoppingCart1: DonutShoppingCart = new DonutShoppingCart()

    // 并调用相应的add、update、search和delete方法
    donutShoppingCart1.add("Vanilla Donut")
    donutShoppingCart1.update("Vanilla Donut")
    donutShoppingCart1.search("Vanilla Donut")
    donutShoppingCart1.delete("Vanilla Donut")

    println("----------------------------------------------")
    // 因为我们的DonutShoppingCart类扩展了特性DonutShoppingCartDao,
    // 也可以将DonutShoppingCart对象的类型分配给trait DonutShoppingCartDao
    val donutShoppingCart2: DonutShoppingCartDao = new DonutShoppingCart()
    donutShoppingCart2.add("Vanilla Donut")
    donutShoppingCart2.update("Vanilla Donut")
    donutShoppingCart2.search("Vanilla Donut")
    donutShoppingCart2.delete("Vanilla Donut")
  }
}

【示例】带类型参数的trait示例。

// 定义一个trait,提供DAO层的方法签名
trait DonutShoppingCartDao[A]{
  def add(donut: A): Long
  def update(donut: A): Boolean
  def search(donut: A): A
  def delete(donut: A): Boolean
}

// 创建一个DonutShoppingCart类,它扩展上面的trait并实现其方法
class DonutShoppingCart[A] extends DonutShoppingCartDao[A] {

  override def add(donut: A): Long = {
    println(s"DonutShoppingCart -> add method -> donut: $donut")
    1
  }

  override def update(donut: A): Boolean = {
    println(s"DonutShoppingCart -> update method -> donut: $donut")
    true
  }

  override def search(donut: A): A = {
    println(s"DonutShoppingCart -> search method -> donut: $donut")
    donut
  }

  override def delete(donut: A): Boolean = {
    println(s"DonutShoppingCart -> delete method -> donut: $donut")
    true
  }
}

// 主类
object TraitDemo3 {
  def main(args: Array[String]): Unit = {
    // 现在我们可以创建DonutShoppingCart的实例
    val donutShoppingCart1: DonutShoppingCart[String] = new DonutShoppingCart[String]()

    // 并调用相应的add、update、search和delete方法
    donutShoppingCart1.add("Vanilla Donut")
    donutShoppingCart1.update("Vanilla Donut")
    donutShoppingCart1.search("Vanilla Donut")
    donutShoppingCart1.delete("Vanilla Donut")

    println("----------------------------------------------")
    // 因为我们的DonutShoppingCart类扩展了特性DonutShoppingCartDao,
    // 也可以将DonutShoppingCart对象的类型分配给trait DonutShoppingCartDao
    val donutShoppingCart2: DonutShoppingCartDao[String] = new DonutShoppingCart[String]()
    donutShoppingCart2.add("Vanilla Donut")
    donutShoppingCart2.update("Vanilla Donut")
    donutShoppingCart2.search("Vanilla Donut")
    donutShoppingCart2.delete("Vanilla Donut")
  }
}

【示例】扩展自多个trait的示例。

// 定义一个trait,提供DAO层的方法签名
trait DonutShoppingCartDao[A]{
  def add(donut: A): Long
  def update(donut: A): Boolean
  def search(donut: A): A
  def delete(donut: A): Boolean
}

// 创建第二个trait,它将定义检查商品库存的方法
trait DonutInventoryService[A] {
  def checkStockQuantity(donut: A): Int
}

// 创建一个DonutShoppingCart类,它扩展上面的多个trait并实现其方法
class DonutShoppingCart[A] extends DonutShoppingCartDao[A] with DonutInventoryService[A] {

  override def add(donut: A): Long = {
    println(s"DonutShoppingCart -> add method -> donut: $donut")
    1
  }

  override def update(donut: A): Boolean = {
    println(s"DonutShoppingCart -> update method -> donut: $donut")
    true
  }

  override def search(donut: A): A = {
    println(s"DonutShoppingCart -> search method -> donut: $donut")
    donut
  }

  override def delete(donut: A): Boolean = {
    println(s"DonutShoppingCart -> delete method -> donut: $donut")
    true
  }

  override def checkStockQuantity(donut: A): Int = {
    println(s"DonutShoppingCart -> checkStockQuantity method -> donut: $donut")
    10
  }
}

// 主类
object TraitDemo3 {
  def main(args: Array[String]): Unit = {
    // 现在我们可以创建DonutShoppingCart的实例
    val donutShoppingCart: DonutShoppingCart[String] = new DonutShoppingCart[String]()

    // 并调用相应的add、update、search和delete方法
    donutShoppingCart.add("Vanilla Donut")
    donutShoppingCart.update("Vanilla Donut")
    donutShoppingCart.search("Vanilla Donut")
    donutShoppingCart.delete("Vanilla Donut")
    donutShoppingCart.checkStockQuantity("Vanilla Donut")
  }
}

【示例】使用标准Scala库来使用trait进行依赖注入,而无需导入任何第三方工具和库。

// 定义一个trait
trait DonutDatabase[A] {
  def addOrUpdate(donut: A): Long
  def query(donut: A): A
  def delete(donut: A): Boolean
}

// 假设我们的存储层是Apache Cassandra。我们创建了一个名为CassandraDonutStore[A]的类,
// 它将通过扩展trait DonutDatabase[A]来促进所有的CRUD操作。
class CassandraDonutStore[A] extends DonutDatabase[A] {

  override def addOrUpdate(donut: A): Long = {
    println(s"CassandraDonutDatabase -> addOrUpdate method -> donut: $donut")
    1
  }

  override def query(donut: A): A = {
    println(s"CassandraDonutDatabase -> query method -> donut: $donut")
    donut
  }

  override def delete(donut: A): Boolean = {
    println(s"CassandraDonutDatabase -> delete method -> donut: $donut")
    true
  }
}

// 创建一个trait,它将定义数据访问层的方法,并将要求DonutDatabase的依赖注入
// 接下来,我们创建一个名为DonutShoppingCartDao的trait,它将是执行CRUD操作的主要API。
// 为此,我们定义了一个类型DonutDatabase[A]的val,它将被依赖注入。
trait DonutShoppingCartDao[A] {

  val donutDatabase: DonutDatabase[A]     // 依赖注入

  def add(donut: A): Long = {
    println(s"DonutShoppingCartDao -> add method -> donut: $donut")
    donutDatabase.addOrUpdate(donut)
  }

  def update(donut: A): Boolean = {
    println(s"DonutShoppingCartDao -> update method -> donut: $donut")
    donutDatabase.addOrUpdate(donut)
    true
  }

  def search(donut: A): A = {
    println(s"DonutShoppingCartDao -> search method -> donut: $donut")
    donutDatabase.query(donut)
  }

  def delete(donut: A): Boolean = {
    println(s"DonutShoppingCartDao -> delete method -> donut: $donut")
    donutDatabase.delete(donut)
  }

}

// 创建一个trait,它将定义检查商品库存的方法,并且需要注入DonutDatabase依赖项
// 类似于前面的例子,我们创建了另一个trait,它将负责检查商品库存。
trait DonutInventoryService[A] {

  val donutDatabase: DonutDatabase[A]   // 依赖注入

  def checkStockQuantity(donut: A): Int = {
    println(s"DonutInventoryService-> checkStockQuantity method -> donut: $donut")
    donutDatabase.query(donut)
    1
  }

}

// 创建一个trait,它将作为一个facade,并扩展多个trait,即DonutShoppingCartDao和DonutInventoryService。
// 我们还注入了存储层的一个实现,在本例中是CassandraDonutStore的一个实例
trait DonutShoppingCartServices[A] extends DonutShoppingCartDao[A] with DonutInventoryService[A] {
  override val donutDatabase: DonutDatabase[A] = new CassandraDonutStore[A]()
}

// 创建一个DonutShoppingCart类,它扩展了一个名为DonutShoppingCartServices的外观,
// 以公开DonutShoppingCart所需的所有底层trait
class DonutShoppingCart[A] extends DonutShoppingCartServices[A] {
}

// 主类
object TraitDemo5 {
  def main(args: Array[String]): Unit = {
    // 创建DonutShoppingCart实例
    val donutShoppingCart: DonutShoppingCart[String] = new DonutShoppingCart[String]()

    // 并调用add、update、search和delete方法
    donutShoppingCart.add("Vanilla Donut")
    donutShoppingCart.update("Vanilla Donut")
    donutShoppingCart.search("Vanilla Donut")
    donutShoppingCart.delete("Vanilla Donut")
  }
}

《Flink原理深入与编程实战》