State Pattern(狀態模式)
是什麼?
State Pattern 讓物件在內部狀態改變時改變它的行為。每個狀態被封裝成獨立的類別,物件把行為委派給當前的狀態物件。從外部看,好像物件改變了它的類別。
ℹ️GoF 分類
State 屬於行為型模式(Behavioral Pattern),重點在於用物件組合取代大量的 if-else 狀態判斷。
什麼時候用?
用以下 if/then 條件判斷:
- 如果物件有 4 個以上的狀態,且不同狀態下同一個操作的行為完全不同 → 用 State
- 如果程式碼中有大量
if (state == "pending")這類判斷散佈在多個方法中 → 用 State 重構 - 如果你需要實作狀態機(State Machine,指物件有有限個狀態、定義了狀態之間的合法轉換規則)→ 用 State
- 如果狀態只有 2-3 個且轉換簡單 → enum + switch 就夠了
什麼時候不該用?
⚠️過度設計警告
如果狀態只有 2-3 個且不太可能增加,用簡單的 enum + switch 就夠了。State Pattern 適合狀態多、轉換邏輯複雜、且狀態會擴增的場景。每個狀態都要建一個類別,簡單場景反而增加複雜度。
執行流程
定義 State 介面
宣告所有狀態共用的行為方法
實作具體 State
每個狀態類別實作該狀態下的行為
建立 Context
持有當前狀態的參考,委派行為給狀態
狀態轉換
在狀態類別內部決定下一個狀態
Client 操作
Client 只跟 Context 互動,不需要知道當前狀態
流程解讀:State 的精髓是「狀態類別自管轉換」。每個 ConcreteState 知道自己在哪些操作下可以轉到哪個狀態,也知道哪些操作在當前狀態下是非法的。Context 只做委派,不包含任何狀態邏輯。這讓新增狀態時只需新增一個類別並定義它的行為和轉換規則,不需要修改 Context 或其他狀態。
程式碼範例
C# 版本
// 1. State 介面
public interface IOrderState
{
void Next(Order order);
void Cancel(Order order);
string GetStatus();
}
// 2. 具體狀態
public class PendingState : IOrderState
{
public void Next(Order order)
{
Console.WriteLine("訂單已確認,開始處理");
order.SetState(new ProcessingState());
}
public void Cancel(Order order)
{
Console.WriteLine("訂單已取消");
order.SetState(new CancelledState());
}
public string GetStatus() => "待確認";
}
public class ProcessingState : IOrderState
{
public void Next(Order order)
{
Console.WriteLine("訂單已出貨");
order.SetState(new ShippedState());
}
public void Cancel(Order order)
{
Console.WriteLine("處理中的訂單無法取消,請聯繫客服");
}
public string GetStatus() => "處理中";
}
public class ShippedState : IOrderState
{
public void Next(Order order)
{
Console.WriteLine("訂單已送達,交易完成");
order.SetState(new CompletedState());
}
public void Cancel(Order order)
{
Console.WriteLine("已出貨無法取消,請申請退貨");
}
public string GetStatus() => "已出貨";
}
public class CompletedState : IOrderState
{
public void Next(Order order) => Console.WriteLine("訂單已完成,無法繼續");
public void Cancel(Order order) => Console.WriteLine("已完成訂單無法取消");
public string GetStatus() => "已完成";
}
public class CancelledState : IOrderState
{
public void Next(Order order) => Console.WriteLine("已取消訂單無法繼續");
public void Cancel(Order order) => Console.WriteLine("訂單已經取消了");
public string GetStatus() => "已取消";
}
// 3. Context
public class Order
{
private IOrderState _state;
public Order() => _state = new PendingState();
public void SetState(IOrderState state) => _state = state;
public void Next() => _state.Next(this);
public void Cancel() => _state.Cancel(this);
public string GetStatus() => _state.GetStatus();
}
// 4. 使用
var order = new Order();
Console.WriteLine($"狀態:{order.GetStatus()}"); // 待確認
order.Next(); // 訂單已確認,開始處理
order.Next(); // 訂單已出貨
order.Cancel(); // 已出貨無法取消,請申請退貨
order.Next(); // 訂單已送達,交易完成TypeScript 版本
// 1. State 介面
interface OrderState {
next(order: Order): void;
cancel(order: Order): void;
getStatus(): string;
}
// 2. 具體狀態
class PendingState implements OrderState {
next(order: Order) {
console.log("訂單已確認,開始處理");
order.setState(new ProcessingState());
}
cancel(order: Order) {
console.log("訂單已取消");
order.setState(new CancelledState());
}
getStatus() { return "待確認"; }
}
class ProcessingState implements OrderState {
next(order: Order) {
console.log("訂單已出貨");
order.setState(new ShippedState());
}
cancel(order: Order) { console.log("處理中的訂單無法取消"); }
getStatus() { return "處理中"; }
}
class ShippedState implements OrderState {
next(order: Order) {
console.log("訂單已送達,交易完成");
order.setState(new CompletedState());
}
cancel(order: Order) { console.log("已出貨無法取消"); }
getStatus() { return "已出貨"; }
}
class CompletedState implements OrderState {
next(_order: Order) { console.log("訂單已完成"); }
cancel(_order: Order) { console.log("已完成無法取消"); }
getStatus() { return "已完成"; }
}
class CancelledState implements OrderState {
next(_order: Order) { console.log("已取消無法繼續"); }
cancel(_order: Order) { console.log("已經取消了"); }
getStatus() { return "已取消"; }
}
// 3. Context
class Order {
private state: OrderState = new PendingState();
setState(state: OrderState) { this.state = state; }
next() { this.state.next(this); }
cancel() { this.state.cancel(this); }
getStatus() { return this.state.getStatus(); }
}
// 4. 使用
const order = new Order();
console.log(`狀態:${order.getStatus()}`); // 待確認
order.next(); // 訂單已確認,開始處理
order.next(); // 訂單已出貨
order.next(); // 訂單已送達,交易完成Python 版本
from abc import ABC, abstractmethod
# 1. State 介面
class OrderState(ABC):
@abstractmethod
def next(self, order: "Order") -> None:
pass
@abstractmethod
def cancel(self, order: "Order") -> None:
pass
@abstractmethod
def get_status(self) -> str:
pass
# 2. 具體狀態
class PendingState(OrderState):
def next(self, order: "Order") -> None:
print("訂單已確認,開始處理")
order.set_state(ProcessingState())
def cancel(self, order: "Order") -> None:
print("訂單已取消")
order.set_state(CancelledState())
def get_status(self) -> str:
return "待確認"
class ProcessingState(OrderState):
def next(self, order: "Order") -> None:
print("訂單已出貨")
order.set_state(ShippedState())
def cancel(self, order: "Order") -> None:
print("處理中的訂單無法取消")
def get_status(self) -> str:
return "處理中"
class ShippedState(OrderState):
def next(self, order: "Order") -> None:
print("訂單已送達,交易完成")
order.set_state(CompletedState())
def cancel(self, order: "Order") -> None:
print("已出貨無法取消")
def get_status(self) -> str:
return "已出貨"
class CompletedState(OrderState):
def next(self, order: "Order") -> None:
print("訂單已完成")
def cancel(self, order: "Order") -> None:
print("已完成無法取消")
def get_status(self) -> str:
return "已完成"
class CancelledState(OrderState):
def next(self, order: "Order") -> None:
print("已取消無法繼續")
def cancel(self, order: "Order") -> None:
print("已經取消了")
def get_status(self) -> str:
return "已取消"
# 3. Context
class Order:
def __init__(self):
self._state: OrderState = PendingState()
def set_state(self, state: OrderState) -> None:
self._state = state
def next(self) -> None:
self._state.next(self)
def cancel(self) -> None:
self._state.cancel(self)
def get_status(self) -> str:
return self._state.get_status()
# 4. 使用
order = Order()
print(f"狀態:{order.get_status()}") # 待確認
order.next() # 訂單已確認,開始處理
order.next() # 訂單已出貨
order.next() # 訂單已送達,交易完成Java 版本
// 1. State 介面
public interface OrderState {
void next(Order order);
void cancel(Order order);
String getStatus();
}
// 2. 具體狀態
public class PendingState implements OrderState {
public void next(Order order) {
System.out.println("訂單已確認,開始處理");
order.setState(new ProcessingState());
}
public void cancel(Order order) {
System.out.println("訂單已取消");
order.setState(new CancelledState());
}
public String getStatus() { return "待確認"; }
}
public class ProcessingState implements OrderState {
public void next(Order order) {
System.out.println("訂單已出貨");
order.setState(new ShippedState());
}
public void cancel(Order order) {
System.out.println("處理中的訂單無法取消");
}
public String getStatus() { return "處理中"; }
}
public class ShippedState implements OrderState {
public void next(Order order) {
System.out.println("訂單已送達,交易完成");
order.setState(new CompletedState());
}
public void cancel(Order order) {
System.out.println("已出貨無法取消");
}
public String getStatus() { return "已出貨"; }
}
public class CompletedState implements OrderState {
public void next(Order order) { System.out.println("訂單已完成"); }
public void cancel(Order order) { System.out.println("已完成無法取消"); }
public String getStatus() { return "已完成"; }
}
public class CancelledState implements OrderState {
public void next(Order order) { System.out.println("已取消無法繼續"); }
public void cancel(Order order) { System.out.println("已經取消了"); }
public String getStatus() { return "已取消"; }
}
// 3. Context
public class Order {
private OrderState state = new PendingState();
public void setState(OrderState state) { this.state = state; }
public void next() { state.next(this); }
public void cancel() { state.cancel(this); }
public String getStatus() { return state.getStatus(); }
}
// 4. 使用
Order order = new Order();
System.out.println("狀態:" + order.getStatus()); // 待確認
order.next(); // 訂單已確認,開始處理
order.next(); // 訂單已出貨
order.next(); // 訂單已送達,交易完成結構圖
結構解讀:Client 只跟 Context(Order)互動,不直接操作 State。Context 持有當前 State 的參考,將行為委派給它。每個 ConcreteState 知道自己的合法轉換(例如 Pending 可以轉到 Processing 或 Cancelled),在方法內部呼叫 context.SetState(new NextState()) 完成轉換。非法操作會被當前狀態拒絕(如已出貨不能取消)。
實戰補充
💡資深開發者經驗
訂單狀態機:電商系統的訂單流程是 State Pattern 最經典的應用場景。每個狀態有明確的合法轉換,非法操作會被拒絕。
遊戲角色狀態:RPG 遊戲中角色的「正常」「中毒」「暈眩」「無敵」狀態,不同狀態下攻擊、防禦、移動的行為都不同。
State vs Strategy:State Pattern 的狀態之間知道彼此的存在(狀態 A 知道要切換到狀態 B),Strategy 的策略之間互不相識。State 強調狀態轉換,Strategy 強調演算法替換。
工作流程引擎:審批流程、CI/CD Pipeline、文件審核流程,都可以用 State Pattern 來管理。搭配持久化(存到資料庫),可以實現跨 Session 的長期狀態管理。
理解測驗
🤔 State Pattern 和大量 if-else 相比,最大的優勢是什麼?
🤔 State Pattern 中,狀態轉換的邏輯通常放在哪裡?
🤔 State Pattern 和 Strategy Pattern 最關鍵的差異是什麼?
面試常見問題
Q: State Pattern 和 Strategy Pattern 結構幾乎一樣,怎麼區分?
A: 三個關鍵差異:(1) 狀態之間的認知 — State 的各狀態知道彼此存在且會自動轉換,Strategy 的各策略互不相識。(2) 切換控制 — State 由狀態物件自己決定轉換,Strategy 由 Client 決定替換。(3) 語意 — State 表達「物件在不同生命階段的行為」,Strategy 表達「同一個操作的不同演算法」。
Q: 狀態轉換邏輯放在 State 裡面好還是 Context 裡面好?
A: 放在 State 裡面(如範例)讓每個狀態自管轉換,新增狀態不影響其他狀態,但狀態之間產生了依賴。放在 Context 裡面讓所有轉換邏輯集中管理,容易看到全貌,但 Context 會膨脹。實務建議:狀態數量少且轉換規則簡單 → 放 State 內部;狀態多且轉換複雜 → 抽出獨立的 State Machine 配置。
相關模式
| 模式 | 關係 | |------|------| | Strategy | 結構幾乎相同,但意圖不同。State 的狀態會自動轉換,Strategy 的策略由 Client 選擇。口訣:「State 自己換,Strategy 別人換」 | | Singleton / Flyweight | 無狀態的 ConcreteState(如只有行為沒有欄位)可以用 Singleton 或 Flyweight 共享,避免每次轉換都 new | | Command | 可搭配使用 — State 決定「能做什麼」,Command 封裝「怎麼做」 | | Mediator | 狀態機的轉換邏輯複雜時,可以用 Mediator 來集中管理狀態之間的轉換規則 |
重點整理
💡一句話記住
State Pattern = 紅綠燈:狀態自動切換,行為跟著變。 口訣:「狀態管行為,轉換靠自己」
| 概念 | 說明 | |------|------| | State(狀態介面) | 定義所有狀態共用的行為方法 | | ConcreteState(具體狀態) | 實作該狀態下的行為和轉換邏輯 | | Context(上下文) | 持有當前狀態,委派行為給狀態物件 | | 核心好處 | 消除 if-else 狀態判斷,新增狀態符合開放封閉原則 | | 代價 | 狀態少時過度設計,狀態類別數量會增加 |