概述
很多人在說狀態模式的時候總拿策略模式來進行對比,可能他們的類圖會有一點類似,可我卻不認爲他們有多麼相像。你可以閱讀《Java設計模式——策略模式》這篇博客,並與本文對比,以找到蛛絲馬跡。
他們最根本的差異在於策略模式是在求解同一個問題的多種解法,這些不同解法之間毫無關聯;狀態模式則不同,狀態模式要求各個狀態之間有所關聯,以便實現狀態轉移。
定義
狀態模式(State),當一個對象的內部狀態改變時允許改變其行爲,這個對象看起來像是改變了其類。
目錄
版權說明
著作權歸作者所有。
商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
本文作者:Coding-Naga
發表日期: 2016年6月6日
本文鏈接:http://blog.csdn.net/lemon_tree12138/article/details/51596556
來源:CSDN
更多內容:分類 >> 設計模式
狀態模式
我很喜歡具有狀態轉移的程序,總是感覺這裏充滿了無限的魅力。如果你也對狀態轉移的邏輯感興趣,那麼你可以閱讀一下我之前的幾篇博客。
- 算法之動態規劃初步(Java版)
- 算法:模式匹配之KMP算法
- 深入理解Aho-Corasick自動機算法
- Trie樹進階:Double-Array Trie原理及狀態轉移過程詳解
- 算法:關於生成抽樣隨機數的這些算法
情境
看《Java 設計模式》的時候,我看到一個例子,感覺很好,拿來跟大家一起分享一下。
實體是電梯,這個大家一定不陌生。我們知道電梯主要有4種狀態:電梯門關閉、電梯門打開、電梯上下運載、電梯停止。而且我們知道,電梯在門打開的時候,只能是關閉電梯門,不能是其他的任何操作。在學習狀態模式之前,如果我們要編寫這個邏輯,一定是長篇累讀地 if … else … 。而且邏輯混亂,很難維護。當然,這裏你可以使用 if … else …,因爲電梯的這些狀態基本是穩定的,不會有什麼變動。而如果你的需求裏,狀態會不斷更新,而你之前使用 if … else … 埋下的患根這時就會讓你苦不堪言。
所以,你需要重構你的代碼。
狀態模式類圖
邏輯實現
如果想要避免使用 if … else … 或是 switch … case …,那麼我們就需要對這些條件進行封裝。在學習狀態模式之前,我很喜歡使用一個 Map 來解決 switch … case … 問題,而且屢試不爽。從使用 Map 來解決 switch … case … 問題中可以知道,這裏的條件類必須去繼承一個共同的類或是共同的接口。這裏就是上面類圖中的 LiftState。
LiftState.java
public abstract class LiftState {
protected Context context;
public void setContext(Context _context) {
this.context = _context;
}
public abstract void open();
public abstract void close();
public abstract void run();
public abstract void stop();
}
可能你很奇怪,爲什麼這裏 LiftState 類裏面會有一個 Context 對象。它的作用是去調節狀態的變化,它就是電梯,你的電梯狀態肯定是針對電梯來說的,所以組合一個 Context 一點也不奇怪。
現在來看看 LiftState 的實現類吧,就拿 StoppingState 類來說吧,其他的實現跟這個類很像,就不多貼代碼了。想要詳細代碼的朋友可以去我的 GitHub 上下載。
StoppingState.java
public class StoppingState extends LiftState {
@Override
public void close() {
// do nothing;
}
@Override
public void open() {
super.context.setLiftState(Context.openningState);
super.context.getLiftState().open();
}
@Override
public void run() {
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}
@Override
public void stop() {
System.out.println("電梯停止了...");
}
}
在停下來的時候,我們不能讓電梯關閉,因爲它原本就是關閉的,我這裏做法是不處理,當然你可以選擇拋出異常。當電梯停下來的時候,電梯是可以打開的,所以在 open() 方法裏可以將電梯的狀態標識爲打開狀態;當然,也可以標識爲運載狀態。而究竟會轉換成哪一種狀態,就要依據實際乘客的使用情況了。
下面看看我們的關鍵實體 Context 是怎麼實現的。
Context.java
public class Context {
// 定義出所有的電梯狀態
public final static OpenningState openningState = new OpenningState();
public final static ClosingState closeingState = new ClosingState();
public final static RunningState runningState = new RunningState();
public final static StoppingState stoppingState = new StoppingState();
// 定一個當前電梯狀態
private LiftState liftState;
public LiftState getLiftState() {
return liftState;
}
public void setLiftState(LiftState liftState) {
this.liftState = liftState;
// 把當前的環境通知到各個實現類中
this.liftState.setContext(this);
}
public void open() {
this.liftState.open();
}
public void close() {
this.liftState.close();
}
public void run() {
this.liftState.run();
}
public void stop() {
this.liftState.stop();
}
}
Context 組合了所有狀態,這一點不奇怪,因爲它是電梯嘛。在上面的代碼中,你們可能很迷惑,這裏 Context 都是去調用 LiftState 接口的相應方法,哪裏體現了狀態的轉移呢?其實狀態轉移的邏輯是在各自的狀態裏面進行的,就像上面的 StoppingState 類。如果調用了 StoppingState 類,是不是說當前 Context 裏的狀態是 StoppingState 呢?而它卻在 open() 方法裏將 Context 的狀態轉換成了 OpenningState 。這樣就完成了狀態的轉換了。Context 類的作用我想只是去觸發狀態的轉換。
下面提供一張電梯的狀態轉移圖:
Ref
- 《Java 設計模式》
GitHub 源碼
https://github.com/William-Hai/DesignPatternCollections