1. 程式人生 > >【趣味設計模式系列】之【狀態模式】

【趣味設計模式系列】之【狀態模式】

1. 簡介

狀態模式(State Pattern),當一個物件內在狀態改變時允許其改變行為,這個物件看起來像改變了其類。簡而言之,就是狀態的變更引起了行為的變更

2. 圖解

下圖四輛汽車,分別代表汽車日常的四種狀態。
開門狀態:

關門狀態:

飛奔狀態:

停止狀態:

其中,某種特定狀態下,都有四個可執行操作,分別是open,close,run,stop,然後做對應的處理得下圖所示。

3. 案例實現

類圖如下:

  • 定義汽車抽象狀態類CarState,持有型別為Context的屬性,同時持有四個可執行操作,opencloserunstop方法;
  • 定義汽車抽象狀態類的子類OpenningState
    ClosingStateRunningStateStoppingState,分別代表開門狀態,關門狀態,飛奔狀態,停止狀態;
  • 定義環境角色類Context,把狀態物件宣告為靜態常量,有幾個狀態物件就宣告幾個靜態常量,環境角色具有狀態抽象角色定義的所有行為,具體執行使用委託方式。具體環境角色有兩個職責:處理本狀態必須完成的任務,決定是否可以過渡到其他狀態。

程式碼實現如下:

package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 20:10
 * @Desc: 汽車狀態抽象類
 */
public abstract class CarState {
    //環境角色,封裝狀態變化引起的行為變化
    protected Context context;

    public void setContext(Context context) {
        this.context = context;
    }

    //汽車開門動作
    public abstract void open();

    //汽車關門動作
    public abstract void close();

    //汽車飛奔動作
    public abstract void run();

    //汽車停止動作
    public abstract void stop();

}
package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 20:23
 * @Desc: 汽車開門狀態類
 */
public class OpenningState extends CarState {

    //開啟汽車門
    public void open() {
        System.out.println("汽車門已開");
    }

    //關閉汽車門
    public void close() {
        //狀態修改
        super.context.setCarState(Context.closingState);
        //動作委託為ClosingState來執行
        super.context.getCarState().close();
    }

    //門開著時汽車一般不奔跑
    public void run() {
        System.out.println("汽車開門狀態,不能奔跑");
    }

    //車門開著時,切換不到停止狀態,因為沒有四種狀態中,沒有開門且停止這個狀態
    public void stop() {
        System.out.println("汽車開門狀態,不能長時間開著門且處於停止狀態");

    }
}
package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 20:23
 * @Desc: 汽車飛奔狀態類
 */
public class RunningState extends CarState {

    //開啟奔跑時不開門
    public void open() {
        System.out.println("車在飛奔,不能開啟");
    }

    //奔跑時肯定是關門的
    public void close() {
        System.out.println("車在飛奔,已經關閉,不能再次關閉");
    }

    //汽車在飛奔
    public void run() {
        System.out.println("汽車在飛奔");
    }

    //汽車可以停下來
    public void stop() {
        //修改汽車為停止狀態
        super.context.setCarState(Context.stoppingState);
        //停止動作委託為StoppingState類來執行
        super.context.getCarState().stop();
    }
}
package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 20:23
 * @Desc: 汽車關門狀態類
 */
public class ClosingState extends CarState {

    //開啟汽車門
    public void open() {
        //修改汽車為開門狀態
        super.context.setCarState(Context.openningState);
        //動作委託為OpenningState類來執行
        super.context.getCarState().open();
    }

    //關閉汽車門
    public void close() {
        System.out.println("汽車門已關");
    }

    //汽車在飛奔
    public void run() {
        //修改汽車為飛奔狀態
        super.context.setCarState(Context.runningState);
        //動作委託為RunningState類來執行
        super.context.getCarState().run();

    }

    //汽車在停止
    public void stop() {
        //設定汽車狀態為停止狀態
        super.context.setCarState(Context.stoppingState);
        //動作委託為StoppingState類來執行
        super.context.getCarState().stop();

    }
}
package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 20:19
 * @Desc: 上下文環境類
 */
public class Context {

    /**列出汽車所有狀態
     * openningState-開門狀態 closingState-關門狀態
     * runningState-賓士狀態 stoppingState-停止狀態
     */
    public static final OpenningState openningState = new OpenningState();
    public static final ClosingState closingState = new ClosingState();
    public static final RunningState runningState = new RunningState();
    public static final StoppingState stoppingState = new StoppingState();

    //定義汽車當前狀態
    private CarState carState;

    public CarState getCarState() {
        return  carState;
    }

    public void setCarState(CarState carState) {
        this.carState = carState;
        //切換狀態
        this.carState.setContext(this);
    }

    //汽車開門
    public void open() {
        this.carState.open();
    }

    //汽車關門
    public void close(){
        this.carState.close();
    }

    //汽車飛奔
    public void run(){
        this.carState.run();
    }

    //汽車停止
    public void stop(){
        this.carState.stop();
    }

}

客戶端類如下:

package com.wzj.state.example1;

/**
 * @Author: wzj
 * @Date: 2019/11/3 21:06
 * @Desc:
 */
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setCarState(new OpenningState());
//        context.setCarState(new ClosingState());
//        context.setCarState(new RunningState());
//        context.setCarState(new StoppingState());
        context.open();
//        context.close();
//        context.run();
//        context.stop();
    }
}

執行結果如下:
當只打開Client15行的時候,分別開啟11,12,13,14行的程式碼,會得到如下結果:
汽車為開門狀態時,執行open

汽車為關門狀態時,執行open

汽車為飛奔狀態時,執行open

汽車為停止狀態時,執行open

上述結果可以看出,同樣執行一個open方法,當狀態的變化時導致行為的變化。

4. 狀態模式總結

優點

  • 結構清晰
    避免了過多的switch...case或者if...else語句的使用,避免了程式的複雜性,提高系統的可維護性;
  • 遵循設計原則
    很好地體現了開閉原則和單一職責原則,每個狀態都是一個子類,你要增加狀態就要增加子類,你要修改狀態,你只修改一個子類就可以了。
  • 封裝性非常好
    這也是狀態模式的基本要求,狀態變換放置到類的內部來實現,外部的呼叫不用知道類內部如何實現狀態和行為的變換。
    缺點
    狀態模式既然有優點,那當然有缺點了。但只有一個缺點,子類會太多,也就是類膨脹。如果一個事物有很多個狀態也不稀奇,如果完全使用狀態模式就會有太多的子類,不好管理,這個需要大家在專案中自己衡量。其實有很多方式可以解決這個狀態問題,如在資料庫中建立一個狀態表,然後根據狀態執行相應的操作,這個也不復雜,看大家的習慣和嗜好了。