策略模式
本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 45 课,共 57 课。
策略模式会将算法的每种变体放在各自的类中,并通过共享接口提供访问方式。使用该模式的代码持有一个策略,并能在程序运行时替换它:
interface Shipping {
fun cost(kg: Int): Int
}
class Standard : Shipping {
override fun cost(kg: Int) = 5 + kg
}
class Express : Shipping {
override fun cost(kg: Int) = 15 + kg * 2
}
class Checkout(var shipping: Shipping) {
fun total(price: Int, kg: Int) = price + shipping.cost(kg)
}在 main 中:
val checkout = Checkout(Standard())
println(checkout.total(100, 3))
checkout.shipping = Express()
println(checkout.total(100, 3))输出:
108
121如果策略只有一个方法,使用函数类型就够了。类会存储一个函数并调用它:
class Formatter(var style: (String) -> String) {
fun show(words: List<String>) = words.joinToString(" ") { style(it) }
}在 main 中:
val f = Formatter { it.uppercase() }
println(f.show(listOf("hello", "world")))
f.style = { it.reversed() }
println(f.show(listOf("hello", "world")))输出:
HELLO WORLD
olleh dlrow策略会替代算法内部的 when。使用 when 时,每增加一个新选项,都要修改算法。使用策略时,新增选项只需添加一个新类或 lambda,Checkout 无需修改:
// 不使用策略:每增加一个选项,都要修改这个函数
fun cost(kind: String, kg: Int) = when (kind) {
"standard" -> 5 + kg
else -> 15 + kg * 2
}
// 使用策略:只需添加一个类,不用修改 Checkout
class Overnight : Shipping {
override fun cost(kg: Int) = 40
}策略通常会根据数据来选择,例如客户的套餐,并使用映射或小型工厂:
val discounts: Map<String, (Int) -> Int> = mapOf(
"gold" to { price -> price * 80 / 100 },
"silver" to { price -> price * 90 / 100 }
)
fun finalPrice(plan: String, price: Int) = discounts[plan]?.invoke(price) ?: price在 main 中:
println(finalPrice("gold", 200))
println(finalPrice("silver", 200))
println(finalPrice("basic", 200))输出:
160
180
200挑战
简单英雄的攻击是一种策略。接口 Attack(包含 name 和 damage(strength))已提供。编写 Sword(伤害为 strength 的 2 倍)、Bow(strength 加 5)和 Magic(mana)(mana 至少为 10 时,伤害为 strength 的 3 倍,否则为 1)。Hero(name, strength, attack) 可以随时更改其 attack,而且 strike() 会返回 Ada hits with sword for 20,不会检查它使用的是哪种攻击。
已提供的代码会读取 name,strength,然后读取命令 equip bow、equip magic 15、equip sword 或 strike,并输出每次攻击的结果。
你的代码要写在 Sword.kt、Bow.kt、Magic.kt、Hero.kt 和 Attack.kt 中。Main.kt 包含已提供的输入/输出代码,不能编辑。
亲手试试
fun main() {
// 提供的输入输出代码:保持原样
val input = generateSequence(::readLine).toList()
val (name, strength) = input[0].split(",")
val hero = Hero(name, strength.toInt(), Sword())
for (cmd in input.drop(1)) {
val p = cmd.split(" ")
if (p[0] == "strike") {
println(hero.strike())
continue
}
hero.attack = when (p[1]) {
"bow" -> Bow()
"magic" -> Magic(p[2].toInt())
else -> Sword()
}
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Kotlin 在线编辑器