不同狀態,不同行為;或說,每個狀態擁有相應行為。
State 模式實際使用頻率高,適合「狀態切換」。因我們常用 if-else if-else 進行狀態切換,若針對狀態的判斷切換反覆出現,就要聯想是否可採用 State 模式。
不只根據狀態,也有根據屬性。若某物件屬性不同導致行為不同,在資料庫系統中出現頻率較高。我們常在資料表尾部加上 property 屬性欄位標識特殊性質記錄,此屬性改變(切換)隨時可能發生,就有可能使用 State。
實際中類似開關的狀態切換很多,但有時不明顯,取決於經驗與對系統理解深度。
這裡要闡述「開關切換狀態」與「一般狀態判斷」有區別。「一般狀態判斷」也是 if-else if 結構,例:
if (which == 1) state = "hello";
else if (which == 2) state = "hi";
else if (which == 3) state = "bye";
這是「一般狀態判斷」,state 值根據 which 變數決定,which 與 state 無關。若改成:
if (state.equals("bye")) state = "hello";
else if (state.equals("hello")) state = "hi";
else if (state.equals("hi")) state = "bye";
這就是「開關切換狀態」,將 state 從 hello 切換到 hi,再到 bye,再回 hello,像旋轉開關,這種狀態改變可用 State 模式。
若單純只有 hello → hi → bye → hello 單一方向切換,不一定需要 State 模式,因 State 模式會建立許多子類別,增加複雜度。但若發生反方向切換、或需任意切換,就需要 State 了。
public class Context {
private Color state = null;
public void push() {
// 如果當前 red 狀態就切換到 blue
if (state == Color.red) state = Color.blue;
// 如果當前 blue 狀態就切換到 green
else if (state == Color.blue) state = Color.green;
// 如果當前 black 狀態就切換到 red
else if (state == Color.black) state = Color.red;
// 如果當前 green 狀態就切換到 black
else if (state == Color.green) state = Color.black;
Sample sample = new Sample(state);
sample.operate();
}
public void pull() {
// 與 push 狀態切換正好相反
if (state == Color.green) state = Color.blue;
else if (state == Color.black) state = Color.green;
else if (state == Color.blue) state = Color.red;
else if (state == Color.red) state = Color.black;
Sample2 sample2 = new Sample2(state);
sample2.operate();
}
}
上例有兩個動作 push 推與 pull 拉,改變 Context 顏色,至此需要 State 模式優化。但注意:本例 state 變化只是簡單顏色賦值,具體行為很簡單,State 適合龐大具體行為,因此實際使用不一定非要用 State 模式,會增加子類別數目、讓簡單變複雜。
start、HaveUsername、Authorized、quit,每個狀態對應行為較大。State 需兩類實體參與:
Context 實為 State Manager,其中有狀態切換動作。public abstract class State {
public abstract void handlePush(Context c);
public abstract void handlePull(Context c);
public abstract Color getColor();
}
父類別方法對應 State Manager 中開關行為。本例 Context 有 push、pull 兩動作,狀態父類別需具體處理:handlePush()、handlePull();同時需取得推/拉結果方法 getColor()。
public class BlueState extends State {
public void handlePush(Context c) {
// 根據 push 方法「如果是 blue 狀態切換到 green」
c.setState(new GreenState());
}
public void handlePull(Context c) {
// 根據 pull 方法「如果是 blue 狀態切換到 red」
c.setState(new RedState());
}
public Color getColor() { return Color.blue; }
}
其他狀態子類別(RedState、GreenState、BlackState)實作類似。
public class Context {
private State state = null; // 原 Color state 改為新 State state
// setState 用來改變 state 狀態,實現狀態切換
public void setState(State state) {
this.state = state;
}
public void push() {
// 狀態切換細節(本例顏色變化)已封裝在子類別 handlePush 中,這裡無需關心
state.handlePush(this);
// Sample 需使用 state 切換結果,用 getColor()
Sample sample = new Sample(state.getColor());
sample.operate();
}
public void pull() {
state.handlePull(this);
Sample2 sample2 = new Sample2(state.getColor());
sample2.operate();
}
}
至此完成 State 重構過程。以上為簡單實例,實際應用中 handlePush、handlePull 處理較複雜。