Stories

Detail Return Return

【設計模式】探索狀態模式在現代軟件開發中的應用 - Stories Detail

概述

【例】通過按鈕來控制一個電梯的狀態,一個電梯有開門狀態,關門狀態,停止狀態,運行狀態。每一種狀態改變,都有可能要根據其他狀態來更新處理。例如,如果電梯門現在處於運行時狀態,就不能進行開門操作,而如果電梯門是停止狀態,就可以執行開門操作。

類圖如下:

代碼如下:

public interface ILift {
    //電梯的4個狀態
    //開門狀態
    public final static int OPENING_STATE = 1;
    //關門狀態
    public final static int CLOSING_STATE = 2;
    //運行狀態
    public final static int RUNNING_STATE = 3;
    //停止狀態
    public final static int STOPPING_STATE = 4;

    //設置電梯的狀態
    public void setState(int state);

    //電梯的動作
    public void open();
    public void close();
    public void run();
    public void stop();
}

public class Lift implements ILift {
    private int state;

    @Override
    public void setState(int state) {
        this.state = state;
    }

    //執行關門動作
    @Override
    public void close() {
        switch (this.state) {
            case OPENING_STATE:
                System.out.println("電梯關門了。。。");//只有開門狀態可以關閉電梯門,可以對應電梯狀態表來看
                this.setState(CLOSING_STATE);//關門之後電梯就是關閉狀態了
                break;
            case CLOSING_STATE:
                //do nothing //已經是關門狀態,不能關門
                break;
            case RUNNING_STATE:
                //do nothing //運行時電梯門是關着的,不能關門
                break;
            case STOPPING_STATE:
                //do nothing //停止時電梯也是關着的,不能關門
                break;
        }
    }

    //執行開門動作
    @Override
    public void open() {
        switch (this.state) {
            case OPENING_STATE://門已經開了,不能再開門了
                //do nothing
                break;
            case CLOSING_STATE://關門狀態,門打開:
                System.out.println("電梯門打開了。。。");
                this.setState(OPENING_STATE);
                break;
            case RUNNING_STATE:
                //do nothing 運行時電梯不能開門
                break;
            case STOPPING_STATE:
                System.out.println("電梯門開了。。。");//電梯停了,可以開門了
                this.setState(OPENING_STATE);
                break;
        }
    }

    //執行運行動作
    @Override
    public void run() {
        switch (this.state) {
            case OPENING_STATE://電梯不能開着門就走
                //do nothing
                break;
            case CLOSING_STATE://門關了,可以運行了
                System.out.println("電梯開始運行了。。。");
                this.setState(RUNNING_STATE);//現在是運行狀態
                break;
            case RUNNING_STATE:
                //do nothing 已經是運行狀態了
                break;
            case STOPPING_STATE:
                System.out.println("電梯開始運行了。。。");
                this.setState(RUNNING_STATE);
                break;
        }
    }

    //執行停止動作
    @Override
    public void stop() {
        switch (this.state) {
            case OPENING_STATE: //開門的電梯已經是是停止的了(正常情況下)
                //do nothing
                break;
            case CLOSING_STATE://關門時才可以停止
                System.out.println("電梯停止了。。。");
                this.setState(STOPPING_STATE);
                break;
            case RUNNING_STATE://運行時當然可以停止了
                System.out.println("電梯停止了。。。");
                this.setState(STOPPING_STATE);
                break;
            case STOPPING_STATE:
                //do nothing
                break;
        }
    }
}

public class Client {
    public static void main(String[] args) {
        Lift lift = new Lift();
        lift.setState(ILift.STOPPING_STATE);//電梯是停止的
        lift.open();//開門
        lift.close();//關門
        lift.run();//運行
        lift.stop();//停止
    }
}

問題分析:

  • 使用了大量的switch…case這樣的判斷(if…else也是一樣),使程序的可閲讀性變差。
  • 擴展性很差。如果新加了斷電的狀態,我們需要修改上面判斷邏輯

定義:

對有狀態的對象,把複雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。

結構

狀態模式包含以下主要角色。

  • 環境(Context)角色:也稱為上下文,它定義了客户程序需要的接口,維護一個當前狀態,並將與狀態相關的操作委託給當前狀態對象來處理。
  • 抽象狀態(State)角色:定義一個接口,用以封裝環境對象中的特定狀態所對應的行為。
  • 具體狀態(Concrete State)角色:實現抽象狀態所對應的行為。

案例實現

對上述電梯的案例使用狀態模式進行改進。類圖如下:

代碼如下:

//抽象狀態類
public abstract class LiftState {
    //定義一個環境角色,也就是封裝狀態的變化引起的功能變化
    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();
}

//開啓狀態
public class OpenningState extends LiftState {

    //開啓當然可以關閉了,我就想測試一下電梯門開關功能
    @Override
    public void open() {
        System.out.println("電梯門開啓...");
    }

    @Override
    public void close() {
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動作委託為CloseState來執行,也就是委託給了ClosingState子類執行這個動作
        super.context.getLiftState().close();
    }

    //電梯門不能開着就跑,這裏什麼也不做
    @Override
    public void run() {
        //do nothing
    }

    //開門狀態已經是停止的了
    @Override
    public void stop() {
        //do nothing
    }
}

//運行狀態
public class RunningState extends LiftState {

    //運行的時候開電梯門?你瘋了!電梯不會給你開的
    @Override
    public void open() {
        //do nothing
    }

    //電梯門關閉?這是肯定了
    @Override
    public void close() {//雖然可以關門,但這個動作不歸我執行
        //do nothing
    }

    //這是在運行狀態下要實現的方法
    @Override
    public void run() {
        System.out.println("電梯正在運行...");
    }

    //這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState);
        super.context.stop();
    }
}

//停止狀態
public class StoppingState extends LiftState {

    //停止狀態,開門,那是要的!
    @Override
    public void open() {
        //狀態修改
        super.context.setLiftState(Context.openningState);
        //動作委託為CloseState來執行,也就是委託給了ClosingState子類執行這個動作
        super.context.getLiftState().open();
    }

    @Override
    public void close() {//雖然可以關門,但這個動作不歸我執行
        //狀態修改
        super.context.setLiftState(Context.closeingState);
        //動作委託為CloseState來執行,也就是委託給了ClosingState子類執行這個動作
        super.context.getLiftState().close();
    }

    //停止狀態再跑起來,正常的很
    @Override
    public void run() {
        //狀態修改
        super.context.setLiftState(Context.runningState);
        //動作委託為CloseState來執行,也就是委託給了ClosingState子類執行這個動作
        super.context.getLiftState().run();
    }

    //停止狀態是怎麼發生的呢?當然是停止方法執行了
    @Override
    public void stop() {
        System.out.println("電梯停止了...");
    }
}

//關閉狀態
public class ClosingState extends LiftState {

    @Override
    //電梯門關閉,這是關閉狀態要實現的動作
    public void close() {
        System.out.println("電梯門關閉...");
    }

    //電梯門關了再打開,逗你玩呢,那這個允許呀
    @Override
    public void open() {
        super.context.setLiftState(Context.openningState);
        super.context.open();
    }


    //電梯門關了就跑,這是再正常不過了
    @Override
    public void run() {
        super.context.setLiftState(Context.runningState);
        super.context.run();
    }

    //電梯門關着,我就不按樓層
    @Override
    public void stop() {
        super.context.setLiftState(Context.stoppingState);
        super.context.stop();
    }
}

//環境角色
public class Context {
    //定義出所有的電梯狀態
    public final static OpenningState openningState = new OpenningState();//開門狀態,這時候電梯只能關閉
    public final static ClosingState closeingState = new ClosingState();//關閉狀態,這時候電梯可以運行、停止和開門
    public final static RunningState runningState = new RunningState();//運行狀態,這時候電梯只能停止
    public final static StoppingState stoppingState = new StoppingState();//停止狀態,這時候電梯可以開門、運行


    //定義一個當前電梯狀態
    private LiftState liftState;

    public LiftState getLiftState() {
        return this.liftState;
    }

    public void setLiftState(LiftState liftState) {
        //當前環境改變
        this.liftState = liftState;
        //把當前的環境通知到各個實現類中
        this.liftState.setContext(this);
    }

    public void open() {
        this.liftState.open();
    }

    public void close() {
        this.liftState.close();
    }

    public void run() {
        this.liftState.run();
    }

    public void stop() {
        this.liftState.stop();
    }
}

//測試類
public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setLiftState(new ClosingState());

        context.open();
        context.close();
        context.run();
        context.stop();
    }
}

優缺點

優點:

  • 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
  • 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。

缺點:

  • 狀態模式的使用必然會增加系統類和對象的個數。
  • 狀態模式的結構與實現都較為複雜,如果使用不當將導致程序結構和代碼的混亂。
  • 狀態模式對"開閉原則"的支持並不太好。

使用場景

  • 當一個對象的行為取決於它的狀態,並且它必須在運行時根據狀態改變它的行為時,就可以考慮使用狀態模式。
  • 一個操作中含有龐大的分支結構,並且這些分支決定於對象的狀態時。

往期推薦

  • 《SpringBoot》EasyExcel實現百萬數據的導入導出
  • 《SpringBoot》史上最全SpringBoot相關注解介紹
  • Spring框架IoC核心詳解
  • 萬字長文帶你窺探Spring中所有的擴展點
  • 如何實現一個通用的接口限流、防重、防抖機制
  • 萬字長文帶你深入Redis底層數據結構
  • volatile關鍵字最全原理剖析

Add a new Comments

Some HTML is okay.