學習設計模式時,幾乎一定會遇到一個卡關的地方。
那就是 Strategy 模式和 State 模式。
把 UML(Unified Modeling Language,統一塑模語言)圖並排比較,會發現它們幾乎像複製貼上一樣。放一個介面、建立多個實作,再由 context 持有它們。
你可能會想:「這不就只是名稱不同嗎?」
先說結論。區分兩種模式的關鍵不是結構,而是意圖。Strategy 從外部替換演算法;State 則由內部的狀態自行移動到下一個狀態。
抓住這一句讀下去,就不會再搞混了。
使用狀態物件移除實際條件判斷的實作,會在 Swift State 模式重構中逐步說明。
為什麼結構會如此相似?
兩者都是 GoF(Gang of Four)設計模式書中的行為模式。
兩者使用相同的材料:共用介面、實作該介面的多個類別,以及持有它們的 context。
所以只看程式碼,真的很難分辨。
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 的主體,也就是外部的 client 程式碼。
把付款方式從卡片改成 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()由狀態類別本身執行。client 只會呼叫 change()。
這正是核心所在。
Strategy 從外部替換,State 則在內部自行轉換。
用三點整理決定性的差異
只用文字說明容易再次混淆,所以整理成表格。
| 分類 | Strategy 模式 | State 模式 |
|---|---|---|
| 意圖 | 替換演算法 | 根據狀態改變行為 |
| 轉換主體 | 外部 client | 狀態物件本身 |
| 物件間關係 | 彼此不知道(獨立) | 彼此知道並互相參照 |
| 生命週期 | 通常決定一次後維持 | 執行期間持續變更 |
請特別記住第二列和第三列。
Strategy 彼此不知道對方的存在。卡片付款不需要知道 Kakao Pay。
相反地,State 必須互相知道。因為紅燈會直接建立並傳遞綠燈物件。
這種「彼此知道還是不知道」,是從程式碼區分兩種模式最可靠的線索。
什麼時候該用,什麼時候該避免?
整理我在實務上的判斷標準如下。
| 情境 | 判斷 |
|---|---|
| 只有執行相同工作的方法不同時(排序、付款、壓縮) | Strategy 模式 |
| 物件會依情境採取不同行為,而且情境會改變時 | State 模式 |
| 轉換規則變成複雜的 if-else 大塊時 | 用 State 模式處理 |
| 簡單到只要傳入一個函式時 | 兩者都過度設計,使用 closure |
也有需要注意的地方。
如果只有 2~3 個狀態且轉換也很簡單,很多時候不必硬套模式,使用 enum 和 switch 就夠了。
模式是管理複雜度的工具,不是替簡單程式碼披上形式的裝飾。
面試時通常會這樣問
Q. Strategy 模式和 State 模式的結構相同,要如何區分?
結構幾乎相同,但意圖不同。Strategy 的目的是從外部替換演算法,State 的目的是依內部狀態改變行為。關鍵差異在於轉換主體:Strategy 由 client 推進,State 則由狀態物件本身前往下一個狀態。
Q. State 模式中狀態彼此參照,這不會造成問題嗎?
狀態之間確實會產生耦合。因此,也可以把轉換邏輯從狀態中移出,放到 context 或獨立的轉換表中,以降低耦合。當狀態變多且轉換互相糾纏時,也可以使用狀態機函式庫。
即使圖看起來一模一樣,也不用害怕。只要問「誰負責轉換?」答案就會立刻出現。
從外部替換就是 Strategy,在內部自行前進就是 State。只要記住這句話,今天這篇文章就達成目的了。下次再帶來另一個容易混淆的模式。

