本章以旋轉閘門(Turnstile)的有限狀態機(FSM)為範例,深入探討 STATE 模式的三種實作策略,並比較 STATE 與 STRATEGY 模式的異同。

有限狀態機:旋轉閘門#

Figure 36.1: Turnstile FSM that covers abnormal events

  • 旋轉閘門有兩個狀態:Locked(上鎖)與 Unlocked(解鎖)
  • 兩個事件:Coin(投幣)與 Pass(通過)
  • 轉換規則:
    • Locked + Coin → Unlocked(解鎖門閘)
    • Unlocked + Pass → Locked(鎖上門閘)
    • Locked + Pass → 保持 Locked(觸發警報)
    • Unlocked + Coin → 保持 Unlocked(退幣)
stateDiagram-v2
    [*] --> Locked
    Locked --> Unlocked : Coin / Unlock
    Unlocked --> Locked : Pass / Lock
    Locked --> Locked : Pass / Alarm
    Unlocked --> Unlocked : Coin / Thankyou

實作策略一:巢狀 Switch/Case#

  • 最簡單直接的實作方式:用列舉表示狀態與事件,以巢狀 switch 處理轉換
public class Turnstile
{
    internal enum State { Locked, Unlocked }
    internal State state = State.Locked;

    public void HandleEvent(Event e)
    {
        switch (state)
        {
            case State.Locked:
                switch (e)
                {
                    case Event.Coin:
                        state = State.Unlocked;
                        Unlock();
                        break;
                    case Event.Pass:
                        Alarm();
                        break;
                }
                break;
            case State.Unlocked:
                switch (e)
                {
                    case Event.Coin:
                        Thankyou();
                        break;
                    case Event.Pass:
                        state = State.Locked;
                        Lock();
                        break;
                }
                break;
        }
    }
}
  • 優點:簡單、容易理解、效能好
  • 缺點:狀態與事件增多時,switch 會變得龐大且難以維護

實作策略二:轉換表(Transition Table)#

  • 使用資料驅動的方式:將所有轉換定義在一個表格中,用查表取代 switch
private void AddTransition(
    State currentState, Event trigger,
    State newState, Action action)
{
    transitions.Add(new Transition(currentState, trigger, newState, action));
}

// 建立轉換表
AddTransition(State.Locked, Event.Coin, State.Unlocked, Unlock);
AddTransition(State.Locked, Event.Pass, State.Locked, Alarm);
AddTransition(State.Unlocked, Event.Coin, State.Unlocked, Thankyou);
AddTransition(State.Unlocked, Event.Pass, State.Locked, Lock);
  • 優點:轉換邏輯集中在表格中,易於修改與閱讀
  • 缺點:查表有輕微效能損失,且不易處理複雜的轉換條件

實作策略三:STATE 模式#

Figure 36.3: STATE versus STRATEGY

  • 使用物件導向的方式:每個狀態是一個類別,實作相同的介面
public interface TurnstileState
{
    void Coin(Turnstile t);
    void Pass(Turnstile t);
}

public class LockedTurnstileState : TurnstileState
{
    public void Coin(Turnstile t)
    {
        t.SetUnlocked();
        t.Unlock();
    }

    public void Pass(Turnstile t)
    {
        t.Alarm();
    }
}

public class UnlockedTurnstileState : TurnstileState
{
    public void Coin(Turnstile t)
    {
        t.Thankyou();
    }

    public void Pass(Turnstile t)
    {
        t.SetLocked();
        t.Lock();
    }
}
  • Turnstile 類別持有一個 TurnstileState 參考,事件發生時委派給當前狀態處理

  • 狀態的切換由狀態物件本身決定(呼叫 t.SetLocked()t.SetUnlocked()

  • 優點:符合 OCP——新增狀態只需新增類別,不需修改既有程式碼;每個狀態的邏輯高度內聚

  • 缺點:類別數量隨狀態數量增長;簡單的 FSM 用 STATE 模式可能過度設計

技巧: 狀態物件通常是無狀態的(Stateless),因此可以使用 Singleton 或靜態實例來避免重複建立。Turnstile 類別可以持有所有狀態的靜態實例。

STATE vs. STRATEGY#

  • 結構上完全相同——兩者都是 Context 持有一個策略/狀態介面的參考
  • 語義上不同
    • STRATEGY:Context 在建立時選擇一個策略,之後通常不再改變
    • STATE:Context 的狀態會隨事件動態切換——這是關鍵區別
  • STATE 模式中,狀態的轉換邏輯由狀態物件自己或 Context 控制

SMC 狀態機編譯器#

  • 當 FSM 變得複雜時,手動編寫狀態類別會很繁瑣
  • 作者介紹了 SMC(State Machine Compiler):一個將文字描述的 FSM 自動產生程式碼的工具
  • SMC 的輸入是簡潔的狀態轉換定義,輸出是完整的 STATE 模式程式碼

進階應用#

GUI 互動#

Figure 36.5: Rectangle interaction state machine

  • 繪圖程式中的滑鼠互動可以用 FSM 建模:
    • 初始狀態 → 滑鼠按下 → 拖曳狀態 → 滑鼠放開 → 完成
    • 拖曳時根據滑鼠移動更新矩形預覽
  • STATE 模式讓這類複雜的互動邏輯清晰可維護

分散式處理#

Figure 36.6: Sending large block, using many packets

  • 網路通訊中的大區塊傳輸:將大資料切成小封包,使用 FSM 管理傳送與確認流程
  • 三層 FSM 架構:
    • 頂層:管理連線的建立與中斷
    • 中層:管理區塊的傳送與確認
    • 底層:管理封包的傳送與重試

重點: 選擇 FSM 實作策略時,應根據複雜度而定:簡單的 FSM 用 switch/case 即可;中等複雜度用轉換表;複雜或需要頻繁修改的 FSM 用 STATE 模式或 SMC 工具。

本章小結#

STATE 模式將有限狀態機的每個狀態封裝為獨立的類別,讓狀態轉換邏輯清晰且易於擴展。三種實作策略(switch/case、轉換表、STATE 模式)各有優劣,應根據 FSM 的規模與變化頻率來選擇。STATE 與 STRATEGY 在結構上相同但語義不同——STATE 的核心特徵是狀態的動態切換。