在軟件開發中,我們經常會遇到對象的行為隨著其內部狀態的變化而變化的情況。例如,一個訂單可能處于"待支付"、"已支付"、"已發貨"或"已完成"等不同狀態,每個狀態下訂單的操作邏輯可能完全不同。如果直接在代碼中使用大量的if-else
或switch-case
語句來判斷狀態,會導致代碼臃腫、難以維護,并且違反開閉原則(OCP)。
狀態模式(State Pattern)提供了一種優雅的解決方案,它允許對象在運行時根據內部狀態改變其行為,而不必在代碼中顯式地檢查狀態。本文將深入探討狀態模式的核心概念、實現方式、優缺點以及實際應用場景,并通過代碼示例幫助讀者更好地理解該模式。
1. 什么是狀態模式?
1.1 定義
狀態模式是一種行為型設計模式,它允許一個對象在其內部狀態改變時改變其行為,使得對象看起來像是修改了它的類。
1.2 核心思想
將狀態抽象成獨立的類,每個狀態類負責處理該狀態下的行為。
上下文(Context)對象持有一個狀態對象的引用,并將行為委托給當前狀態對象。
狀態轉換由狀態類自身或上下文類控制,而不是在業務邏輯中硬編碼。
1.3 適用場景
對象的行為依賴于它的狀態,并且需要在運行時動態改變行為。
代碼中包含大量與對象狀態相關的條件分支語句,導致邏輯復雜。
狀態轉換邏輯較復雜,且未來可能新增狀態。
2. 狀態模式的結構
狀態模式主要由以下幾個角色組成:
角色 | 作用 |
---|---|
Context(上下文) | 定義客戶端接口,維護當前狀態對象。 |
State(抽象狀態) | 定義狀態接口,封裝與特定狀態相關的行為。 |
ConcreteState(具體狀態) | 實現狀態接口,處理該狀態下的行為邏輯,并可觸發狀態轉換。 |
2.1 UML 類圖
┌─────────────┐ ┌─────────────┐
│ Context │ │ State │
├─────────────┤ ├─────────────┤
│ -state:State│<>---->│ +handle() │
├─────────────┤ └─────────────┘
│ +request() │ ▲
└─────────────┘ ││┌──────────────┴──────────────┐│ │┌─────────────┐ ┌─────────────┐│ConcreteStateA│ │ConcreteStateB│├─────────────┤ ├─────────────┤│ +handle() │ │ +handle() │└─────────────┘ └─────────────┘
3. 代碼示例:訂單狀態管理
假設我們有一個訂單系統,訂單可能處于以下狀態:
待支付(Pending)
已支付(Paid)
已發貨(Shipped)
已完成(Completed)
3.1 定義狀態接口
public interface OrderState {void next(Order order);void prev(Order order);void printStatus();
}
3.2 實現具體狀態類
// 待支付狀態
public class Pending implements OrderState {@Overridepublic void next(Order order) {order.setState(new Paid());}@Overridepublic void prev(Order order) {System.out.println("訂單尚未支付,無法回退。");}@Overridepublic void printStatus() {System.out.println("訂單狀態:待支付");}
}// 已支付狀態
public class Paid implements OrderState {@Overridepublic void next(Order order) {order.setState(new Shipped());}@Overridepublic void prev(Order order) {order.setState(new Pending());}@Overridepublic void printStatus() {System.out.println("訂單狀態:已支付");}
}// 已發貨狀態
public class Shipped implements OrderState {@Overridepublic void next(Order order) {order.setState(new Completed());}@Overridepublic void prev(Order order) {order.setState(new Paid());}@Overridepublic void printStatus() {System.out.println("訂單狀態:已發貨");}
}// 已完成狀態
public class Completed implements OrderState {@Overridepublic void next(Order order) {System.out.println("訂單已完成,無法繼續推進。");}@Overridepublic void prev(Order order) {order.setState(new Shipped());}@Overridepublic void printStatus() {System.out.println("訂單狀態:已完成");}
}
3.3 定義上下文類(Order)
public class Order {private OrderState state;public Order() {this.state = new Pending(); // 初始狀態:待支付}public void setState(OrderState state) {this.state = state;}public void nextState() {state.next(this);}public void prevState() {state.prev(this);}public void printStatus() {state.printStatus();}
}
3.4 客戶端測試
public class Client {public static void main(String[] args) {Order order = new Order();order.printStatus(); // 待支付order.nextState(); // 推進到已支付order.printStatus(); // 已支付order.nextState(); // 推進到已發貨order.printStatus(); // 已發貨order.prevState(); // 回退到已支付order.printStatus(); // 已支付order.nextState(); // 推進到已發貨order.nextState(); // 推進到已完成order.printStatus(); // 已完成}
}
輸出結果:
訂單狀態:待支付
訂單狀態:已支付
訂單狀態:已發貨
訂單狀態:已支付
訂單狀態:已發貨
訂單狀態:已完成
4. 狀態模式的優缺點
4.1 優點
??符合單一職責原則:每個狀態類只負責自己的行為邏輯。
??符合開閉原則(OCP):新增狀態時無需修改現有代碼。
??減少條件分支:避免復雜的if-else
或switch-case
邏輯。
??狀態轉換邏輯清晰:狀態轉換由狀態類自身控制,易于維護。
4.2 缺點
??類數量增加:每個狀態都需要一個單獨的類,可能導致類膨脹。
??狀態轉換邏輯分散:如果狀態轉換邏輯復雜,可能難以追蹤。
??可能引入循環依賴:狀態類可能依賴上下文類,反之亦然。
5. 狀態模式的實際應用
5.1 電商訂單系統
訂單狀態流轉:
待支付 → 已支付 → 已發貨 → 已完成
。不同狀態下,訂單的操作(如取消、退款)邏輯不同。
5.2 游戲角色狀態
角色可能有
站立
、奔跑
、跳躍
、攻擊
等狀態。不同狀態下,角色的動畫、移動速度、碰撞檢測等行為不同。
5.3 線程狀態管理
Java 線程的狀態:
NEW
、RUNNABLE
、BLOCKED
、WAITING
、TERMINATED
。不同狀態下,線程的調度策略不同。
5.4 UI 組件狀態
按鈕的狀態:
正常
、懸停
、按下
、禁用
。不同狀態下,按鈕的樣式和交互邏輯不同。
6. 狀態模式 vs. 策略模式
狀態模式和策略模式在結構上非常相似,但它們的設計意圖不同:
對比點 | 狀態模式 | 策略模式 |
---|---|---|
目的 | 讓對象在不同狀態下改變行為 | 讓客戶端選擇不同的算法 |
狀態轉換 | 狀態類可以自動切換 | 策略通常由客戶端設置 |
依賴關系 | 狀態類可能依賴上下文 | 策略類通常獨立 |
7. 總結
狀態模式是一種強大的設計模式,特別適用于對象行為隨狀態變化而變化的場景。它通過將狀態邏輯分散到不同的類中,使得代碼更加清晰、可擴展。盡管它可能增加類的數量,但在復雜狀態管理場景下,它能顯著提升代碼的可維護性。
適用場景總結:
對象有多個狀態,且行為隨狀態變化。
代碼中有大量狀態相關的條件分支。
未來可能新增狀態,需要靈活擴展。
推薦使用場景:
? 訂單狀態管理
? 游戲角色狀態切換
? 線程/進程狀態管理
? UI 組件交互狀態
?