装饰器模式
本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 47 课,共 57 课。
装饰器模式通过用另一个具有相同接口的对象包装对象,来为对象添加行为。装饰器会调用它包装的对象,并添加自身的部分,因此可以叠加装饰器:
interface Coffee {
fun cost(): Int
fun description(): String
}
class Espresso : Coffee {
override fun cost() = 200
override fun description() = "espresso"
}
class Milk(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 50
override fun description() = inner.description() + ", milk"
}
class Syrup(private val inner: Coffee) : Coffee {
override fun cost() = inner.cost() + 30
override fun description() = inner.description() + ", syrup"
}在 main 中:
val order = Syrup(Milk(Milk(Espresso())))
println("${order.description()}: ${order.cost()}")输出:
espresso, milk, milk, syrup: 330当装饰器改变结果时,它们的顺序很重要:每个装饰器都会处理它内部的对象所返回的内容:
interface Text {
fun render(): String
}
class Word(private val w: String) : Text {
override fun render() = w
}
class Bracket(private val inner: Text) : Text {
override fun render() = "[" + inner.render() + "]"
}
class Twice(private val inner: Text) : Text {
override fun render() = inner.render() + inner.render()
}在 main 中:
println(Twice(Bracket(Word("hi"))).render())
println(Bracket(Twice(Word("hi"))).render())输出:
[hi][hi]
[hihi]使用 by 时,装饰器只需编写它要更改的成员,其余部分会自动转发:
interface Source {
fun read(): String
fun name(): String
}
class FileSource(private val content: String) : Source {
override fun read() = content
override fun name() = "notes.txt"
}
class Censored(private val inner: Source, private val word: String) : Source by inner {
override fun read() = inner.read().replace(word, "***")
}在 main 中:
val src = Censored(FileSource("the secret plan"), "secret")
println("${src.name()}: ${src.read()}")输出:
notes.txt: the *** plan装饰器会在程序运行时组合起来,例如根据顾客的选择;而子类会在编写代码时固定一种组合。若把 3 种额外配料作为子类,每种组合都需要一个类;使用装饰器时,fold 会根据列表将它们叠加起来:
interface Coffee {
fun cost(): Int
}
class Espresso : Coffee {
override fun cost() = 200
}
class Extra(private val inner: Coffee, private val price: Int) : Coffee {
override fun cost() = inner.cost() + price
}在 main 中:
val prices = mapOf("milk" to 50, "syrup" to 30, "cream" to 70)
val choices = listOf("milk", "cream", "milk")
val coffee = choices.fold<String, Coffee>(Espresso()) { c, extra -> Extra(c, prices.getValue(extra)) }
println(coffee.cost())输出:
370挑战
简单披萨由实现给定接口 Pizza(包含 price() 和 toppings())的装饰器构建。编写 Base(价格为 800,不加配料),以及装饰器 Cheese(加价 150,添加 cheese)、Olives(加价 100,添加 olives)和 DoubleUp。DoubleUp 会将内部所有内容的价格加倍,并将配料重复两次。然后编写 build(names),从 Base 开始,按顺序为其套上每个名字对应的装饰器(cheese、olives 或 double)。
给定的代码会读取多行逗号分隔的名称或 plain,为每行构建一份披萨,并打印 1050: cheese, olives 或 800: plain。
请在 Base.kt、Cheese.kt、Olives.kt、DoubleUp.kt、Build.kt 和 Pizza.kt 中编写代码。Main.kt 包含给定的输入和输出代码,不能修改。
亲手试试
fun main() {
// 提供的输入/输出代码:保持原样
val input = generateSequence(::readLine).toList()
for (line in input) {
val names = if (line == "plain") listOf() else line.split(",")
val pizza = build(names)
val toppings = pizza.toppings()
println("${pizza.price()}: " + if (toppings.isEmpty()) "plain" else toppings.joinToString(", "))
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Kotlin 在线编辑器