意圖(Intent)#
讓物件在其內部狀態改變時改變行為,使物件看起來好像改變了它的類別。
別名(Also Known As)#
- Objects for States
動機(Motivation)#
考慮一個代表網路連線的 TCPConnection 類別。TCP 連線可以處於多種狀態:Established、Listening、Closed。當 TCPConnection 收到請求時,會根據當前狀態做出不同回應——例如 Open 請求在 Closed 和 Established 狀態下的效果截然不同。
State 模式的關鍵做法是引入抽象類別 TCPState,代表連線的各種狀態。TCPState 宣告一個所有狀態共用的介面,其子類別(如 TCPEstablished、TCPClosed)實作各自的狀態行為。
TCPConnection 持有一個 TCPState 的實例,將所有狀態相關的請求委派給它。當連線狀態轉換時,TCPConnection 會替換其 state 物件——例如從 Established 轉為 Closed 時,將 TCPEstablished 替換為 TCPClosed。
stateDiagram-v2
[*] --> Closed
Closed --> Listening : Open (passive)
Listening --> Established : Acknowledge
Established --> Listening : Close
Established --> Closed : Close
Closed --> Established : Open (active)
Listening --> Closed : Close適用場景(Applicability)#
- 物件的行為取決於其狀態,且必須在執行期根據狀態改變行為
- 操作中包含大量多分支條件判斷,且這些判斷依賴於物件狀態。State 模式將每個分支封裝到獨立類別中,把狀態提升為一等物件
結構(Structure)#
- Context 持有一個 ConcreteState 實例,定義客戶端感興趣的介面
- State 定義封裝特定狀態行為的介面
- ConcreteState 子類別各自實作與某個狀態對應的行為
classDiagram
class Context {
+Request()
}
class State {
<<interface>>
+Handle()
}
class ConcreteStateA {
+Handle()
}
class ConcreteStateB {
+Handle()
}
Context o--> State
State <|.. ConcreteStateA
State <|.. ConcreteStateB參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Context | TCPConnection | 定義客戶端使用的介面;維護一個 ConcreteState 實例,代表當前狀態 |
| State | TCPState | 定義封裝 Context 特定狀態行為的介面 |
| ConcreteState 子類別 | TCPEstablished、TCPListen、TCPClosed | 各自實作對應狀態的行為 |
協作方式(Collaborations)#
- Context 將狀態相關的請求委派給當前的 ConcreteState 物件
- Context 可將自身作為引數傳給 State 物件,讓 State 存取 Context 的資料
- Context 是客戶端的主要介面;客戶端配置 Context 的 State 物件後,不需直接與 State 互動
- Context 或 ConcreteState 都可以決定狀態轉換的時機與規則
優缺點(Consequences)#
- 狀態行為的局部化與分隔:每個狀態的行為集中在對應的 State 子類別中,新增狀態只需定義新子類別。避免了將條件判斷分散在 Context 各處,消除了大型
if/switch語句
State 模式將狀態轉換顯式化。引入獨立的 State 物件使轉換變為重新綁定一個變數(Context 的 State 物件),而非修改多個內部變數,從而保護 Context 免於進入不一致的內部狀態。
- State 物件可共享:若 State 物件沒有實例變數(狀態完全由其型別表示),多個 Context 可共享同一個 State 物件。此時 State 物件本質上是沒有 intrinsic state 的 Flyweight
實作要點(Implementation)#
- 誰定義狀態轉換:
- 若轉換規則固定,可在 Context 中實作
- 更有彈性的做法是讓 State 子類別自行指定後繼狀態與轉換時機,但這會在子類別之間引入實作依賴
- Table-based 替代方案:用表格將輸入映射到狀態轉換。優點是規律性高、可透過修改資料改變轉換規則;缺點是查表效率較低、轉換邏輯不夠明確、難以附加轉換時的額外動作
Table-driven 方法聚焦於定義狀態轉換,而 State 模式聚焦於建模狀態特定的行為。根據需求選擇——需要豐富的狀態行為用 State 模式,只需簡單轉換用表格。
- State 物件的建立與銷毀:
- 需要時建立、用完銷毀——適合狀態不確定且轉換不頻繁的情況
- 預先建立、永不銷毀——適合狀態轉換頻繁的情況,避免重複建立的開銷
- 動態繼承:在支援委派的語言(如 Self)中,可透過改變委派目標來實現狀態轉換,等同在執行期改變繼承結構
已知應用(Known Uses)#
- TCP 連線協定:Johnson 和 Zweig 描述了 State 模式在 TCP 連線協定中的應用
- 互動式繪圖工具:工具列中的每個工具(繪線工具、選取工具等)代表編輯器的不同狀態。使用者切換工具時,編輯器的行為隨之改變。HotDraw 的 DrawingController 和 Unidraw 的 Viewer 都將請求轉發給當前的 Tool 物件
- Coplien 的 Envelope-Letter 慣用法:相關但更通用,用於在執行期改變物件的類別
相關模式(Related Patterns)#
- Flyweight:當 State 物件沒有實例變數時,可作為 Flyweight 共享
- Singleton:State 物件通常實作為 Singleton