装饰器模式
本课属于 Coddy Swift 学习旅程的“面向对象编程”单元。第 47 课,共 57 课。
装饰器模式通过将对象包装在遵循相同协议的另一个对象中,为对象添加行为。包装器会将调用转发给被包装的对象,并在调用前后添加自己的部分:
protocol Coffee {
func cost() -> Int
func label() -> String
}
struct Espresso: Coffee {
func cost() -> Int { 200 }
func label() -> String { "espresso" }
}
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
let drink = WithMilk(base: Espresso())
print(drink.label())
print(drink.cost())输出:
espresso + milk
250由于装饰器本身也是一个 Coffee,装饰器可以按任意组合和顺序互相包装,而不必为每种组合都创建一个子类:
protocol Coffee { func cost() -> Int; func label() -> String }
struct Espresso: Coffee { func cost() -> Int { 200 }; func label() -> String { "espresso" } }
struct WithMilk: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 50 }
func label() -> String { base.label() + " + milk" }
}
struct WithSyrup: Coffee {
let base: any Coffee
func cost() -> Int { base.cost() + 30 }
func label() -> String { base.label() + " + syrup" }
}
let fancy = WithSyrup(base: WithMilk(base: WithMilk(base: Espresso())))
print(fancy.label())
print(fancy.cost())输出:
espresso + milk + milk + syrup
330装饰器适合为现有组件添加日志记录、缓存或验证等横切功能,同时保持组件本身不变:
protocol DataSource { func fetch(_ id: Int) -> String }
struct Database: DataSource { func fetch(_ id: Int) -> String { "row \(id)" } }
struct Logged: DataSource {
let inner: any DataSource
func fetch(_ id: Int) -> String {
print("fetching \(id)")
return inner.fetch(id)
}
}
let source: any DataSource = Logged(inner: Database())
print(source.fetch(7))输出:
fetching 7
row 7使用该对象的代码只能看到协议,因此无法区分装饰后的对象和普通对象。这就是这种模式可以安全地在之后添加的原因。
func printReceipt(_ c: any Coffee) { // 适用于 Espresso 及其任何包装后的版本
print("\(c.label()): \(c.cost())")
}挑战
简单披萨使用装饰器构建。协议 Pizza 有 price() 和 toppings()(返回名称数组)。Base 的价格是 800,没有配料。请实现装饰器 Cheese(+150)、Olives(+100)和 Double(将内部所有内容的价格加倍,并将配料重复两次)。完成 build(_:),它从 Base 开始,并按名称顺序逐个套上装饰器。
提供的代码会读取多行内容,每行包含以逗号分隔的配料名称(也可以是空行),然后构建每个披萨并输出 1050: cheese, olives 或 800: plain。
你的代码应写在 Pizza.swift、Base.swift、Cheese.swift、Olives.swift、Double.swift 和 Functions.swift 中。main.swift 包含提供的输入/输出代码,不能编辑。
亲手试试
// 提供的输入输出代码:保持原样。
var input: [String] = []
while let line = readLine() { input.append(line) }
for line in input {
let names = line.isEmpty ? [] : line.split(separator: ",").map(String.init)
let p = build(names)
let tops = p.toppings()
print("\(p.price()): " + (tops.isEmpty ? "plain" : tops.joined(separator: ", ")))
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Swift 在线编辑器