Menu
CoddyTech

策略模式

本课属于 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
challenge icon

挑战

简单

英雄的攻击是一种策略。接口 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()
        }
    }
}
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Kotlin 在线编辑器