策略模式
本课属于 Coddy Swift 学习旅程的“面向对象编程”单元。第 45 课,共 57 课。
策略模式将算法的每种变体放在各自的类型中,并通过共享协议来统一。使用它的代码持有一个策略,并可以在运行时切换它:
protocol ShippingStrategy {
func cost(weight: Int) -> Int
}
struct Standard: ShippingStrategy { func cost(weight: Int) -> Int { 5 + weight } }
struct Express: ShippingStrategy { func cost(weight: Int) -> Int { 15 + weight * 2 } }
struct Checkout {
var shipping: any ShippingStrategy
func total(price: Int, weight: Int) -> Int { price + shipping.cost(weight: weight) }
}
var c = Checkout(shipping: Standard())
print(c.total(price: 100, weight: 3))
c.shipping = Express()
print(c.total(price: 100, weight: 3))输出:
108
121在 Swift 中,对于只有一个方法的策略,闭包通常就够用了。这个类型会存储一个函数并调用它:
struct Sorter {
var rule: (String, String) -> Bool
func sort(_ words: [String]) -> [String] { words.sorted(by: rule) }
}
let words = ["banana", "kiwi", "apple"]
var s = Sorter(rule: <)
print(s.sort(words))
s.rule = { $0.count < $1.count }
print(s.sort(words))输出:
["apple", "banana", "kiwi"]
["kiwi", "apple", "banana"]策略会替换算法中的 switch。新增变体时,只需添加新的类型(或闭包),使用它们的代码无需改动:
// 没有策略时:每增加一个新选项,都要修改这个函数
func cost(kind: String, weight: Int) -> Int {
switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// 使用策略后:Checkout 不再需要修改
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }你可以根据数据选择策略,例如根据用户设置,使用一个简单的工厂:
protocol Discount { func apply(_ price: Int) -> Int }
struct NoDiscount: Discount { func apply(_ p: Int) -> Int { p } }
struct Percent: Discount { let rate: Int; func apply(_ p: Int) -> Int { p - p * rate / 100 } }
func discount(for member: String) -> any Discount {
member == "gold" ? Percent(rate: 20) : NoDiscount()
}
print(discount(for: "gold").apply(200))
print(discount(for: "basic").apply(200))输出:
160
200挑战
简单游戏角色的攻击是一种策略。协议 Attack 包含 name 和 damage(strength:) -> Int。实现 Sword(strength * 2)、Bow(strength + 5)和 Magic(存储一个 mana;当 mana 至少为 10 时,伤害为 strength * 3,否则为 1;damage 本身不会改变 mana)。Hero 保存自己的 strength 和 attack,并且 strike() 返回 Ada hits with sword for 20。
提供的代码会读取英雄的名字和 strength,然后读取命令 equip sword、equip bow、equip magic 15 或 strike。它会输出每次攻击的结果。
你的代码应写在 Attack.swift、Sword.swift、Bow.swift、Magic.swift 和 Hero.swift 中。main.swift 包含提供的输入/输出代码,不能编辑。
亲手试试
// 提供的输入输出代码:保持原样
var input: [String] = []
while let line = readLine() { input.append(line) }
let head = input[0].split(separator: ",").map(String.init)
var hero = Hero(name: head[0], strength: Int(head[1])!, attack: Sword())
for cmd in input.dropFirst() {
let p = cmd.split(separator: " ").map(String.init)
if p[0] == "strike" { print(hero.strike()); continue }
switch p[1] {
case "bow": hero.attack = Bow()
case "magic": hero.attack = Magic(mana: Int(p[2])!)
default: hero.attack = Sword()
}
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Swift 在线编辑器