Head First 設計模式 —— 12. 狀態 (State) 模式
- 2021 年 1 月 15 日
- 筆記
- Head First 設計模式, 設計模式
思考題
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static SOLD = 3;
int state = SOLD_OUT;
int count = 0;
public GumballMachine(int count) {
this.count = count;
if(count > 0) {
state = NO_QUARTER;
}
}
public void insertQuarter() {
if(state == HAS_QUARTER) {
// print error message
} else if(state == NO_QUARTER) {
state = HAS_QUARTER;
// print success message
} else if(state == SOLD_OUT) {
// print error message
} else if(state == SOLD) {
// print error message
}
}
public void ejectQuarter() {
// ...
}
public void turnCrank() {
// ...
}
public void dispense() {
// ...
}
}
下列哪一項描述了我們實現的狀態?(多選) P396
- [x] A. 這份程式碼確實沒有遵守開放-關閉原則
- 當新增狀態時,必須在所有方法中加上對新狀態的條件判斷,所以沒有遵守開放-關閉原則
- [ ] B. 這份程式碼會讓 Fortran 程式設計師感到驕傲
- 不知道為什麼
- 【答案有此選項】
- [x] C. 這個設計其實不符合面向對象
- 這個設計是面向過程的,所有的操作都通過條件判斷,沒有封裝狀態
- [x] D. 狀態轉換被埋藏在條件語句中,所以並不明顯
- 狀態轉換是在行為方法內的條件語句中,要找到狀態轉換前後的狀態需要閱讀行為方法內的全部程式碼,難以快速了解某種狀態會如何轉換
- [x] E. 我們還沒有把會改變的那部分包起來
- 狀態和行為都會改變,但行為比較固定且與實際相對應,狀態是抽象出來的,所以應該將狀態封裝起來
- [x] F. 未來加入的程式碼很有可能會導致 bug
- 由於所有行為方法內都有不同狀態的條件判斷,所以在任何狀態發生變化時,都要對所有行為方法進行修改進行處理,很容易遺忘對某行為方法的修改
思考題
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
// print success message
if(count != 0) {
count = count - 1;
}
}
}
讓我們來回頭看看糖果機的實現。如果曲柄被轉動了,但是沒有成功(比方說顧客沒有先投入25分錢的硬幣)。在這種情況下,儘管沒有必要,但我們還是會調用 dispense()
方法。對於這個問題你要如何修改呢? P405
State
介面的turnCrank()
方法增加返回值以表示是否正確處理,只有在正確處理時,才調用dispense()
方法
狀態模式
允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類。 P410
特點
- 將每個狀態行為局部化到自己的狀態類中
P407
- 讓每個狀態「對修改更換比」,讓上下文「對擴展開放」,因為可以加入新的狀態類
P407
缺點
- 通常會導致設計中類的數目大量增加
P423
狀態模式和策略模式的區別
狀態模式
- 將一群行為封裝在狀態對象中,上下文的行為隨時可委託到那些狀態對象中的一個。當前狀態會在狀態對象集合中遊走改變,以反應出上下文內部的狀態,因此,上下文的行為也會跟著改變。但時客戶對於上下文的狀態對象了解不多,甚至根本是渾然不知
P411
- 是不用在上下文中放置許多條件判斷的替代方案,通過將行為包裝進狀態對象中,在上下文內簡單地改變狀態對象來改變上下文的行為
P411
策略模式
- 客戶通常主動指定上下文所要組合的策略對象
P411
- 是除繼承之外的一種彈性替代方案,可以通過組合不同的對象來改變行為
P411
思考題
應該由狀態類還是上下文決定狀態轉換的流向? P412
- 當狀態轉換是固定的時候,適合放在上下文中(此時狀態類之間不相互依賴,是對狀態類修改封閉)
P412
- 當狀態轉換是更動態的時候,通常就會放在狀態類中(此時狀態類之間產生了依賴,是對上下文修改封閉)
P412
思考題
我們需要你為糖果機寫一個重填糖果的 refill()
方法。這個方法需要一個變數——所要填入機器中的糖果數目。它應該能更新糖果機內的糖果數目,並重設機器的狀態。 P421
void refill(int num) {
this.count += num;
if(state instanceof SoldOutState) {
state = noQuarterState;
}
}
思考題
配對下列模式和描述: P422
狀態模式:封裝基於狀態的行為,並將行為委託到當前狀態
策略模式:將可以互換的行為封裝起來,然後使用委託的方法,決定使用哪一個行為
模板方法模式:由子類決定如何實現演算法中的某些步驟
本文首發於公眾號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/head-first-design-patterns