設計模式(二十一)行為型:狀態模式詳解
狀態模式(State Pattern)是 GoF 23 種設計模式中的行為型模式之一,其核心價值在于允許一個對象在其內部狀態改變時改變其行為,使得對象看起來像是修改了它的類。它通過將與特定狀態相關的行為封裝到獨立的狀態類中,將龐大的條件分支(如
if-else
或switch-case
)轉化為對象間的委托關系,從而實現行為的動態切換與高度可擴展性。狀態模式是構建復雜狀態機、工作流引擎、游戲角色行為、網絡連接管理、訂單生命周期、UI 狀態管理等系統的理想選擇,是將“狀態驅動行為”這一自然邏輯優雅映射到面向對象設計的關鍵范式。
一、詳細介紹
狀態模式解決的是“一個對象的行為取決于其內部狀態,且狀態數量較多、狀態轉換復雜、行為隨狀態頻繁變化”的問題。在傳統設計中,通常使用條件語句(如 switch
)根據當前狀態決定執行何種行為。這導致:
- 代碼臃腫:所有狀態相關的行為集中在單一類中,方法龐大。
- 難以維護:新增狀態或修改狀態轉換邏輯需要修改大量
switch
語句。 - 違反單一職責原則:一個類承擔了所有狀態的行為。
- 違反開閉原則:對擴展開放,對修改關閉。
狀態模式的核心思想是:將每個狀態封裝成一個獨立的類,每個狀態類實現與該狀態相關的行為。原對象(上下文)持有當前狀態對象的引用,并將狀態相關的行為委托給當前狀態對象執行。
該模式包含以下核心角色:
- Context(上下文):定義客戶端使用的接口,維護一個對
State
對象的引用,表示當前狀態。它將狀態相關的行為委托給當前狀態對象。 - State(狀態接口):定義所有具體狀態類共有的接口,聲明與狀態相關的行為方法(如
handle()
)。 - ConcreteStateA, ConcreteStateB, …(具體狀態類):實現
State
接口,封裝與特定狀態相關的行為。每個具體狀態類知道在特定操作下應如何響應,并可能在響應后改變上下文的當前狀態(即狀態轉換)。
狀態模式的關鍵優勢:
- 消除復雜條件語句:將
switch
邏輯轉化為多態方法調用。 - 符合開閉原則:新增狀態只需添加新的具體狀態類,無需修改現有代碼。
- 符合單一職責原則:每個狀態類只負責一種狀態的行為。
- 提高可維護性:狀態行為集中,邏輯清晰。
- 支持動態狀態轉換:狀態轉換由狀態對象內部決定或由上下文協調。
與“策略模式”相比,策略模式關注算法的替換,狀態模式關注狀態驅動的行為變化;策略通常由客戶端選擇,狀態由內部邏輯自動轉換。與“觀察者模式”相比,觀察者是一對多通知,狀態是單對象行為切換。與“命令模式”相比,命令封裝請求,狀態封裝行為。
狀態模式適用于:
- 對象有明確的狀態概念(如訂單:新建、已支付、已發貨、已完成)。
- 行為隨狀態變化而變化。
- 狀態轉換邏輯復雜。
- 需要避免龐大的條件語句。
二、狀態模式的UML表示
以下是狀態模式的標準 UML 類圖:
圖解說明:
Context
持有對State
的引用,代表當前狀態。Context
的request()
方法將調用委托給當前State
的handle()
。ConcreteState
實現handle()
,執行特定于該狀態的行為,并可能調用Context
的setState()
來觸發狀態轉換。- 狀態轉換可以由狀態對象自身決定,或由
Context
根據業務邏輯協調。
三、一個簡單的Java程序實例及其UML圖
以下是一個 TCP 連接狀態機的簡化示例,包含 CLOSED
, LISTEN
, ESTABLISHED
, CLOSE_WAIT
狀態。
Java 程序實例
// 狀態接口
interface TCPState {void activeOpen(TCPConnection context);void passiveOpen(TCPConnection context);void close(TCPConnection context);void acknowledge(TCPConnection context);void send(TCPConnection context);String getStateName(); // 用于顯示
}// 上下文:TCP連接
class TCPConnection {private TCPState currentState;// 預定義狀態實例(可單例)private static final TCPState CLOSED = new ClosedState();private static final TCPState LISTEN = new ListenState();private static final TCPState ESTABLISHED = new EstablishedState();private static final TCPState CLOSE_WAIT = new CloseWaitState();public TCPConnection() {this.currentState = CLOSED;System.out.println("🔌 連接初始化為 CLOSED 狀態");}// 狀態相關操作,委托給當前狀態public void activeOpen() {System.out.println("👉 客戶端發起主動打開...");currentState.activeOpen(this);}public void passiveOpen() {System.out.println("👉 服務端發起被動打開...");currentState.passiveOpen(this);}public void close() {System.out.println("👉 發起關閉連接...");currentState.close(this);}public void acknowledge() {System.out.println("👉 收到確認...");currentState.acknowledge(this);}public void send() {System.out.println("👉 發送數據...");currentState.send(this);}// 狀態轉換方法public void changeState(TCPState newState) {if (this.currentState != null) {System.out.println("🔄 狀態轉換: " + this.currentState.getStateName() + " → " + newState.getStateName());}this.currentState = newState;}// 獲取當前狀態(用于狀態判斷,可選)public TCPState getCurrentState() {return currentState;}// 預定義狀態的獲取方法(簡化客戶端使用)public static TCPState getClosedState() { return CLOSED; }public static TCPState getListenState() { return LISTEN; }public static TCPState getEstablishedState() { return ESTABLISHED; }public static TCPState getCloseWaitState() { return CLOSE_WAIT; }
}// 具體狀態:CLOSED
class ClosedState implements TCPState {@Overridepublic void activeOpen(TCPConnection context) {System.out.println(" ? CLOSED: 執行主動打開 -> 發送 SYN, 進入 SYN_SENT (本例簡化為直接進入 ESTABLISHED)");context.changeState(TCPConnection.getEstablishedState());}@Overridepublic void passiveOpen(TCPConnection context) {System.out.println(" ? CLOSED: 執行被動打開 -> 進入 LISTEN");context.changeState(TCPConnection.getListenState());}@Overridepublic void close(TCPConnection context) {System.out.println(" ?? CLOSED: 連接已關閉,無需操作");}@Overridepublic void acknowledge(TCPConnection context) {System.out.println(" ? CLOSED: 無法處理確認");}@Overridepublic void send(TCPConnection context) {System.out.println(" ? CLOSED: 連接未建立,無法發送");}@Overridepublic String getStateName() {return "CLOSED";}
}// 具體狀態:LISTEN
class ListenState implements TCPState {@Overridepublic void activeOpen(TCPConnection context) {System.out.println(" ? LISTEN: 收到 SYN -> 發送 SYN-ACK, 進入 SYN_RECEIVED (本例簡化為進入 ESTABLISHED)");context.changeState(TCPConnection.getEstablishedState());}@Overridepublic void passiveOpen(TCPConnection context) {System.out.println(" ?? LISTEN: 已在監聽狀態");}@Overridepublic void close(TCPConnection context) {System.out.println(" ? LISTEN: 關閉監聽 -> 進入 CLOSED");context.changeState(TCPConnection.getClosedState());}@Overridepublic void acknowledge(TCPConnection context) {System.out.println(" ? LISTEN: 未建立連接,無法確認");}@Overridepublic void send(TCPConnection context) {System.out.println(" ? LISTEN: 連接未建立,無法發送");}@Overridepublic String getStateName() {return "LISTEN";}
}// 具體狀態:ESTABLISHED
class EstablishedState implements TCPState {@Overridepublic void activeOpen(TCPConnection context) {System.out.println(" ?? ESTABLISHED: 連接已建立,無需打開");}@Overridepublic void passiveOpen(TCPConnection context) {System.out.println(" ?? ESTABLISHED: 連接已建立,無需打開");}@Overridepublic void close(TCPConnection context) {System.out.println(" ? ESTABLISHED: 發送 FIN -> 進入 FIN_WAIT_1 (本例簡化為進入 CLOSE_WAIT)");context.changeState(TCPConnection.getCloseWaitState());}@Overridepublic void acknowledge(TCPConnection context) {System.out.println(" ? ESTABLISHED: 確認數據包");// 通常發送 ACK}@Overridepublic void send(TCPConnection context) {System.out.println(" ? ESTABLISHED: 數據發送成功");// 發送數據包}@Overridepublic String getStateName() {return "ESTABLISHED";}
}// 具體狀態:CLOSE_WAIT
class CloseWaitState implements TCPState {@Overridepublic void activeOpen(TCPConnection context) {System.out.println(" ? CLOSE_WAIT: 連接正在關閉,無法打開");}@Overridepublic void passiveOpen(TCPConnection context) {System.out.println(" ? CLOSE_WAIT: 連接正在關閉,無法打開");}@Overridepublic void close(TCPConnection context) {System.out.println(" ? CLOSE_WAIT: 發送 FIN -> 進入 LAST_ACK (本例簡化為進入 CLOSED)");context.changeState(TCPConnection.getClosedState());}@Overridepublic void acknowledge(TCPConnection context) {System.out.println(" ? CLOSE_WAIT: 確認對方的 FIN");}@Overridepublic void send(TCPConnection context) {System.out.println(" ?? CLOSE_WAIT: 可能允許發送最后數據,但本例禁止");}@Overridepublic String getStateName() {return "CLOSE_WAIT";}
}// 客戶端使用示例
public class StatePatternDemo {public static void main(String[] args) {System.out.println("🌐 TCP 連接狀態機 - 狀態模式示例\n");TCPConnection connection = new TCPConnection();System.out.println("\n--- 客戶端連接流程 ---");connection.activeOpen(); // CLOSED -> ESTABLISHEDconnection.send(); // 發送數據connection.acknowledge(); // 確認connection.close(); // ESTABLISHED -> CLOSE_WAIT -> CLOSEDSystem.out.println("\n--- 服務端連接流程 ---");connection.passiveOpen(); // CLOSED -> LISTENconnection.activeOpen(); // LISTEN -> ESTABLISHED (收到客戶端 SYN)connection.send(); // 發送數據connection.close(); // ESTABLISHED -> CLOSE_WAIT -> CLOSEDSystem.out.println("\n--- 在 CLOSED 狀態嘗試無效操作 ---");connection.send(); // 應提示無法發送}
}
實例對應的UML圖(簡化版)
運行說明:
TCPConnection
是上下文,持有當前TCPState
。- 每個操作(
activeOpen
,close
等)被委托給當前狀態對象的對應方法。 - 狀態方法執行特定行為,并可能調用
changeState()
觸發狀態轉換。 - 不同狀態對同一操作的響應不同(如
CLOSED
和ESTABLISHED
對send
的處理)。 - 狀態轉換邏輯內置于狀態類中,清晰且可擴展。
四、總結
特性 | 說明 |
---|---|
核心目的 | 允許對象在狀態改變時改變行為,消除條件分支 |
實現機制 | 將狀態行為封裝到獨立類,上下文委托調用 |
優點 | 消除復雜條件語句、符合開閉/單一職責原則、提高可維護性、支持動態轉換 |
缺點 | 增加類數量、狀態轉換邏輯可能分散、簡單狀態機可能過度設計 |
適用場景 | 復雜狀態機、工作流、訂單生命周期、游戲AI、UI狀態管理 |
不適用場景 | 狀態極少、行為簡單、狀態轉換固定 |
狀態模式使用建議:
- 狀態類可設計為單例(無狀態或共享狀態)。
- 狀態轉換可由狀態對象自身決定,或由上下文根據業務規則協調。
- 可結合“工廠模式”創建狀態對象。
- 在 Java 中,
enum
可實現簡單狀態模式(每個枚舉常量實現接口)。
架構師洞見:
狀態模式是“有限狀態機(FSM)”在面向對象中的優雅實現。在現代架構中,其思想已演變為工作流引擎(如 Camunda, Airflow)、事件驅動狀態管理(Redux, Vuex)、服務編排(Kubernetes Operators) 和 AI Agent 的決策狀態 的核心。例如,Redux 的reducer
函數根據action
和當前state
計算新state
,本質是狀態模式的函數式變體;微服務的 Saga 模式管理分布式事務狀態;在自動駕駛中,車輛行為模式(巡航、變道、停車)是復雜的狀態機。未來趨勢是:狀態模式將與形式化方法結合,實現狀態機的自動驗證;在量子計算中,量子態的演化可建模為狀態轉換;在元宇宙中,虛擬角色的行為狀態(行走、戰斗、交互)由狀態模式驅動;在AI中,LLM Agent 的“思考-行動-觀察”循環可視為一個高級狀態機。
掌握狀態模式,是設計復雜業務邏輯、高可靠性系統的關鍵。作為架構師,應在面對“多狀態、多行為、復雜轉換”的領域模型時,果斷采用狀態模式。它不僅是模式,更是系統確定性的保障——它將混沌的條件邏輯轉化為清晰、可驗證、可演進的狀態圖,讓系統的每一次行為變遷都變得可預測、可追溯、可管理,從而構建出真正健壯、可信賴的軟件系統。