在上一章中,我们学习了 Scala 的继承体系,但类只能直接继承自一个父类,这限制了代码的复用能力。为了解决这个问题,Scala 提供了一个非常强大的特性——特质 (Trait)。Trait类似于 Java 8+ 中的接口,但功能更加强大,它可以包含具体实现的方法和字段,是 Scala 中实现代码组合和行为注入的核心机制。

思维导图

在这里插入图片描述
在这里插入图片描述

一、特质入门

特质可复用代码片段,用于封装一组方法和字段。一个类可以混入 多个特质,从而获得这些特质定义的行为

基本语法:

定义特质: trait TraitName { ... }
类混入特质:
如果类没有父类,使用 extends 关键字混入第一个特质。
如果类已有父类,或需要混入多个特质,使用 with 关键字。

代码案例:

// 定义一个记录日志的特质
trait Logger {
  // 抽象方法,混入的类需要实现
  def log(message: String): Unit

  // 具体方法
  def info(message: String): Unit = log(s"INFO: $message")
  def warn(message: String): Unit = log(s"WARN: $message")
}

// ConsoleLogger 类实现了 Logger 特质
class ConsoleLogger extends Logger {
  // 实现抽象方法
  override def log(message: String): Unit = println(message)
}

// 一个类可以混入多个特质
trait TimestampLogger extends Logger {
  // 重写具体方法
  override def log(message: String): Unit = {
    println(s"${java.time.Instant.now()} $message")
  }
}

// MyService 类继承自 Object (默认),并混入了 TimestampLogger
class MyService extends TimestampLogger {
  def performAction(): Unit = {
    info("Action started.")
    warn("Something might be wrong.")
  }
}

val service = new MyService()
service.performAction()

二、Trait 的高级特性

1. 对象混入 Trait

Scala 允许创建对象实例时,动态地为其混入特质。这使得只有这个特定的实例拥有该特质的行为

代码案例:

class Person(val name: String)

trait Singer {
  def sing(): Unit = println("La la la...")
}

val person1 = new Person("Alice")
// person1.sing() // 编译错误,Person 类没有 sing 方法

// 在创建 person2 实例时,动态混入 Singer 特质
val person2 = new Person("Bob") with Singer

person2.sing() // 正确,person2 实例拥有了 sing 方法

2. Trait 继承 Class

Trait 也可以继承自一个。这样做会施加一个约束:任何混入该 Trait 的类,必须是该 Trait 所继承的那个类子类

代码案例:

class Service {
  def serviceName: String = "Base Service"
}

// MyServiceTrait 继承自 Service 类
trait MyServiceTrait extends Service {
  def extendedFeature(): Unit = println(s"${serviceName} has an extended feature.")
}

// ValidService 继承自 Service, 所以可以混入 MyServiceTrait
class ValidService extends Service with MyServiceTrait

// class InvalidService { ... }
// val invalid = new InvalidService with MyServiceTrait // 编译错误!
// 因为 InvalidService 不是 Service 的子类

三、Trait 的构造机制

当一个类继承了父类并混入了多个特质时,它们的构造器按照一个明确的线性化 顺序依次执行

构造顺序规则:

  1. 首先执行父类的构造器。
  2. 然后,从左到右依次执行每个 Trait 的构造代码。
  3. 最后执行子类的构造器。

代码案例:

class Base { println("Constructing Base") }
trait T1 extends Base { println("Constructing T1") }
trait T2 extends Base { println("Constructing T2") }
trait T3 extends Base { println("Constructing T3") }

// Child 继承 T1 并混入 T2 和 T3
class Child extends T1 with T2 with T3 {
  println("Constructing Child")
}

println("Creating a new Child instance:")
val c = new Child

输出结果:

Creating a new Child instance:
Constructing Base
Constructing T1
Constructing T2
Constructing T3
Constructing Child

解析:尽管所有特质都继承自 Base,但 Base 的构造器只会被执行一次。然后按照 with 关键字从左到右的顺序,依次执行 T1, T2, T3 的构造代码,最后才是 Child 自己的构造代码。

四、使用 Trait 实现设计模式

Trait 的灵活性使其成为实现多种设计模式的理想工具

1. 适配器模式

使用 Trait 可以优雅地将一个已存在的类接口转换成客户端期望的另一个接口。

代码案例:

// 目标接口
trait Target {
  def request(): Unit
}

// 已存在的类 (被适配者)
class Adaptee {
  def specificRequest(): Unit = println("Adaptee's specific request.")
}

// 适配器:继承 Adaptee,混入 Target Trait
class Adapter extends Adaptee with Target {
  override def request(): Unit = {
    // 将对 request() 的调用适配到对 specificRequest() 的调用
    this.specificRequest()
  }
}

val adapter: Target = new Adapter()
adapter.request()

2. 模板方法模式

Trait 非常适合定义一个算法的骨架,而将一些步骤延迟到混入该 Trait 的子类中去实现。

代码案例:

trait DataProcessor {
  // 模板方法,定义了算法骨架,声明为 final 防止被重写
  final def process(): Unit = {
    readData()
    processData()
    writeData()
  }
  
  // 抽象方法,由子类实现
  protected def readData(): Unit
  protected def processData(): Unit
  protected def writeData(): Unit
}

class FileDataProcessor extends DataProcessor {
  override protected def readData(): Unit = println("Reading data from a file.")
  override protected def processData(): Unit = println("Processing file data.")
  override protected def writeData(): Unit = println("Writing processed data to a file.")
}

val fileProcessor = new FileDataProcessor()
fileProcessor.process()

3. 职责链模式

Trait 的线性化super 调用机制可以巧妙地实现职责链模式。

代码案例:

// 处理器基类
abstract class Handler {
  def handle(request: String): String
}

// 具体的处理器,通过 Trait 实现
trait UppercaseHandler extends Handler {
  abstract override def handle(request: String): String = {
    super.handle(request.toUpperCase)
  }
}
trait ReverseHandler extends Handler {
  abstract override def handle(request: String): String = {
    super.handle(request.reverse)
  }
}

// 链的终点
class FinalHandler extends Handler {
  override def handle(request: String): String = {
    s"Final result: $request"
  }
}

// 通过混入 Trait 来构建职责链
val chain = new FinalHandler with UppercaseHandler with ReverseHandler
println(chain.handle("hello"))
// 输出: Final result: OLLEH

解析abstract override 是关键。当 chain.handle 被调用时,调用链ReverseHandler -> UppercaseHandler -> FinalHandler (从右到左)。super.handle 会将请求传递给线性化顺序中的前一个处理器。

五、综合案例

这个案例将展示如何使用 Trait 来为一个基类灵活地组合不同的功能模块,例如数据源格式化分发渠道。

代码实现:

// 基类,定义报告生成的骨架
class Report(val title: String) {
  def generate(): Unit = {
    println(s"--- Generating Report: $title ---")
    // 基础生成逻辑
    println("Core report generation logic finished.")
  }
}

// 定义不同功能的 Trait
trait FromDatabase {
  def loadData(): Unit = println("Loading data from database...")
}
trait AsPDF {
  def format(): Unit = println("Formatting report as PDF...")
}
trait AsCSV {
  def format(): Unit = println("Formatting report as CSV...")
}
trait SendByEmail {
  def send(recipient: String): Unit = println(s"Sending report to $recipient via Email...")
}
trait UploadToFTP {
  def send(location: String): Unit = println(s"Uploading report to FTP server at $location...")
}


// 通过混入 Trait 定义不同类型的报告生成器
// 案例1:一个从数据库获取数据,生成PDF,并通过邮件发送的报告
class MonthlySalesReport(title: String) extends Report(title) with FromDatabase with AsPDF with SendByEmail {
  // 可以重写或扩展方法
  override def generate(): Unit = {
    loadData()
    format()
    super.generate() // 调用基类的核心逻辑
    send("management@example.com")
    println("--- Report generation complete ---")
  }
}

// 案例2:一个生成CSV并通过FTP上传的报告
class DailyInventoryReport(title: String) extends Report(title) with AsCSV with UploadToFTP {
  override def generate(): Unit = {
    format()
    super.generate()
    send("ftp://inventory.server/daily/")
    println("--- Report generation complete ---")
  }
}

// 动态为对象添加功能
val adHocReport = new Report("Ad-hoc Analysis")
// 假设这个临时报告需要PDF格式
val pdfAdHocReport = adHocReport with AsPDF

// 使用
println("--- Running Monthly Sales Report ---")
val salesReport = new MonthlySalesReport("October Sales")
salesReport.generate()

println("\n--- Running Daily Inventory Report ---")
val inventoryReport = new DailyInventoryReport("Inventory Status")
inventoryReport.generate()

println("\n--- Running Ad-hoc Report ---")
pdfAdHocReport.format()
pdfAdHocReport.generate()

练习题

题目一:基本 Trait 混入
定义一个 HasEngine 特质,包含一个具体方法 startEngine(),打印 “Engine started.”。然后定义一个 Car 类,混入这个特质,并创建一个实例调用 startEngine 方法。

题目二:Trait 与抽象成员
定义一个 CanFly 特质,包含一个抽象字段 maxAltitude: Int。然后创建一个 Drone 类,混入该特质,并将 maxAltitude 实现为 500。创建 Drone 实例并打印其 maxAltitude

题目三:多个 Trait 混入
定义两个特质:Floats (有 float() 方法,打印 “Floating on water.”) 和 Flies (有 fly() 方法,打印 “Flying in the air.”)。创建一个 Seaplane 类,同时混入这两个特质,并创建实例分别调用 floatfly 方法。

题目四:对象混入 Trait
创建一个 Robot 类。然后,创建一个 Robot 的实例 cookingRobot,并在创建时动态地为其混入一个 Cook 特质 (该特质有一个 cookDinner() 方法,打印 “Cooking dinner.”)。最后调用 cookingRobotcookDinner 方法。

题目五:Trait 继承 Class
创建一个 Appliance 类。创建一个 CanConnectToWifi 特质,继承自 Appliance。然后创建一个 SmartFridge 类,继承自 Appliance 并混入 CanConnectToWifi 特质。

题目六:Trait 构造顺序
预测以下代码的输出结果,并写出最终的输出。

trait T1 { println("Init T1") }
class C1 { println("Init C1") }
class C2 extends C1 with T1 { println("Init C2") }
val instance = new C2()

题目七:Trait 构造顺序 (多特质)
预测以下代码的输出结果,并写出最终的输出。

trait T_A { println("A") }
trait T_B { println("B") }
class C_Base { println("Base") }
class C_Child extends C_Base with T_A with T_B { println("Child") }
val child_instance = new C_Child()

题目八:适配器模式
有一个 LegacyPrinter 类,它有一个 printDocument(content: String, copies: Int) 方法。你需要一个符合 SimplePrinter 特质 (有 print(content: String) 方法) 的对象。请使用适配器模式创建一个 PrinterAdapter 类,使其调用 print 方法时,默认打印1份。

题目-九:模板方法模式
创建一个 Builder 特质,它有一个 final 的模板方法 build(),该方法依次调用三个抽象方法:layFoundation(), buildWalls(), addRoof()。然后创建一个 HouseBuilder 类来实现这三个步骤,每个步骤打印相应的信息。

题目十:职责链模式
使用 superabstract override,创建两个处理器 Trait:HeaderHandler (在字符串前加 "[HEADER] “) 和 FooterHandler (在字符串后加 " [FOOTER]”)。将它们混入一个 ContentHandler (它只返回原始内容),并构造一个调用链处理字符串 “My Data”。

题目十一:Trait 中的字段初始化
定义一个 HasID 特质,它有一个具体字段 val id: String = java.util.UUID.randomUUID().toString。创建一个 User 类混入此特质,并创建两个 User 实例,打印它们的 id,观察ID是否相同。

题目十二:Trait 与 self-type
创建一个 Notifier 特质,它需要日志功能,但本身不实现。使用 self-type 注解,强制要求任何混入 Notifier 特质的类,必须也混入一个 Logger 特质 (该特质有 log(msg: String) 方法)。然后创建一个合法的 EmailNotifier 类。

题目十三:Trait 解决菱形继承问题
定义一个基特质 Worker,以及两个继承自 Worker 的特质 CanCodeCanManage,它们都重写了 work() 方法。创建一个 TeamLead 类,同时混入 CanCodeCanManage,并观察 super.work() 调用的是哪个实现 (根据线性化规则)。

题目十四:abstract override 的应用
创建一个 Logger 特质,有一个 log(msg: String) 方法。再创建一个 TimestampLogger 特质,使用 abstract override 来为 log 方法添加时间戳前缀。

题目十五:综合案例
定义一个 Character 基类。定义 FighterMage 两个 Trait,分别有 fight()castSpell() 方法。创建一个 Spellsword 类,它继承自 Character 并同时混入 FighterMage

答案与解析

答案一:

trait HasEngine {
  def startEngine(): Unit = println("Engine started.")
}
class Car extends HasEngine

val myCar = new Car()
myCar.startEngine()

答案二:

trait CanFly {
  val maxAltitude: Int
}
class Drone extends CanFly {
  override val maxAltitude: Int = 500
}
val myDrone = new Drone()
println(s"Drone max altitude: ${myDrone.maxAltitude}")

答案三:

trait Floats { def float(): Unit = println("Floating on water.") }
trait Flies { def fly(): Unit = println("Flying in the air.") }
class Seaplane extends Floats with Flies

val seaplane = new Seaplane()
seaplane.float()
seaplane.fly()

答案四:

class Robot
trait Cook {
  def cookDinner(): Unit = println("Cooking dinner.")
}

val cookingRobot = new Robot with Cook
cookingRobot.cookDinner()

解析: with 关键字可以在创建实例时动态地为对象添加新的特质和行为。

答案五:

class Appliance
trait CanConnectToWifi extends Appliance
class SmartFridge extends Appliance with CanConnectToWifi
// 如果 SmartFridge 不继承 Appliance,则混入 CanConnectToWifi 会编译失败

解析: 当一个 Trait 继承自某个类时,它就为所有混入它的类设定了一个“必须是该父类的子类”的约束。

答案六:
输出:

Init C1
Init T1
Init C2

答案七:
输出:

Base
A
B
Child

答案八:

class LegacyPrinter {
  def printDocument(content: String, copies: Int): Unit = {
    for (i <- 1 to copies) println(content)
  }
}
trait SimplePrinter {
  def print(content: String): Unit
}
class PrinterAdapter extends LegacyPrinter with SimplePrinter {
  override def print(content: String): Unit = {
    this.printDocument(content, 1)
  }
}

答案九:

trait Builder {
  final def build(): Unit = {
    layFoundation()
    buildWalls()
    addRoof()
  }
  protected def layFoundation(): Unit
  protected def buildWalls(): Unit
  protected def addRoof(): Unit
}
class HouseBuilder extends Builder {
  override protected def layFoundation(): Unit = println("Laying the house foundation.")
  override protected def buildWalls(): Unit = println("Building the house walls.")
  override protected def addRoof(): Unit = println("Adding the house roof.")
}
val hb = new HouseBuilder()
hb.build()

答案十:

abstract class BaseHandler { def handle(request: String): String }
trait HeaderHandler extends BaseHandler {
  abstract override def handle(request: String): String = super.handle(s"[HEADER] $request")
}
trait FooterHandler extends BaseHandler {
  abstract override def handle(request: String): String = super.handle(s"$request [FOOTER]")
}
class ContentHandler extends BaseHandler {
  override def handle(request: String): String = request
}

val chain = new ContentHandler with HeaderHandler with FooterHandler
println(chain.handle("My Data")) // 输出: [HEADER] My Data [FOOT-ER]

答案十一:

trait HasID {
  val id: String = java.util.UUID.randomUUID().toString
}
class User extends HasID

val user1 = new User()
val user2 = new User()
println(user1.id)
println(user2.id)
// 两个 id 将会不同

答案十二:

trait Logger { def log(msg: String): Unit }
trait Notifier {
  this: Logger => // Self-type: I must also be a Logger
  def notify(message: String): Unit = {
    log(s"NOTIFICATION: $message")
  }
}
class EmailNotifier extends Logger with Notifier {
  override def log(msg: String): Unit = println(s"LOG: $msg")
}

答案十三:

trait Worker { def work(): Unit = println("Working...") }
trait CanCode extends Worker {
  override def work(): Unit = {
    super.work() // 调用 Worker 的 work
    println("Coding...")
  }
}
trait CanManage extends Worker {
  override def work(): Unit = {
    super.work() // 调用 Worker 的 work
    println("Managing...")
  }
}
// 混入顺序决定 super 调用链, 从右到左: CanManage -> CanCode -> Worker
class TeamLead extends Worker with CanCode with CanManage
val lead = new TeamLead()
lead.work()
// 输出:
// Working...
// Coding...
// Managing...

答案十四:

trait Logger {
  def log(msg: String): Unit = println(msg)
}
trait TimestampLogger extends Logger {
  abstract override def log(msg: String): Unit = {
    super.log(s"${java.time.Instant.now()}: $msg")
  }
}
class MyLogger extends Logger with TimestampLogger
val logger = new MyLogger()
logger.log("System startup.")

答案十五:

class Character
trait Fighter {
  def fight(): Unit = println("Swinging a sword!")
}
trait Mage {
  def castSpell(): Unit = println("Casting a fireball!")
}
class Spellsword extends Character with Fighter with Mage
val spellsword = new Spellsword()
spellsword.fight()
spellsword.castSpell()

在这里插入图片描述

日期:2025年9月16日
专栏:Scala教程

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐