学习设计模式时,几乎一定会遇到一个卡住的地方。
那就是 Strategy 模式和 State 模式。
把 UML(Unified Modeling Language,统一建模语言)图并排放在一起看,几乎像复制粘贴一样。一个接口、多个实现类,再由上下文持有它们。
你可能会想:“这不就是名字不同吗?”
先说结论。区分这两个模式的关键不是结构,而是意图。Strategy 从外部替换算法;State 则由内部的状态自行转移到下一个状态。
记住这一句话再往下读,就不会再混淆了。
使用状态对象移除实际条件判断的实现,会在 Swift State 模式重构中分步骤展开。
为什么它们的结构如此相似?
两者都是 GoF(Gang of Four)设计模式书中的行为型模式。
两者使用相同的材料:公共接口、多个实现类,以及持有它们的上下文。
所以只看代码,确实很难区分。
Strategy 模式写成代码是这样的。
protocol PaymentStrategy {
func pay(_ amount: Int)
}
struct CardPayment: PaymentStrategy {
func pay(_ amount: Int) { print("使用银行卡 \(amount)韩元付款") }
}
struct KakaoPay: PaymentStrategy {
func pay(_ amount: Int) { print("使用 Kakao Pay \(amount)韩元付款") }
}
final class Checkout {
var strategy: PaymentStrategy
init(strategy: PaymentStrategy) { self.strategy = strategy }
func pay(_ amount: Int) { strategy.pay(amount) }
}
let checkout = Checkout(strategy: CardPayment())
checkout.pay(10000)
checkout.strategy = KakaoPay() // 从外部替换
checkout.pay(5000)
// 输出:
// 使用银行卡 10000韩元付款
// 使用 Kakao Pay 5000韩元付款
这里需要注意的是替换 strategy 的主体,也就是外部的客户端代码。
把支付方式从银行卡改成 Kakao Pay 的,是开发者,也就是外部的某个人。
Strategy 自己完全不知道接下来会是什么,也不关心。
那么 State 模式有什么不同?
State 模式由状态本身决定状态转换。
想想红绿灯。红灯知道下一个是绿灯,绿灯知道下一个是黄灯。
也就是说,转移到下一个状态的规则位于各个状态内部。
protocol TrafficState {
func next(_ light: TrafficLight)
}
final class Red: TrafficState {
func next(_ light: TrafficLight) {
print("红 → 绿")
light.state = Green() // 状态自行指定下一个状态
}
}
final class Green: TrafficState {
func next(_ light: TrafficLight) {
print("绿 → 黄")
light.state = Yellow()
}
}
final class Yellow: TrafficState {
func next(_ light: TrafficLight) {
print("黄 → 红")
light.state = Red()
}
}
final class TrafficLight {
var state: TrafficState = Red()
func change() { state.next(self) }
}
let light = TrafficLight()
light.change()
light.change()
light.change()
// 输出:
// 红 → 绿
// 绿 → 黄
// 黄 → 红
看出区别了吗?
在 Strategy 模式中,我们像 checkout.strategy = KakaoPay()一样从外部直接替换。
在 State 模式中,light.state = Green()由状态类自身完成。客户端只调用 change()。
这正是关键所在。
Strategy 从外部替换,State 在内部自行转移。
用三点总结决定性的区别
只用语言解释容易再次混淆,所以整理成表格。
| 分类 | Strategy 模式 | State 模式 |
|---|---|---|
| 意图 | 替换算法 | 根据状态改变行为 |
| 转换主体 | 外部客户端 | 状态对象自身 |
| 对象之间的关系 | 互不了解(独立) | 相互了解并互相引用 |
| 生命周期 | 通常确定后保持不变 | 执行期间持续变化 |
请特别记住第二行和第三行。
各个 Strategy 互不了解彼此的存在。银行卡支付不需要知道 Kakao Pay。
而状态必须相互了解。因为红灯会直接创建并传递绿灯对象。
“是否相互了解”是从代码中区分这两个模式最可靠的线索。
什么时候该用,什么时候该避免?
我在实际工作中的判断标准可以总结如下。
| 情况 | 判断 |
|---|---|
| 只是完成同一件事的方法有多种时(排序、支付、压缩) | Strategy 模式 |
| 对象会根据情况采取不同的行为,且情况会发生变化时 | State 模式 |
| 转换规则变成复杂的 if-else 代码块时 | 用 State 模式解决 |
| 简单到只需传入一个函数时 | 两者都过度设计,使用闭包 |
还需要注意一点。
如果只有 2~3 个状态,转换也很简单,很多时候无需引入模式,使用 enum 和 switch 就够了。
模式是管理复杂度的工具,不是给简单代码披上形式的装饰。
面试时通常会这样问
Q. Strategy 模式和 State 模式结构相同,如何区分?
结构几乎相同,但意图不同。Strategy 的目的是从外部替换算法,State 的目的是根据内部状态改变行为。决定性的区别在于转换主体:Strategy 由客户端推进,State 则由状态对象自身转移到下一个状态。
Q. State 模式中状态之间会互相引用,这不会有问题吗?
状态之间确实会产生耦合。因此,也可以把转换逻辑从状态中移出,放到上下文或单独的转换表中,以降低耦合。当状态增多、转换相互交织时,也可以使用状态机库。
即使图看起来完全一样,也不用害怕。只要问“谁负责转换”,答案马上就会出现。
从外部替换就是 Strategy,在内部自行转移就是 State。记住这一句话,今天这篇文章就算达成目标了。下次再聊另一个容易混淆的模式。

