一、狀態模式的定義與特點
應用程序中的有些對象可能會根據不同的情況做出不同的行為,我們把這種對象稱為有狀態的對象,而把影響對象行為的一個或多個動態變化的屬性稱為狀態。當有狀態的對象與外部事件產生互動時,其內部狀態會發生改變,從而使得其行為也隨之發生改變。
狀態(State)模式的定義:對有狀態的對象,把復雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。
狀態模式的解決思想是:當控制一個對象狀態轉換的條件表達式過於復雜時,把相關“判斷邏輯”提取出來,放到一系列的狀態類當中,這樣可以把原來復雜的邏輯判斷簡單化。
二、狀態模式優缺點
狀態模式是一種對象行為型模式,其主要優點如下:
- 狀態模式將與特定狀態相關的行為局部化到一個狀態中,並且將不同狀態的行為分割開來,滿足“單一職責原則”。
- 減少對象間的相互依賴。將不同的狀態引入獨立的對象中會使得狀態轉換變得更加明確,且減少對象間的相互依賴。
- 有利於程序的擴展。通過定義新的子類很容易地增加新的狀態和轉換。
狀態模式的主要缺點如下:
- 狀態模式的使用必然會增加系統的類與對象的個數。
- 狀態模式的結構與實現都較為復雜,如果使用不當會導致程序結構和代碼的混亂。
三、狀態模式的實現
狀態模式把受環境改變的對象行為包裝在不同的狀態對象里,其意圖是讓一個對象在其內部狀態改變的時候,其行為也隨之改變。現在我們來分析其基本結構和實現方法。
狀態模式包含以下主要角色:
- 環境(Context)角色:也稱為上下文,它定義了客戶感興趣的接口,維護一個當前狀態,並將與狀態相關的操作委托給當前狀態對象來處理。
- 抽象狀態(State)角色:定義一個接口,用以封裝環境對象中的特定狀態所對應的行為。
- 具體狀態(Concrete State)角色:實現抽象狀態所對應的行為。
其結構圖如圖所示:
代碼實現如下:
public class StatePatternClient { public static void main(String[] args) { Context context=new Context(); //創建環境 context.Handle(); //處理請求 context.Handle(); context.Handle(); context.Handle(); } } //環境類 class Context { private State state; //定義環境類的初始狀態 public Context() { this.state=new ConcreteStateA(); } //設置新狀態 public void setState(State state) { this.state=state; } //讀取狀態 public State getState() { return(state); } //對請求做處理 public void Handle() { state.Handle(this); } } //抽象狀態類 abstract class State { public abstract void Handle(Context context); } //具體狀態A類 class ConcreteStateA extends State { public void Handle(Context context) { System.out.println("當前狀態是 A."); context.setState(new ConcreteStateB()); } } //具體狀態B類 class ConcreteStateB extends State { public void Handle(Context context) { System.out.println("當前狀態是 B."); context.setState(new ConcreteStateA()); } }
運行結果如下:
當前狀態是 A.
當前狀態是 B.
當前狀態是 A.
當前狀態是 B.
四、狀態模式的應用實例
用“狀態模式”設計一個學生成績的狀態轉換程序:多線程存在 5 種狀態,分別為新建狀態、就緒狀態、運行狀態、阻塞狀態和死亡狀態,各個狀態當遇到相關方法調用或事件觸發時會轉換到其他狀態,其狀態轉換規律如圖所示:
現在先定義一個抽象狀態類(TheadState),然后為上圖所示的每個狀態設計一個具體狀態類,它們是新建狀態(New)、就緒狀態(Runnable )、運行狀態(Running)、阻塞狀態(Blocked)和死亡狀態(Dead),每個狀態中有觸發它們轉變狀態的方法,環境類(ThreadContext)中先生成一個初始狀態(New),並提供相關觸發方法,線程狀態轉換程序的結構圖如下圖所示:
代碼如下:
public class ThreadStateTest { public static void main(String[] args) { ThreadContext context=new ThreadContext(); context.start(); context.getCPU(); context.suspend(); context.resume(); context.getCPU(); context.stop(); } } //環境類 class ThreadContext { private ThreadState state; ThreadContext() { state=new New(); } public void setState(ThreadState state) { this.state=state; } public ThreadState getState() { return state; } public void start() { ((New) state).start(this); } public void getCPU() { ((Runnable) state).getCPU(this); } public void suspend() { ((Running) state).suspend(this); } public void stop() { ((Running) state).stop(this); } public void resume() { ((Blocked) state).resume(this); } } //抽象狀態類:線程狀態 abstract class ThreadState { protected String stateName; //狀態名 } //具體狀態類:新建狀態 class New extends ThreadState { public New() { stateName="新建狀態"; System.out.println("當前線程處於:新建狀態."); } public void start(ThreadContext hj) { System.out.print("調用start()方法-->"); if(stateName.equals("新建狀態")) { hj.setState(new Runnable()); } else { System.out.println("當前線程不是新建狀態,不能調用start()方法."); } } } //具體狀態類:就緒狀態 class Runnable extends ThreadState { public Runnable() { stateName="就緒狀態"; System.out.println("當前線程處於:就緒狀態."); } public void getCPU(ThreadContext hj) { System.out.print("獲得CPU時間-->"); if(stateName.equals("就緒狀態")) { hj.setState(new Running()); } else { System.out.println("當前線程不是就緒狀態,不能獲取CPU."); } } } //具體狀態類:運行狀態 class Running extends ThreadState { public Running() { stateName="運行狀態"; System.out.println("當前線程處於:運行狀態."); } public void suspend(ThreadContext hj) { System.out.print("調用suspend()方法-->"); if(stateName.equals("運行狀態")) { hj.setState(new Blocked()); } else { System.out.println("當前線程不是運行狀態,不能調用suspend()方法."); } } public void stop(ThreadContext hj) { System.out.print("調用stop()方法-->"); if(stateName.equals("運行狀態")) { hj.setState(new Dead()); } else { System.out.println("當前線程不是運行狀態,不能調用stop()方法."); } } } //具體狀態類:阻塞狀態 class Blocked extends ThreadState { public Blocked() { stateName="阻塞狀態"; System.out.println("當前線程處於:阻塞狀態."); } public void resume(ThreadContext hj) { System.out.print("調用resume()方法-->"); if(stateName.equals("阻塞狀態")) { hj.setState(new Runnable()); } else { System.out.println("當前線程不是阻塞狀態,不能調用resume()方法."); } } } //具體狀態類:死亡狀態 class Dead extends ThreadState { public Dead() { stateName="死亡狀態"; System.out.println("當前線程處於:死亡狀態."); } }
測試結果如下:
當前線程處於:新建狀態. 調用start()方法-->當前線程處於:就緒狀態. 獲得CPU時間-->當前線程處於:運行狀態. 調用suspend()方法-->當前線程處於:阻塞狀態. 調用resume()方法-->當前線程處於:就緒狀態. 獲得CPU時間-->當前線程處於:運行狀態. 調用stop()方法-->當前線程處於:死亡狀態.
五、狀態模式的應用場景
通常在以下情況下可以考慮使用狀態模式。
- 當一個對象的行為取決於它的狀態,並且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式。
- 一個操作中含有龐大的分支結構,並且這些分支決定於對象的狀態時。
六、狀態模式的擴展
在有些情況下,可能有多個環境對象需要共享一組狀態,這時需要引入享元模式,將這些具體狀態對象放在集合中供程序共享,其結構圖如圖所示:
共享狀態模式的不同之處是在環境類中增加了一個 HashMap 來保存相關狀態,當需要某種狀態時可以從中獲取,其程序代碼如下:
public class FlyweightStatePattern { public static void main(String[] args) { ShareContext context=new ShareContext(); //創建環境 context.Handle(); //處理請求 context.Handle(); context.Handle(); context.Handle(); } } //環境類 class ShareContext { private ShareState state; private HashMap<String, ShareState> stateSet=new HashMap<String, ShareState>(); public ShareContext() { state=new ConcreteState1(); stateSet.put("1", state); state=new ConcreteState2(); stateSet.put("2", state); state=getState("1"); } //設置新狀態 public void setState(ShareState state) { this.state=state; } //讀取狀態 public ShareState getState(String key) { ShareState s=(ShareState)stateSet.get(key); return s; } //對請求做處理 public void Handle() { state.Handle(this); } } //抽象狀態類 abstract class ShareState { public abstract void Handle(ShareContext context); } //具體狀態1類 class ConcreteState1 extends ShareState { public void Handle(ShareContext context) { System.out.println("當前狀態是: 狀態1"); context.setState(context.getState("2")); } } //具體狀態2類 class ConcreteState2 extends ShareState { public void Handle(ShareContext context) { System.out.println("當前狀態是: 狀態2"); context.setState(context.getState("1")); } }
執行結果如下:
當前狀態是: 狀態1
當前狀態是: 狀態2
當前狀態是: 狀態1
當前狀態是: 狀態2