意圖(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)#

參與者範例職責
ContextTCPConnection定義客戶端使用的介面;維護一個 ConcreteState 實例,代表當前狀態
StateTCPState定義封裝 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