設(shè)計(jì)模式學(xué)習(xí)專欄十一--------狀態(tài)模式
名稱: 狀態(tài)模式 (State)
價(jià)值觀念: 通過(guò)改變對(duì)象內(nèi)部的狀態(tài)來(lái)幫助對(duì)象控制自己的行為
場(chǎng)景
設(shè)計(jì)一個(gè)萬(wàn)能糖果機(jī) , 我們希望設(shè)計(jì)盡可能有彈性 , 而且將來(lái)我們可能要為它增加更多的行為~
剛開(kāi)始的設(shè)計(jì)方式
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int 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) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter, the machine is sold out");
} else if (state == SOLD) {
System.out.println("Please wait, we're already giving you a gumball");
}
}
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}
......
}
我們的第一版設(shè)計(jì)完成了 , 發(fā)現(xiàn)每個(gè)動(dòng)作下都需要判斷當(dāng)前的狀態(tài),然后做出相應(yīng)的動(dòng)作.
新功能引入 , 我們加入了一個(gè)新的游戲狀態(tài) , 當(dāng)曲柄被轉(zhuǎn)動(dòng)時(shí),有10%的幾率掉下來(lái)的是兩顆糖果(贏家狀態(tài))
此時(shí)我們會(huì)發(fā)現(xiàn)第一版設(shè)計(jì)中. 每個(gè)動(dòng)作下 都需要新增條件判斷 , 違反了對(duì)修改關(guān)閉的原則. 程序出錯(cuò)概率大大提升.
如何解決
分析程序擴(kuò)展時(shí)的 可變部分與不變部分
insertCoin | returnCoin | trunCrank | dispense | |
---|---|---|---|---|
OnReadyState | - | - | - | - |
HasCoin | - | - | - | - |
SoldState | - | - | - | - |
SoldOutState | - | - | - | - |
WinnerState | *** | *** | *** | *** |
不變部分: 從橫向來(lái)看。 用戶能執(zhí)行的操作都是一樣的。 (插入硬幣,按下退幣按鈕,拉下把手)
變化部分: 從橫向看。如果糖果工廠新增的狀態(tài), 對(duì)于用戶每一種動(dòng)作,糖果機(jī)的響應(yīng)都是不同的。都要做出對(duì)應(yīng)的修改
將可變部分抽取出來(lái): 每一種狀態(tài)都會(huì)執(zhí)行4種操作,糖果機(jī)具體的操作與當(dāng)前狀態(tài)有關(guān)。 因此將狀態(tài)與該狀態(tài)下的對(duì)應(yīng)的動(dòng)作行為抽取
出來(lái)形成接口。讓每一個(gè)狀態(tài)都實(shí)現(xiàn)該接口。
狀態(tài)模式總覽
定義:允許
對(duì)象在內(nèi)部狀態(tài)在改變時(shí)改變它的行為
,對(duì)象看起來(lái)好像改變了它的類
(將狀態(tài)與該狀態(tài)下的行為封裝成獨(dú)立的類,并將動(dòng)作委托
到代表當(dāng)前狀態(tài)的對(duì)象)
-
模式的理解
-
類圖
image -
角色
- 上下文Context
- 封裝狀態(tài)及該狀態(tài)下行為的 State
- 具體的狀態(tài)實(shí)習(xí)那類ConcreteState
-
細(xì)節(jié)
- 狀態(tài)模式允許一個(gè)對(duì)象基于內(nèi)部狀態(tài)而擁有不同的行為
- 通過(guò)把每個(gè)狀態(tài)封裝進(jìn)一個(gè)類, 我們把以后需要做的任何改變都局部化了 (改變這個(gè)狀態(tài)下的行為)
- 狀態(tài)模式與策略模式有相同的類圖 ,但是它們的意圖不同
- 策略模式通常會(huì)用行為或算法來(lái)配置Context類
- 狀態(tài)模式允許Context隨著狀態(tài)的改變而改變行為
- 狀態(tài)轉(zhuǎn)換可以由State類(某個(gè)行為后改變狀態(tài))或者Context(外部設(shè)置狀態(tài)setState)類控制.
- 使用狀態(tài)模式通常會(huì)導(dǎo)致設(shè)計(jì)中的類的數(shù)據(jù)大量增量(狀態(tài)類)
-
核心代碼部分
-
上下文
public class GumballMachine { //上下文持有不同的狀態(tài)引用 State soldOutState; State noQuarterState; State hasQuarterState; State soldState; State winnerState; 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); winnerState = new WinnerState(this); this.count = numberGumballs; if (numberGumballs > 0) { state = noQuarterState; } } //上下文提供改變狀態(tài)的接口 void setState(State state) { this.state = state; } public void insertQuarter() { state.insertQuarter(); //將行為委托給狀態(tài)對(duì)象來(lái)處理 } public void ejectQuarter() { state.ejectQuarter(); } public void turnCrank() { state.turnCrank(); state.dispense(); } void releaseBall() { System.out.println("A gumball comes rolling out the slot..."); if (count != 0) { count = count - 1; } } int getCount() { return count; } void refill(int count) { this.count += count; System.out.println("The gumball machine was just refilled; it's new count is: " + this.count); state.refill(); } getter and setter... }
-
狀態(tài)接口
public interface State { public void insertQuarter(); public void ejectQuarter(); public void turnCrank(); public void dispense(); public void refill(); }
- 具體的狀態(tài)
public class WinnerState implements State {
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
public void ejectQuarter() {
System.out.println("Please wait, we're already giving you a Gumball");
}
public void turnCrank() {
System.out.println("Turning again doesn't get you another gumball!");
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
//通過(guò)上下文改變狀態(tài)
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
System.out.println("YOU'RE A WINNER! You got two gumballs for your quarter");
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
public void refill() { }
public String toString() {
return "despensing two gumballs for your quarter, because YOU'RE A WINNER!";
}
}
-
主程序
public class GumballMachineTestDrive { public static void main(String[] args) { GumballMachine gumballMachine = new GumballMachine(10); System.out.println(gumballMachine); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); System.out.println(gumballMachine); } }
-
輸出結(jié)果
Mighty Gumball, Inc. Java-enabled Standing Gumball Model #2004 Inventory: 10 gumballs Machine is waiting for quarter You inserted a quarter You turned... A gumball comes rolling out the slot... You inserted a quarter You turned... A gumball comes rolling out the slot... Mighty Gumball, Inc. Java-enabled Standing Gumball Model #2004 Inventory: 8 gumballs Machine is waiting for quarter
參考
? 書(shū)籍: HeadFirst設(shè)計(jì)模式
? 代碼參考地址: 我就是那個(gè)地址