六、Scala特质
在上一章中,我们学习了 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 Singerperson2.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 的构造机制
当一个类继承了父类并混入了多个特质时,它们的构造器会按照一个明确的线性化 顺序依次执行。
构造顺序规则:
- 首先执行父类的构造器。
- 然后,从左到右依次执行每个 Trait 的构造代码。
- 最后执行子类的构造器。
代码案例:
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(): Unitprotected def processData(): Unitprotected 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
类,同时混入这两个特质,并创建实例分别调用 float
和 fly
方法。
题目四:对象混入 Trait
创建一个 Robot
类。然后,创建一个 Robot
的实例 cookingRobot
,并在创建时动态地为其混入一个 Cook
特质 (该特质有一个 cookDinner()
方法,打印 “Cooking dinner.”)。最后调用 cookingRobot
的 cookDinner
方法。
题目五: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
类来实现这三个步骤,每个步骤打印相应的信息。
题目十:职责链模式
使用 super
和 abstract 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
的特质 CanCode
和 CanManage
,它们都重写了 work()
方法。创建一个 TeamLead
类,同时混入 CanCode
和 CanManage
,并观察 super.work()
调用的是哪个实现 (根据线性化规则)。
题目十四:abstract override
的应用
创建一个 Logger
特质,有一个 log(msg: String)
方法。再创建一个 TimestampLogger
特质,使用 abstract override
来为 log
方法添加时间戳前缀。
题目十五:综合案例
定义一个 Character
基类。定义 Fighter
和 Mage
两个 Trait,分别有 fight()
和 castSpell()
方法。创建一个 Spellsword
类,它继承自 Character
并同时混入 Fighter
和 Mage
。
答案与解析
答案一:
trait HasEngine {def startEngine(): Unit = println("Engine started.")
}
class Car extends HasEngineval 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 Fliesval 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(): Unitprotected def buildWalls(): Unitprotected 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 HasIDval 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 Loggerdef 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 的 workprintln("Coding...")}
}
trait CanManage extends Worker {override def work(): Unit = {super.work() // 调用 Worker 的 workprintln("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()