Menu
CoddyTech

状态模式

本课属于 Coddy Swift 学习旅程的“面向对象编程”单元。第 49 课,共 57 课。

状态模式让对象在内部状态发生变化时改变行为。每种状态都有自己的类型,由它决定会发生什么,以及下一个状态是什么:

protocol LightState {
    var name: String { get }
    func next() -> any LightState
}
struct Red: LightState { var name: String { "red" }; func next() -> any LightState { Green() } }
struct Green: LightState { var name: String { "green" }; func next() -> any LightState { Yellow() } }
struct Yellow: LightState { var name: String { "yellow" }; func next() -> any LightState { Red() } }

var light: any LightState = Red()
for _ in 1...4 {
    print(light.name)
    light = light.next()
}

输出:

red
green
yellow
red

拥有状态的对象会将请求转发给当前状态。每种状态都以自己的方式处理事件,而不允许执行这些操作的状态会拒绝非法操作:

protocol OrderState {
    var name: String { get }
    func pay() -> (any OrderState)?
    func ship() -> (any OrderState)?
}
struct New: OrderState { var name: String { "new" }; func pay() -> (any OrderState)? { Paid() }; func ship() -> (any OrderState)? { nil } }
struct Paid: OrderState { var name: String { "paid" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { Shipped() } }
struct Shipped: OrderState { var name: String { "shipped" }; func pay() -> (any OrderState)? { nil }; func ship() -> (any OrderState)? { nil } }

final class Order {
    private(set) var state: any OrderState = New()
    func handle(_ event: String) -> String {
        guard let next = event == "pay" ? state.pay() : state.ship() else { return "cannot \(event) when \(state.name)" }
        state = next
        return "now \(state.name)"
    }
}

let order = Order()
for e in ["ship", "pay", "pay", "ship"] { print(order.handle(e)) }

输出:

cannot ship when new
now paid
cannot pay when paid
now shipped

对于少量没有自身数据的状态,使用带有 switch 的枚举通常能更简洁地表达相同的意思,而且编译器会检查是否处理了每种状态:

enum Door {
    case open, closed, locked

    func handle(_ action: String) -> Door? {
        switch (self, action) {
        case (.closed, "open"): return .open
        case (.open, "close"): return .closed
        case (.closed, "lock"): return .locked
        case (.locked, "unlock"): return .closed
        default: return nil
        }
    }
}

var door = Door.closed
for a in ["lock", "open", "unlock", "open"] {
    if let next = door.handle(a) { door = next; print("\(a) -> \(door)") } else { print("cannot \(a)") }
}

输出:

lock -> locked
cannot open
unlock -> closed
open -> open

当每种状态都有多种行为或自己的数据时,状态类型就很有用。无论哪种情况,每种状态的规则都集中在一处,而不是分散在对象中的 if 检查里。

// 分散处理:每个方法都要检查状态
func ship() { if status == "paid" { ... } else if status == "new" { ... } }
// 状态模式:每种状态自行决定如何处理
state = state.ship() ?? state
challenge icon

挑战

简单

用包含 idle、hasCoin(Int) 和 soldOut 这些分支的枚举状态 Vending 来模拟一台自动售货机。handle(_ event:) 返回下一个状态;如果事件不允许,则返回 nil:在 idle 状态下,coin <n> 会转到 hasCoin(n);在 hasCoin 状态下,它会增加金额;在 hasCoin 状态下,金额至少为 150 时执行 buy 会转到 idle(表示售出成功);在 hasCoin 状态下执行 refund 会转到 idle;在任何状态下执行 empty 都会转到 soldOut;在 soldOut 状态下执行 refill 会转到 idle。

提供的代码会按顺序应用这些事件,并在每次应用后输出状态(idle、coin 100、sold out)或 not allowed: <event>。

将你的代码写在 Vending.swift 中。main.swift 包含提供的输入/输出代码,不能编辑。

亲手试试

// 提供的输入/输出代码:保持原样
var input: [String] = []
while let line = readLine() { input.append(line) }
var machine = Vending.idle
for event in input {
    if let next = machine.handle(event) {
        machine = next
        print(machine.label)
    } else {
        print("not allowed: \(event)")
    }
}
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Swift 在线编辑器