狀態模式基礎概念
狀態模式(State Pattern)是一種行為型設計模式,其核心思想是允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類。狀態模式將狀態相關的行為封裝在獨立的狀態類中,并將狀態轉換邏輯集中管理,從而使對象的行為可以根據狀態動態變化,而不必使用大量的條件語句。
狀態模式的核心組件
- 狀態接口(State)?- 定義特定狀態下的行為接口,所有具體狀態類需實現該接口。
- 具體狀態類(ConcreteState)?- 實現狀態接口,封裝與特定狀態相關的行為。
- 上下文(Context)?- 持有一個狀態對象的引用,負責狀態的切換和委托行為到當前狀態。
狀態模式的實現
下面通過一個簡單的自動販賣機示例展示狀態模式的實現:
// 1. 狀態接口
interface State {void insertCoin(); // 投幣void ejectCoin(); // 退幣void selectItem(); // 選擇商品void dispense(); // 發放商品
}// 2. 具體狀態類 - 沒有硬幣
class NoCoinState implements State {private VendingMachine machine; // 持有上下文引用public NoCoinState(VendingMachine machine) {this.machine = machine;}@Overridepublic void insertCoin() {System.out.println("您投入了硬幣");machine.setState(machine.getHasCoinState()); // 狀態轉換}@Overridepublic void ejectCoin() {System.out.println("您還沒有投幣");}@Overridepublic void selectItem() {System.out.println("請先投幣");}@Overridepublic void dispense() {System.out.println("請先投幣并選擇商品");}
}// 3. 具體狀態類 - 有硬幣
class HasCoinState implements State {private VendingMachine machine;public HasCoinState(VendingMachine machine) {this.machine = machine;}@Overridepublic void insertCoin() {System.out.println("您已經投過硬幣了,無需再投");}@Overridepublic void ejectCoin() {System.out.println("硬幣已退回");machine.setState(machine.getNoCoinState()); // 狀態轉換}@Overridepublic void selectItem() {System.out.println("您選擇了商品");machine.setState(machine.getSoldState()); // 狀態轉換}@Overridepublic void dispense() {System.out.println("請先選擇商品");}
}// 4. 具體狀態類 - 已售出
class SoldState implements State {private VendingMachine machine;public SoldState(VendingMachine machine) {this.machine = machine;}@Overridepublic void insertCoin() {System.out.println("請稍等,正在出貨");}@Overridepublic void ejectCoin() {System.out.println("抱歉,您已經選擇了商品,無法退幣");}@Overridepublic void selectItem() {System.out.println("請稍等,正在出貨");}@Overridepublic void dispense() {if (machine.getCount() > 0) {machine.releaseItem();System.out.println("商品已發放");if (machine.getCount() > 0) {machine.setState(machine.getNoCoinState()); // 還有庫存,回到無硬幣狀態} else {System.out.println("商品已售罄");machine.setState(machine.getSoldOutState()); // 庫存不足,轉為售罄狀態}} else {System.out.println("商品已售罄");machine.setState(machine.getSoldOutState());}}
}// 5. 具體狀態類 - 售罄
class SoldOutState implements State {private VendingMachine machine;public SoldOutState(VendingMachine machine) {this.machine = machine;}@Overridepublic void insertCoin() {System.out.println("抱歉,商品已售罄");}@Overridepublic void ejectCoin() {System.out.println("您還沒有投幣");}@Overridepublic void selectItem() {System.out.println("抱歉,商品已售罄");}@Overridepublic void dispense() {System.out.println("抱歉,商品已售罄");}
}// 6. 上下文 - 自動販賣機
class VendingMachine {private State noCoinState;private State hasCoinState;private State soldState;private State soldOutState;private State state; // 當前狀態private int count; // 商品數量public VendingMachine(int count) {this.count = count;// 初始化狀態noCoinState = new NoCoinState(this);hasCoinState = new HasCoinState(this);soldState = new SoldState(this);soldOutState = new SoldOutState(this);// 設置初始狀態if (count > 0) {state = noCoinState;} else {state = soldOutState;}}// 狀態轉換方法public void setState(State state) {this.state = state;}// 委托行為到當前狀態public void insertCoin() {state.insertCoin();}public void ejectCoin() {state.ejectCoin();}public void selectItem() {state.selectItem();}public void dispense() {state.dispense();}// 其他方法public void releaseItem() {if (count > 0) {count--;}}public int getCount() {return count;}// 獲取各種狀態(供狀態類使用)public State getNoCoinState() {return noCoinState;}public State getHasCoinState() {return hasCoinState;}public State getSoldState() {return soldState;}public State getSoldOutState() {return soldOutState;}
}// 7. 客戶端代碼
public class StatePatternClient {public static void main(String[] args) {// 創建有2個商品的販賣機VendingMachine machine = new VendingMachine(2);// 測試流程1:投幣 -> 選擇商品 -> 出貨System.out.println("=== 測試流程1 ===");machine.insertCoin();machine.selectItem();machine.dispense();// 測試流程2:投幣 -> 退幣System.out.println("\n=== 測試流程2 ===");machine.insertCoin();machine.ejectCoin();// 測試流程3:連續購買2個商品,使販賣機售罄System.out.println("\n=== 測試流程3 ===");machine.insertCoin();machine.selectItem();machine.dispense();machine.insertCoin();machine.selectItem();machine.dispense();// 嘗試在售罄狀態下操作System.out.println("\n=== 測試售罄狀態 ===");machine.insertCoin();machine.selectItem();}
}
狀態模式的應用場景
- 工作流系統?- 如訂單狀態(待支付、已支付、已發貨、已完成)
- 游戲開發?- 角色狀態(站立、行走、跑步、跳躍、攻擊)
- 狀態機實現?- 如協議解析、編譯原理中的詞法分析器
- GUI 組件?- 如按鈕狀態(正常、懸停、點擊、禁用)
- 線程狀態管理?- 線程的不同狀態(新建、就緒、運行、阻塞、終止)
- 文檔編輯系統?- 文檔狀態(草稿、審核中、已發布、已歸檔)
狀態模式的優缺點
優點:
- 封裝狀態行為?- 將特定狀態的行為封裝在獨立的類中,提高代碼內聚性
- 簡化條件語句?- 避免使用大量的 if-else 或 switch 語句,使代碼更清晰
- 符合開閉原則?- 可以輕松添加新的狀態類,無需修改現有代碼
- 狀態轉換集中管理?- 狀態轉換邏輯集中在上下文或狀態類中,便于維護
- 狀態變化透明?- 狀態變化對客戶端透明,客戶端無需關心狀態轉換細節
缺點:
- 類數量增加?- 每個狀態都需要一個類,可能導致類爆炸
- 狀態間依賴?- 狀態類之間可能存在依賴關系,增加系統復雜度
- 初始化復雜?- 上下文需要初始化所有可能的狀態,增加初始化復雜度
- 不適用于簡單狀態?- 對于狀態較少或狀態轉換簡單的場景,使用狀態模式可能過于繁瑣
使用狀態模式的注意事項
- 合理設計狀態接口?- 確保狀態接口包含所有可能的行為,避免狀態類中出現空實現
- 控制狀態數量?- 避免定義過多的狀態,狀態過多會增加系統復雜度
- 狀態轉換邏輯?- 決定狀態轉換邏輯放在上下文還是狀態類中:
- 上下文控制:集中管理狀態轉換,狀態類更簡單
- 狀態類控制:狀態類可以自主決定何時轉換,更靈活
- 狀態對象的生命周期?- 決定狀態對象是單例還是每次創建新實例
- 與策略模式的區別?- 狀態模式的狀態之間通常有關聯和轉換,而策略模式的策略之間相互獨立
- 調試和監控?- 狀態模式可能使調試變得復雜,建議添加狀態日志或監控機制
總結
狀態模式通過將對象的狀態相關行為封裝在獨立的狀態類中,并將狀態轉換邏輯集中管理,實現了對象行為的動態變化。它在不修改對象類的前提下,允許對象在內部狀態改變時改變其行為,是處理復雜狀態轉換場景的理想選擇。在實際開發中,狀態模式常用于工作流系統、游戲開發、狀態機實現等領域。合理使用狀態模式可以提高系統的可維護性和可擴展性,但需要注意控制類的數量和狀態轉換邏輯的復雜度。