初識設計模式 – 狀態模式

簡介

狀態模式(State Design Pattern)的定義是,允許一個對象在內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。

在狀態模式中,通常有兩種方式實現狀態轉換:統一由環境類來負責狀態之間的轉換;由具體狀態類來負責狀態之間的轉換。

狀態機

概念

狀態模式一般用於實現狀態機,而狀態機常用在遊戲、工作流引擎等系統開發中。狀態機的實現方式有多種,除了狀態模式,比較常用的還有分支邏輯法和查表法。

狀態機會有 3 個組成部分:狀態(State)、事件(Event)、動作(Action)。

拿「超級馬里奧」遊戲來舉例,其中馬里奧形態的轉變就是一個狀態機:初始狀態是小馬里奧,吃蘑菇這個事件會觸髮狀態的轉移,從小馬里奧轉變成超級馬里奧,以及觸發動作的執行(增加積分)。

分支邏輯法

最簡單的狀態機實現方式就是分支邏輯法,其理解非常簡單,就是將每一個狀態轉移都直譯成程式碼。

其缺點是,程式碼中會充斥著 if-else 或 switch 分支判斷邏輯,甚至是嵌套的分支判斷邏輯,當狀態較多時,程式碼的可讀性會比較低。

查表法

查表法的實現邏輯是,將狀態、事件和動作三者存儲到一個二維表中,這樣可以清晰地表示,一個動作發生某個事件時,會轉移到怎樣的狀態以及觸發怎樣的動作。

狀態機查表法二維表

在實現過程中,將二維表的數據存儲到配置文件中,可以通過動態地修改配置文件以達到修改狀態機的目的。

具體實現

仍然還是拿「超級馬里奧」遊戲來舉例說明,初始狀態是小馬里奧,吃蘑菇這個事件會觸髮狀態的轉移,從小馬里奧轉變成超級馬里奧,以及觸發動作的執行(增加積分)。

首先,定義一個抽象狀態 State 介面,其程式碼示例如下:

public interface State {
    // 聲明抽象業務方法,不同的具體狀態可以有不同的方法實現
    void handle();
}

對於小馬里奧狀態,定義一個實現 State 介面的 SmallState 類,其程式碼示例如下:

public class SmallState implements State {
    @Override
    public void handle() {
        // 業務方法的具體實現
        System.out.println("變成小馬里奧狀態");
    }
}

對於超級馬里奧狀態,定義一個實現 State 介面的 SuperState 類,其程式碼示例如下:

public class LargeState implements State {
    @Override
    public void handle() {
        // 業務方法的具體實現
        System.out.println("變成超級馬里奧狀態");
    }
}

在狀態模式中,需要創建一個 Context 類用於保存對於一個具體狀態對象的引用,並且負責狀態的保持和轉變。其程式碼示例如下:

public class Context {
    private State state;

    public void setState(State state) {
        // 注入狀態對象
        this.state = state;
    }

    public void request() {
        // 調用狀態對象的業務方法
        this.state.handle();
    }
}

對於客戶端,直接操作 Context 對象並根據狀態的轉變傳入不同的狀態對象,這樣即可實現狀態機的功能,其程式碼示例如下:

class StateDemo {
    public static void main(String[] args) {
        Context context = new Context();

        State smallState = new SmallState();
        context.setState(smallState);
        // 變成小馬里奧狀態
        context.request();

        State largeState = new LargeState();
        context.setState(largeState);
        // 變成超級馬里奧狀態
        context.request();
    }
}

總結

優點

狀態模式的主要優點如下:

  • 狀態模式統一封裝了狀態的轉換規則,對狀態轉換程式碼進行集中管理
  • 將不同的狀態引入獨立的對象中使得狀態轉換變得更加明確,且減少對象間的相互依賴
  • 狀態的職責分明,通過定義新的子類可以很容易地增加新的狀態和轉換

缺點

狀態模式的主要缺點如下:

  • 每個狀態都會新增一個具體的狀態子類,導致系統的運行開銷增大
  • 狀態模式的結構和實現都較為複雜,使用不當會導致程式結構和程式碼的混亂
  • 對於可以切換的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源碼,否則無法切換到新增的狀態,而且修改某個狀態類的行為也要修改對應類的源碼

適用場景

狀態模式的適用場景如下:

  • 對象的行為依賴於它的狀態,狀態的改變將導致行為的變化
  • 在程式碼中包括大量與對象狀態有關的條件語句