第7章——特质

Java 只允许单继承,这会强制建立一种线性的层次结构模型。但现实世界中充满了横切 关注点(crosscutting concerns)—一种横切且影响多个抽象的概念,这些抽象并不同属于 某个单一的类层次结构 ① 。在典型的企业级应用程序中,安全、日志记录、验证、事务以及 资源管理都是这些横切关注点的应用场景。但是,因为我们受限于单一的类层次结构,所以 实现这些横切关注点变得相当困难,往往需要代码上的重复或者引入重量级工具 ② 。Scala 使 用特质(trait)解决了这个问题。

  1. 即横跨应用程序中多个模块的特性。——译者注

7.1 理解特质

UsingTraits/HumanWithListen.scala

class Human(val name: String) {
  def listen(): Unit = println(s"Your friend $name is listening")
}

class Man(override val name: String) extends Human(name)
class Woman(override val name: String) extends Human(name)
Full source at GitHub

UsingTraits/Friend.scala

trait Friend {
  val name: String
  def listen(): Unit = println(s"Your friend $name is listening")
}
Full source at GitHub

UsingTraits/Human.scala

class Human(val name: String) extends Friend

class Woman(override val name: String) extends Human(name)
class Man(override val name: String) extends Human(name)
Full source at GitHub

UsingTraits/Dog.scala

class Dog(val name: String) extends Animal with Friend {
  //optionally override method here.
  override def listen(): Unit = println(s"$name's listening quietly")
}
Full source at GitHub

UsingTraits/Animal.scala

class Animal
Full source at GitHub

UsingTraits/UseFriend.scala

object UseFriend extends App {
  val john = new Man("John")
  val sara = new Woman("Sara")
  val comet = new Dog("Comet")

  john.listen()
  sara.listen()
  comet.listen()

  val mansBestFriend: Friend = comet
  mansBestFriend.listen()

  def helpAsFriend(friend: Friend): Unit = friend.listen()

  helpAsFriend(sara)
  helpAsFriend(comet)
}
Full source at GitHub

运行结果

Your friend John is listening
Your friend Sara is listening
Comet's listening quietly
Comet's listening quietly
Your friend Sara is listening
Comet's listening quietly
Full source at GitHub

7.2 选择性混入

UsingTraits/Cat.scala

class Cat(val name: String) extends Animal
Full source at GitHub

UsingTraits/UseCat.scala

object UseCat extends App {
  def useFriend(friend: Friend): Unit = friend.listen()

  val alf = new Cat("Alf")
  val friend: Friend = alf // ERROR

  useFriend(alf) // ERROR
}
Full source at GitHub

编译结果

UseCat.scala:5: error: type mismatch;
 found   : Cat
 required: Friend
  val friend : Friend = alf // ERROR
                        ^
UseCat.scala:7: error: type mismatch;
 found   : Cat
 required: Friend
  useFriend(alf) // ERROR
            ^
two errors found
Full source at GitHub

UsingTraits/TreatCatAsFriend.scala

def useFriend(friend: Friend): Unit = friend.listen()

val angel = new Cat("Angel") with Friend
val friend: Friend = angel
angel.listen()

useFriend(angel)
Full source at GitHub

运行结果

Your friend Angel is listening
Your friend Angel is listening
Full source at GitHub

7.3 使用特质实现装饰器模式

UsingTraits/Decorator.scala

abstract class Check {
  def check: String = "Checked Application Details..."
}
Full source at GitHub

UsingTraits/Decorator.scala

trait CreditCheck extends Check {
  override def check: String = s"Checked Credit... ${super.check}"
}

trait EmploymentCheck extends Check {
  override def check: String = s"Checked Employment...${super.check}"
}

trait CriminalRecordCheck extends Check {
  override def check: String = s"Check Criminal Records...${super.check}"
}
Full source at GitHub

UsingTraits/Decorator.scala

val apartmentApplication =
  new Check with CreditCheck with CriminalRecordCheck

println(apartmentApplication.check)
Full source at GitHub

UsingTraits/Decorator.scala

val employmentApplication =
  new Check with CriminalRecordCheck with EmploymentCheck

println(employmentApplication.check)
Full source at GitHub

运行结果

Check Criminal Records...Checked Credit... Checked Application Details...
Checked Employment...Check Criminal Records...Checked Application 
Details...
Full source at GitHub

7.4 特质中的方法延迟绑定

UsingTraits/MethodBinding.scala

abstract class Writer {
  def writeMessage(message: String): Unit
}
Full source at GitHub

UsingTraits/MethodBinding.scala

trait UpperCaseWriter extends Writer {
  abstract override def writeMessage(message: String): Unit =
    super.writeMessage(message.toUpperCase)
}

trait ProfanityFilteredWriter extends Writer {
  abstract override def writeMessage(message: String): Unit =
    super.writeMessage(message.replace("stupid", "s-----"))
}
Full source at GitHub

UsingTraits/MethodBinding.scala

class StringWriterDelegate extends Writer {
  val writer = new java.io.StringWriter

  def writeMessage(message: String): Unit = writer.write(message)
  override def toString: String = writer.toString
}
Full source at GitHub

UsingTraits/MethodBinding.scala

val myWriterProfanityFirst =
  new StringWriterDelegate with UpperCaseWriter with ProfanityFilteredWriter

val myWriterProfanityLast =
  new StringWriterDelegate with ProfanityFilteredWriter with UpperCaseWriter

myWriterProfanityFirst writeMessage "There is no sin except stupidity"
myWriterProfanityLast writeMessage "There is no sin except stupidity"

println(myWriterProfanityFirst)
println(myWriterProfanityLast)
Full source at GitHub

运行结果

THERE IS NO SIN EXCEPT S-----ITY
THERE IS NO SIN EXCEPT STUPIDITY
Full source at GitHub