1. 程式人生 > 其它 >設計模式詳解——狀態模式

設計模式詳解——狀態模式

前言

今天我們來看一個號稱策略模式雙胞胎的設計模式——狀態模式,如它的名字一樣,狀態模式最核心的設計思路就是將物件的狀態抽象出一個介面,然後根據它的不同狀態封裝其行為,這樣就可以實現狀態和行為的繫結,最終實現物件和狀態的有效解耦。下面我們就來詳細看下它的基本原理和實現過程吧。

狀態模式

狀態模式允許物件在內部狀態改變時改變它的行為,物件看起來好像修改了它的類。

要點

  • 狀態模式允許一個物件基於內部狀態而擁有不同的行為
  • 和程式狀態機(PSM)不同,狀態模式用類代表狀態
  • Context會將行為委託給當前狀態物件
  • 通過將每個狀態封裝進一個類,我們把以後需要做的任何改變區域性化了
  • 狀態模式和策略模式有相同的類圖,但是它們的意圖不同
  • 策略模式通常會用行為或演算法來配置Context
  • 狀態模式允許Context隨著狀態的改變而改變行為
  • 狀態轉換可以由State類或Context類控制
  • 使用狀態模式通常會導致設計中類的數目大量增加
  • 狀態類可以被多個Context示例共享

優缺點

優點
  1. 封裝了轉換規則。

  2. 列舉可能的狀態,在列舉狀態之前需要確定狀態種類。

  3. 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為。

  4. 允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件語句塊

  5. 可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。

缺點
  1. 狀態模式的使用必然會增加系統類和物件的個數。
  2. 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂。
  3. 狀態模式對"開閉原則"的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的原始碼。

使用場景

  1. 行為隨狀態改變而改變的場景。
  2. 條件、分支語句的代替者。

示例

狀態介面

首先是狀態介面,這個介面是給我們實際的狀態物件繼承的,這個介面有一個方法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啟動過程中也發現了;但如果是涉及到演算法層面的內容,比如兩個數的加減乘除,顯然策略模式才是更好的選擇。

總之,學習設計模式除了要了解它的基本原理和應用場景之外,更重要的是,要學會辨識優秀框架中的設計模式(知識,知就是知道,瞭解,識局勢辨識,分析),最終將這些設計模式應用到我們的業務開發之中。