設計模式之狀態模式

語言: CN / TW / HK

實際開發中訂單往往都包含著訂單狀態,使用者每進行一次操作都要切換對應的狀態,而每次切換判斷當前的狀態是必須的,就不可避免的引入一系列判斷語句,為了讓程式碼更加清晰直觀,我們引入今天的主角——狀態模式。

一、概念理解

假設訂單狀態有,下單、發貨、確認收貨,如果使用者確認收貨,在常規程式設計中就要判斷當前使用者的狀態,然後再修改狀態,如果這種情況下使用狀態模式。

將各個狀態都抽象成一個狀態類,比如下單狀態類、發貨狀態類、確認收貨類,在狀態類中處理相應的邏輯和控制下一個狀態,在定義一個環境類,定義初始狀態,並控制切換狀態。

在狀態模式中應該包含著三個角色:

環境類(Context)角色:也稱為上下文,它定義了客戶端需要的介面,內部維護一個當前狀態,這個類持有State介面,負責保持並切換當前的狀態。

抽象狀態(State)角色:定義一個介面,用以封裝環境物件中的特定狀態所對應的行為,可以有一個或多個行為。

具體狀態(Concrete State)角色:實現抽象狀態所對應的行為,並且在需要的情況下進行狀態切換。

以下為狀態模式的類圖,看起來是很直觀的,理解起來也簡單,我們需要說明的是狀態模式的類圖和策略模式的類圖長的一樣,但寫起來狀態模式比策略模式要難。

我們要注意這段話,在狀態模式中,類的行為是基於它的狀態改變的,狀態之間的切換,在狀態A執行完畢後自己控制狀態指向狀態B,狀態模式是不停的切換狀態執行。這也是狀態模式和策略模式不一樣的地方。

另外在狀態模式中,狀態A到B是由自己控制的,而不是由客戶端來控制,這是狀態模式和策略模式最顯著的特徵。

我們基於訂單狀態案例實現demo。

二、案例實現

抽象狀態:

定義統一的狀態切換方法

/**
 * 抽象狀態
 * @author tcy
 * @Date 20-09-2022
 */
public abstract class OrderStateAbstract {
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }
        /**
         * 狀態切換
         */
    public abstract void handle();

}

具體狀態-訂單付款:

實現狀態介面,處理相應的邏輯,並定義下一個狀態

/**
 * 訂單付款
 * @author tcy
 * @Date 21-09-2022
 */
public class OrderStatePay extends OrderStateAbstract {
   @Override
    public void handle() {
        System.out.println("訂單已支付,執行下個狀態...");
        context.changeState(new OrderStateOut());

    }
}

具體狀態-訂單發貨

/**
 * 訂單發貨
 * @author tcy
 * @Date 21-09-2022
 */
public class OrderStateOut extends OrderStateAbstract {
     @Override
    public void handle() {
        System.out.println("訂單已經發貨,開始下一狀態...");
        context.changeState(new OrderStateSubmit());
    }
}

具體狀態-訂單確認收貨

/**
 * 訂單提交
 * @author tcy
 * @Date 21-09-2022
 */
public class OrderStateSubmit extends OrderStateAbstract {
    @Override
    public void handle() {
        System.out.println("訂單已經確認收貨...");
    }
}

環境類:

持有最新狀態,並呼叫具體的狀態切換方法

/**
 * 環境類
 * @author tcy
 * @Date 20-09-2022
 */
public class Context {

   private OrderStateAbstract state;

    //定義環境類的初始狀態
    public Context() {
        this.state = new OrderStatePay();
        state.setContext(this);
    }

	//狀態切換
    public void changeState(OrderStateAbstract state) {
        this.state = state;
        this.state.setContext(this);
    }


    /**
     * 審批通過請求
     */
    public void request() {
        this.state.handle();
    }
}

客戶端呼叫:

/**
 * @author tcy
 * @Date 20-09-2022
 */
public class Client {
    public static void main(String[] args) {

         //建立環境
        Context context = new Context();
        //訂單付款
        context.request();
        //訂單發貨
        context.request();
        //訂單付款
        context.request();

    }
}

狀態模式客戶端呼叫比較簡單,由狀態內部類進行狀態切換。

三、總結

很多部落格都將策略模式的案例程式碼當做狀態模式來講解,這是不正確的,讀者可以參考 策略模式 兩篇做對比學習,認真體會他們之間的區別。

在實際開發中,當控制一個物件狀態轉換的條件表示式過於複雜時,就可以使用狀態模式把相關“判斷邏輯”提取出來,用各個不同的類進行表示。

系統處於哪種情況,直接使用相應的狀態類物件進行處理,這樣能把原來複雜的邏輯判斷簡單化,消除了 if-else、switch-case 等冗餘語句,程式碼更有層次性,並且具備良好的擴充套件力。

比如審批流程,我們案例也僅僅是用於訂單流程做例子,在實際開發中並不會使用這種方式處理訂單,因為訂單的處理邏輯實際上並不是那麼複雜,引入狀態模式反而增加了更多的類,造成系統更加的複雜,這也是設計模式最顯著的缺點。

設計模式的學習要成體系,推薦你看我往期釋出的設計模式文章。

一、設計模式概述

二、設計模式之工廠方法和抽象工廠

三、設計模式之單例和原型

四、設計模式之建造者模式

五、設計模式之代理模式

六、設計模式之介面卡模式

七、設計模式之橋接模式

八、設計模式之組合模式

九、設計模式之裝飾器模式

十、設計模式之外觀模式

十一、外觀模式之享元模式

十二、設計模式之責任鏈模式

十三、設計模式之命令模式

十四、設計模式之直譯器模式

十五、設計模式之迭代器模式

十六、設計模式之中介者模式

十七、設計模式之備忘錄模式

十八、設計模式之觀察者模式