1. 程式人生 > 其它 >Java設計模式-狀態模式

Java設計模式-狀態模式

狀態模式: 允許一個物件在其內部狀態改變時改變其行為, 其物件看起來像是改變了其類.

(圖片來源: 設計模式:可複用面向物件軟體的基礎)

其目的是: 解決系統中複雜物件的狀態流轉以及不同狀態下的行為封裝問題.

模式實現

案例: 問題跟蹤(Bug狀態流轉):

有過Kelude、Jira使用經驗的同學都知道一個Bug由測試同學提出, 一直到被開發同學解決會經過一系列狀態的流轉:

新建(New) -> 開啟(Open) -> 解決(Fixed) -> 關閉(Closed) …

且每種狀態都會對應複雜業務的處理邏輯(如通知相應開發/測試人員、郵件/簡訊提醒、報表記錄等等), 下面我們就以這個場景來討論狀態模式的實現:

狀態模式-Bug流轉:

State

抽象狀態: 定義一個介面封裝與 Context的一個特定狀態 相關的行為:

/**
 * @author jifang
 * @since 16/8/28 下午6:06.
 */
public interface State {
    void handle(Context context);
}

ConcreteState

具體狀態: 每一個子類實現一個與 Context的某一個特定狀態相關的具體行為 :

class NewState implements State {
    static final NewState instance = new NewState();
    // 單例 or 享元
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            // 本狀態下的核心業務處理
            System.out.println("測試: 發現了Bug, 開發同學趕緊處理");
            // 狀態流轉
            context.setCurrent(OpenState.instance());
        }
    }
}
class OpenState implements State {
    static final OpenState instance = new OpenState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("開發: Bug已經看到, 正在處理");
            context.setCurrent(FixedState.instance());
        }
    }
}
class FixedState implements State {
    static final FixedState instance = new FixedState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("開發: Bug已經修復, 測試同學看一下");
            context.setCurrent(ClosedState.instance());
        }
    }
}
class ClosedState implements State {
    static final ClosedState instance = new ClosedState();
    public static State instance() {
        return instance;
    }
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("測試: Bug驗證通過, 已關閉");
            context.setCurrent(null);
        }
    }
}

Context

定義客戶感興趣的介面

維護一個ConcreteState子類例項 -當前狀態.

public class Context {
    private State current;
    public Context(State current) {
        this.current = current;
    }
    public State getCurrent() {
        return current;
    }
    public void setCurrent(State current) {
        this.current = current;
    }
    public void request() {
        if (current != null) {
            current.handle(this);
        }
    }
}

Client

public class Client {
    @Test
    public void client() {
        Context context = new Context(NewState.instance());
        context.request();
        context.request();
        context.request();
        context.request();
        context.request();
    }
}

狀態推動

前面介紹的狀態流轉需要由Client推動(Client呼叫Context的request()), 還有其他幾種推動方式. 如State自動流轉: 每個State處理結束, 自動進入下一狀態的處理環節(在State內部呼叫Context的request()):

class NewState implements State {
    @Override
    public void handle(Context context) {
        if (context.getCurrent() == this) {
            System.out.println("測試: 發現了Bug, 開發同學趕緊處理");
            context.setCurrent(new OpenState());
        }
        context.request();
    }
}

另外還有一種基於表驅動的狀態機實現, 實現細節參考 設計模式:可複用面向物件軟體的基礎 P204.

小結

將與特定狀態相關的行為區域性化, 並將不同狀態的行為分隔開:

將特定的狀態相關的行為都放入一個物件中: 由於所有與狀態相關的程式碼都存在於某ConcreteState中, 所以通過定義新的子類可以很容易地增加新的狀態和轉換.

可以將狀態轉移邏輯分佈到State之間, 將每一個狀態轉換和動作封裝到一個類中, 就把著眼點從執行狀態提高到整個物件的狀態, 這將使程式碼結構化並使意圖更加清晰,消除龐大的條件分支語句.

狀態轉換顯式化:

當一個物件僅以內部資料值來定義當前狀態時, 其狀態僅表現為一些變數的賦值, 這不夠明確. 為不同的狀態引入獨立的物件使得轉換變得更加明確(類原子化).

場景:

當一個物件的行為取決於它的狀態, 並且它必須在執行時刻根據狀態改變它的行為;

一個操作中含有龐大的條件分支語句, 且這些分支依賴於該物件的狀態, 這個狀態通常用一個/多個列舉常量表示:

OA系統請求狀態流轉

銀行系統資金狀態流轉

執行緒物件狀態切換

TCP連線狀態流轉

State模式將每一個條件分支放入一個獨立的類中. 這使得可以根據物件自身的情況將物件的狀態作為一個物件, 這一物件可以不依賴於其他物件而獨立變化.