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