狀態模式(State Pattern)。
定義
當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。
狀態模式的核心是封裝,狀態的變更引起了行為的變更,從外部看起來就好像這個物件對應的類發生了改變一樣。
我們先來看看狀態模式中的3個角色。
- State——抽象狀態角色
介面或抽象類,負責物件狀態定義,並且封裝環境以實現狀態切換。
- ConcreteState——具體狀態角色
每一個具體狀態必須完成兩個職責:本狀態的行為管理以及趨向狀態處理,通俗的說,就是本狀態下要做的事情,以及本狀態如何過渡到其他狀態。
- Context——環境角色
定義客戶端需要的介面,並且負責具體狀態的切換。
狀態模式相對來說比較複雜,他提供了一種對物質運動的另一個觀察視角,通過狀態變更促使行為的變化,就類似水的狀態變更一樣,一碗水的初始狀態是液態,通過加熱轉變為氣態,狀態的改變同時也引起體積的擴大,然後就產生了一個新的行為:鳴笛或頂起壺蓋,瓦特就是這麼發明蒸汽機的。
通用原始碼
我們再來看看狀態模式的通用原始碼,首先來看抽象環境角色,如下所示。
public abstract class State { // 定義一個環境角色,提供子類訪問 protected Context context; /** * 設定環境角色 * * @param context */ public void setContext(Context context) { this.context = context; } /** * 行為1 */ public abstract void handle1(); /** * 行為2 */ public abstract void handle2(); }
抽象環境中宣告一個環境角色,提供各個狀態類自行訪問,並且提供所有狀態的抽象行為,由各個實現類實現。具體環境角色如下所示。
public class ConcreteState1 extends State { @Override public void handle1() { // 本狀態下必須處理的邏輯 } @Override public void handle2() { // 設定當前狀態為state2 super.context.setCurrentState(Context.STATE2); // 過濾到state2狀態,由Context實現 super.context.handle2(); } } public class ConcreteState2 extends State { @Override public void handle1() { // 設定當前狀態為state1 super.context.setCurrentState(Context.STATE1); // 過濾到state1狀態,由Context實現 super.context.handle1(); } @Override public void handle2() { // 本狀態下必須處理的邏輯 } }
具體環境角色有兩個職責:處理本狀態必須完成的任務,決定是否可以過渡到其他狀態。我們再來看環境角色,如下所示。
public class Context {
// 定義狀態
public final static State STATE1 = new ConcreteState1();
public final static State STATE2 = new ConcreteState2();
// 當前狀態
private State CurrentState;
public State getCurrentState() {
return CurrentState;
}
public void setCurrentState(State currentState) {
CurrentState = currentState;
// 切換狀態
this.CurrentState.setContext(this);
}
/**
* 行為委託
*/
public void handle1() {
this.CurrentState.handle1();
}
/**
* 行為委託
*/
public void handle2() {
this.CurrentState.handle2();
}
}
環境角色有兩個不成文的約束:
- 把狀態物件宣告為靜態常量,有幾個狀態物件就宣告幾個靜態常量。
- 環境角色具有狀態抽象角色定義的所有行為,具體執行使用委託方式。
我們再來看場景類如何執行,如下所示。
public class Client {
public static void main(String[] args) {
// 定義環境角色
Context context = new Context();
// 初始化狀態
context.setCurrentState(new ConcreteState1());
// 行為執行
context.handle1();
context.handle2();
}
}
我們已經隱藏了狀態的變化過程,他的切換引起了行為的變化。對外來說,我們只看到行為的發生改變,而不用知道是狀態變化引起的。
優點
- 結構清晰
避免了過多的switch...case或者if...else語句的使用,避免了程式的複雜性,提高系統的可維護性。
- 遵循設計原則
很好的體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。
- 封裝性非常好
這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。
缺點
只有一個缺點,子類會太多,也就是類膨脹。如果一個事物有很多個狀態也不稀奇,如果完全使用狀態模式就會有太多的子類,不好管理,這個需要大家在專案中自己衡量。其實有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行相應的操作,這個也不復雜,看大家的習慣和嗜好了。
使用場景
- 行為隨狀態改變而改變的場景
這也是狀態模式的根本出發點,例如許可權設計,人員的狀態不同即使執行相同的行為結果也會不同,在這種情況下需要考慮使用狀態模式。
- 條件、分支判斷語句的替代者
在程式中大量使用switch語句或者if判斷語句會導致程式結構不清晰,邏輯混亂,使用狀態模式可以很好的避免這一問題,他通過擴充套件子類實現了條件的判斷處理。
注意事項
狀態模式適用於當某個物件在他的狀態發生改變時,他的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時物件的狀態最好不要超過5個。
最佳實踐
狀態間的自由切換,那會有很多種,你要挨個牢記一遍嗎?比如電梯的例子,我要一個正常的電梯執行邏輯,規則是開門→關門→執行→停止;還要一個緊急狀態(如火災)下的執行邏輯,關門→停止,緊急狀態時,電梯當然不能用了;再要一個維修狀態下的執行邏輯,這個狀態任何情況都可以,開著門電梯執行?可以!門來回關?可以!永久停止不動?可以!那這怎麼實現呢?需要我們把已經有的幾種狀態按照一定的順序再重新組裝一下,那這個是什麼模式?建造者模式,對建造模式+狀態模式會起打非常好的封裝作用。
更進一步,應該有做過工作流開發,如果不是土製框架,那麼就應該有個狀態機管理(即使是土製框架也應該有),如一個Activity(節點)有初始化狀態(Initialized State)、掛起狀態(Suspended State)、完成狀態(Completed State)等,流程例項也有這麼多狀態,那這些狀態怎麼管理呢?通過狀態機(State Machine)來管理,那狀態機是什麼東西呢?就是我們上面提到的Context類的升級變態BOSS!