每位開發者職涯中都會實作有限狀態機(FSM):訂單流程、遊戲 AI、工作流引擎、通訊協定解析。最直覺的寫法是「查表法」或巨大的 switch-case,但隨著狀態與轉移條件增加,程式碼會變得:
State 模式將每個狀態封裝為獨立類別,將轉移邏輯分散到各狀態內部,實現「狀態即物件」,完美解決上述痛點。
需求:投入 5、10、25 分硬幣,累計 ≥ 25 分出貨並找零(本例簡化不找零),歸零重置。
+----------------+ +------------------+
| VendingMachine|------>| State |
| (Context) | | (Abstract) |
| - state: State | | + AddNickel() |
| + AddNickel() | | + AddDime() |
| + AddDime() | | + AddQuarter() |
| + AddQuarter() | | + ChangeState() |
| + Vend() | +------------------+
+----------------+ ▲
▲ │
│ +-----------+-----------+
│ │ │ │
+---+---+ +---+---+ +---+---+ +---+---+
| Start | | Five | | Ten | |TwentyFive|
+-------+ +-------+ +-------+ +---------+
public abstract class State
{
protected VendingMachine Machine { get; private set; }
public void SetMachine(VendingMachine machine) => Machine = machine;
public virtual void AddNickel() { ThrowInvalid(); }
public virtual void AddDime() { ThrowInvalid(); }
public virtual void AddQuarter() { ThrowInvalid(); }
protected void TransitionTo(State nextState)
{
Machine.TransitionTo(nextState);
}
private void ThrowInvalid() => throw new InvalidOperationException("當前狀態不允許此操作");
}
public class StartState : State
{
private static readonly Lazy _instance = new Lazy(() => new StartState());
public static StartState Instance => _instance.Value;
private StartState() { }
public override void AddNickel() => TransitionTo(FiveState.Instance);
public override void AddDime() => TransitionTo(TenState.Instance);
public override void AddQuarter() { Machine.Vend(); TransitionTo(StartState.Instance); }
public override string ToString() => "Credit: 0¢";
}
public class FiveState : State
{
private static readonly Lazy _instance = new Lazy(() => new FiveState());
public static FiveState Instance => _instance.Value;
private FiveState() { }
public override void AddNickel() => TransitionTo(TenState.Instance);
public override void AddDime() => TransitionTo(FifteenState.Instance);
public override void AddQuarter() { Machine.Vend(); TransitionTo(StartState.Instance); }
public override string ToString() => "Credit: 5¢";
}
// TenState, FifteenState, TwentyFiveState 同理...
public class VendingMachine
{
private State _currentState;
public VendingMachine()
{
_currentState = StartState.Instance;
_currentState.SetMachine(this);
Console.WriteLine("自動販賣機啟動,商品 25¢");
Console.WriteLine(_currentState);
}
public void TransitionTo(State newState)
{
_currentState = newState;
_currentState.SetMachine(this);
Console.WriteLine(_currentState);
}
public void Vend() => Console.WriteLine(">>> 出貨!謝謝惠顧 <<<");
// 對外暴露動作,委派給目前狀態
public void AddNickel() => _currentState.AddNickel();
public void AddDime() => _currentState.AddDime();
public void AddQuarter() => _currentState.AddQuarter();
}
var vm = new VendingMachine();
vm.AddNickel(); // 5¢
vm.AddNickel(); // 10¢
vm.AddDime(); // 20¢
vm.AddNickel(); // 25¢ -> 出貨,歸零
vm.AddQuarter(); // 直接出貨
| 維度 | 查表法 / Switch-Case | State 模式 |
|---|---|---|
| 新增狀態 | 修改核心邏輯,風險高 | 新增類別,零修改既有程式碼 |
| 狀態行為內聚性 | 分散在條件分支 | 封裝於各自類別 |
| 測試性 | 需整合測試 | 可單元測試每個狀態 |
| 記憶體 | 較低 | 每個狀態一個物件(可用單例) |
| 適用場景 | 狀態少、轉移固定 | 狀態多、轉移複雜、頻繁擴充 |
State 模式以「物件代表狀態」的優雅設計,將狀態轉移邏輯局部化,實現高內聚、低耦合、易擴充。下次面對複雜流程控制時,請先問自己:「這是不是一個有限狀態機?若是,State 模式可能是最佳解。」