Java設計模式之狀態模式

TianYi發表於2016-11-30

狀態模式簡介

在狀態模式(State Pattern)中,類的行為是基於它的狀態改變的。這種型別的設計模式屬於行為型模式。在狀態模式中,我們建立表示各種狀態的物件和一個行為隨著狀態物件改變而改變的 context 物件。

意圖:允許物件在內部狀態發生改變時改變它的行為,物件看起來好像修改了它的類。

主要解決:物件的行為依賴於它的狀態(屬性),並且可以根據它的狀態改變而改變它的相關行為。

何時使用:程式碼中包含大量與物件狀態有關的條件語句。

如何解決:將各種具體的狀態類抽象出來。

基本類圖

Java設計模式之狀態模式

我們可以看到使用一箇中間類Context負責狀態的更改和呼叫不同狀態的方法,而使用者不知道具體實現的細節,context類可以省略。

Context 是一個環境角色,它的作用是串聯各個狀態的過渡,在BaseSate 抽象類中我們定義了並把這個環境角色聚合進來,並傳遞到了子類,也就是五個具體的實現類中自己根據環境來決定如何進行狀態的過渡。

不使用狀態模式以電梯為例

電梯自定義有五種狀態:開、關、異常、走、停止,每個狀態執行不同的方法能夠改變狀態,並且不同的狀態只能能執行特定的方法。

public class NOState {

    public static final int CLOSING_STATE = 0;
    public static final int FAULT_STATE = 1;
    public static final int OPENING_STATE = 2;
    public static final int RUNNING_STATE = 3;
    public static final int STOPPING_STATE = 4;

    private static int state;


    /**
     * 模擬電梯的執行方法
     */
    public void run() {
        switch (state) {
            case CLOSING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case FAULT_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case OPENING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case RUNNING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case STOPPING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            default:
                break;
        }
    }

    /**
     * 模擬電梯的停止方法
     */
    public void stop() {
        switch (state) {
            case CLOSING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case FAULT_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case OPENING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case RUNNING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case STOPPING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            default:
                break;
        }
    }

    /**
     * 模擬電梯的開門方法
     */
    public void open() {
        switch (state) {
            case CLOSING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case FAULT_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case OPENING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case RUNNING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case STOPPING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            default:
                break;
        }
    }

    /**
     * 模擬電梯的關門方法
     */
    public void close() {
        switch (state) {
            case CLOSING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case FAULT_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case OPENING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case RUNNING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            case STOPPING_STATE:
//              執行具體的方法
//              state = XXXX;
                break;
            default:
                break;
        }
    }
}複製程式碼

程式碼高度重複,使用狀態模式來重構程式碼。

使用狀態模式重構程式碼

新建一個所有狀態共有的基類BaseState,並且抽取公共方法,然後新建一個Context類來負責管理狀態,使客戶端能夠在不知道狀態的情況下實現邏輯。

Java設計模式之狀態模式

簡單而言類圖如上,客戶端呼叫Context,Context負責狀態的改變,而且客戶端不知道實現的細節,高度解耦。

使用狀態模式的基本邏輯

Java設計模式之狀態模式

方法的呼叫伴隨的是狀態的改變,在呼叫方法前首先判斷狀態,比如電梯開時不能呼叫執行方法。

不同狀態的實現

/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 *          模擬電梯狀態中不同的方法
 */
public abstract class BaseState {

    protected Context mContext;

    public void setContext(Context context) {
        mContext = context;
    }

    /**
     * 模擬電梯的執行方法
     */
    public abstract void run();
    /**
     * 模擬電梯的停止方法
     */
    public abstract void stop();
    /**
     * 模擬電梯的開門方法
     */
    public abstract void open();
    /**
     * 模擬電梯的關門方法
     */
    public abstract void close();
}複製程式碼

Java設計模式之狀態模式

五種狀態都有共有的方法


ClosingState


/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 * 模擬電梯關閉狀態
 */
public class ClosingState extends BaseState {
    /**
     * 模擬電梯的執行方法
     */
    @Override
    public void run() {
        super.mContext.setBaseState(Context.RUNNING_STATE);
        System.out.println("電梯開始跑起來-----------");
    }

    /**
     * 模擬電梯的停止方法
     */
    @Override
    public void stop() {
        super.mContext.setBaseState(Context.STOPPING_STATE);
        System.out.println("電梯關門-----------");
    }

    /**
     * 模擬電梯的開門方法
     */
    @Override
    public void open() {
        super.mContext.setBaseState(Context.CLOSING_STATE);
        System.out.println("電梯開門-----------");
    }

    /**
     * 模擬電梯的關門方法
     */
    @Override
    public void close() {
        super.mContext.setBaseState(Context.STOPPING_STATE);
        System.out.println("電梯關門-----------");
    }
}複製程式碼

FaultState

/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 *          模擬故障狀態,四個方法都不可用
 */
public class FaultState extends BaseState {
    /**
     * 模擬電梯的執行方法
     */
    @Override
    public void run() {
        System.out.println("電梯發生故障,不能正常工作");
    }

    /**
     * 模擬電梯的停止方法
     */
    @Override
    public void stop() {
        System.out.println("電梯發生故障,不能正常工作");
    }

    /**
     * 模擬電梯的開門方法
     */
    @Override
    public void open() {
        System.out.println("電梯發生故障,不能正常工作");
    }

    /**
     * 模擬電梯的關門方法
     */
    @Override
    public void close() {
        System.out.println("電梯發生故障,不能正常工作");
    }
}複製程式碼

OpeningState

/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 *          模擬電梯開啟狀態
 */
public class OpeningState extends BaseState {
    /**
     * 模擬電梯的執行方法
     */
    @Override
    public void run() {
        super.mContext.setBaseState(Context.FAULT_STATE);
    }

    /**
     * 模擬電梯的停止方法
     */
    @Override
    public void stop() {

    }

    /**
     * 模擬電梯的開門方法
     */
    @Override
    public void open() {
        System.out.println("電梯開門-----------");
    }

    /**
     * 模擬電梯的關門方法
     */
    @Override
    public void close() {
        super.mContext.setBaseState(Context.CLOSING_STATE);
        System.out.println("電梯關門-----------");
    }
}複製程式碼

RunningState

/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 *          模擬電梯執行狀態
 */
public class RunningState extends BaseState {
    /**
     * 模擬電梯的執行方法
     */
    @Override
    public void run() {
        System.out.println("電梯開始跑起來-----------");
    }

    /**
     * 模擬電梯的停止方法
     */
    @Override
    public void stop() {
        super.mContext.setBaseState(Context.STOPPING_STATE);
        System.out.println("電梯停止-----------");
    }

    /**
     * 模擬電梯的開門方法
     */
    @Override
    public void open() {
        super.mContext.setBaseState(Context.FAULT_STATE);
        System.out.println("電梯發生故障");
    }

    /**
     * 模擬電梯的關門方法
     */
    @Override
    public void close() {
        System.out.println("電梯關門-----------");
    }
}複製程式碼

StoppingState

/**
 * @author HaoTianYi  hao.ty@haotianyi.win
 * @version v1.0
 *          模擬電梯停止狀態
 */
public class StoppingState extends BaseState {
    /**
     * 模擬電梯的執行方法
     */
    @Override
    public void run() {
        super.mContext.setBaseState(Context.RUNNING_STATE);
        System.out.println("電梯開始跑起來-----------");
    }

    /**
     * 模擬電梯的停止方法
     */
    @Override
    public void stop() {
        System.out.println("電梯停止-----------");
    }

    /**
     * 模擬電梯的開門方法,中途停止狀態省略
     */
    @Override
    public void open() {
        super.mContext.setBaseState(Context.OPENING_STATE);
        System.out.println("電梯開門-----------");
    }

    /**
     * 模擬電梯的關門方法
     */
    @Override
    public void close() {
        System.out.println("電梯關門-----------");
    }
}複製程式碼

Context方法

public class Context {

    public static final ClosingState CLOSING_STATE = new ClosingState();
    public static final FaultState FAULT_STATE = new FaultState();
    public static final OpeningState OPENING_STATE = new OpeningState();
    public static final RunningState RUNNING_STATE = new RunningState();
    public static final StoppingState STOPPING_STATE = new StoppingState();

    private BaseState mBaseState;

    public BaseState getBaseState() {
        return mBaseState;
    }

    public void setBaseState(BaseState baseState) {
        this.mBaseState = baseState;
        this.mBaseState.setContext(this);
    }

    /**
     * 模擬電梯的執行方法
     */
    public Context run() {
        this.mBaseState.run();
        return this;
    }

    /**
     * 模擬電梯的停止方法
     */
    public Context stop() {
        this.mBaseState.stop();
        return this;
    }

    /**
     * 模擬電梯的開門方法
     */
    public Context open() {
        this.mBaseState.open();
        return this;
    }

    /**
     * 模擬電梯的關門方法
     */
    public Context close() {
        this.mBaseState.close();
        return this;
    }
}複製程式碼

注意:

    public void setBaseState(BaseState baseState) {
        this.mBaseState = baseState;
        this.mBaseState.setContext(this);
    }複製程式碼

中:this.mBaseState.setContext(this);的作用:繫結相應的上下文

客戶端的實現

public class Client {
    public static void main(String[] args) {
        Context context = new Context();
        context.setBaseState(new ClosingState());
        context.run().open().close();

        Context context1 = new Context();
        context1.setBaseState(new RunningState());
        context1.stop().open().run().open();
    }
}複製程式碼

Java設計模式之狀態模式

優缺點

優點: 1、封裝了轉換規則。 2、列舉可能的狀態,在列舉狀態之前需要確定狀態種類。 3、將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為。 4、允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件語句塊。 5、可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。

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

和策略模式的對比

狀態模式和策略模式幾乎完全一樣,但是他們的本質不同。狀態模式的狀態是不可替換的而且是兄弟關係,但是策略模式是可以替換的彼此獨立的。而且他們執行完之後的行為也不同狀態模式會更改狀態但是策略模式執行完就是執行完。

具體程式碼參見:GitHub

相關文章