前言
今天我們來看一個號稱策略模式雙胞胎的設計模式——狀態模式,如它的名字一樣,狀態模式最核心的設計思路就是將對象的狀態抽象出一個接口,然后根據它的不同狀態封裝其行為,這樣就可以實現狀態和行為的綁定,最終實現對象和狀態的有效解耦。下面我們就來詳細看下它的基本原理和實現過程吧。
狀態模式
狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類。
要點
- 狀態模式允許一個對象基於內部狀態而擁有不同的行為
- 和程序狀態機(
PSM
)不同,狀態模式用類代表狀態 Context
會將行為委托給當前狀態對象- 通過將每個狀態封裝進一個類,我們把以后需要做的任何改變局部化了
- 狀態模式和策略模式有相同的類圖,但是它們的意圖不同
- 策略模式通常會用行為或算法來配置
Context
類 - 狀態模式允許
Context
隨着狀態的改變而改變行為 - 狀態轉換可以由
State
類或Context
類控制 - 使用狀態模式通常會導致設計中類的數目大量增加
- 狀態類可以被多個
Context
示例共享
優缺點
優點
-
封裝了轉換規則。
-
枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
-
將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
-
允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊
-
可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
缺點
- 狀態模式的使用必然會增加系統類和對象的個數。
- 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
- 狀態模式對"開閉原則"的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源代碼。
使用場景
- 行為隨狀態改變而改變的場景。
- 條件、分支語句的代替者。
示例
狀態接口
首先是狀態接口,這個接口是給我們實際的狀態對象繼承的,這個接口有一個方法doAction
,這個方法就是給不同的狀態對象實現的,用於處理不同狀態下的行為的。
public interface State {
/**
* 改變狀態的操作
* @param context
*/
void doAction(Context context);
}
狀態所屬者
然后是我們的狀態所屬者,這個類有一個核心的屬性就是我們的State
接口。
public class Context {
private State state;
public Context(){}
public void setState(State state){
this.state = state;
}
public State getState(){
return state;
}
@Override
public String toString() {
return "Context{" +
"state=" + state +
'}';
}
}
狀態實現
狀態實現者繼承了State
接口,並實現了doAction
方法,在方法內部可以對我們的狀態所有者進行對應的操作。
這里是一個啟動狀態:
public class StopState implements State {
private String name;
public StopState() {
this.name = "stop";
}
@Override
public void doAction(Context context) {
System.out.println("Context is in stop state");
context.setState(this);
System.out.println(context);
}
@Override
public String toString() {
return "StopState{" +
"name='" + name + '\'' +
'}';
}
}
這里是停止狀態
public class StartState implements State{
private String name;
public StartState() {
this.name = "start";
}
@Override
public void doAction(Context context) {
System.out.println("Context is in start state");
context.setState(this);
System.out.println(context);
}
@Override
public String toString() {
return "StartState{" +
"name='" + name + '\'' +
'}';
}
}
測試代碼
這里分別實例化了容器和狀態的示例,然后通過示例的doAction
方法操作容器
@Test
public void testState() {
Context context = new Context();
StartState startState = new StartState();
startState.doAction(context);
StopState stopState = new StopState();
stopState.doAction(context);
}
運行結果
可以看到,狀態對象的doAction
方法執行后,容器對應的狀態也發生了改變:
好了,關於狀態模式就先說這么多,接下來我們做一個簡單的總結。
總結
有用過策略模式或者對策略模式比較熟悉的小伙伴應該發現了:策略模式其實和我們今天分析狀態模式特別像,甚至連架構模式都是一樣的,所以這里我們有必要說下它們的區別。
首先是策略模式,它其實是將不同的算法封裝成不同的策略,然后在具體的策略中實現具體的行為,但是測試本身是被動被選擇的,容器選擇策略,調用過程發生在容器中,而且策略本身是入參;
而我們今天分析的狀態模式,它是將不同狀態對應的行為封裝,然后由具體的狀態操作容器,整個過程更像是狀態主動發起的,由狀態執行其自己的方法,入參是容器。
這兩種設計模式從某種程度上說是可以互相替換的,但是還是要結合具體業務分析的,比如spring boot
啟動過程中,它用到的就是狀態模式,這一點我們在分析spring boot
啟動過程中也發現了;但如果是涉及到算法層面的內容,比如兩個數的加減乘除,顯然策略模式才是更好的選擇。
總之,學習設計模式除了要了解它的基本原理和應用場景之外,更重要的是,要學會辨識優秀框架中的設計模式(知識,知就是知道,了解,識局勢辨識,分析),最終將這些設計模式應用到我們的業務開發之中。