定義
狀態模式允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。
這個模式將狀態封裝成爲獨立的類,並將動作委託到代表當前狀態的對象,行爲會隨着內部狀態而改變。
場景描述
糖果機就跟街上那個卡一元錢後,小蘋果能轉出來小球很類似,對於糖果機來說,一共有四種狀態,分別是沒有25分錢的狀態,有25分錢狀態,有糖果狀態,沒糖果狀態,其中1和2、3和4狀態是互斥的。
沒引入設計模式之前的代碼一般這樣寫:
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;
}
}
// 當有25分錢投進來,就會執行這裏
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");
}
}
// 現在,如果顧客試着退回25分錢
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");
}
}
// 顧客試着轉動曲柄
public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}
// 調用此方法,發放糖果
private void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}
// 添加糖豆
public void refill(int numGumBalls) {
this.count = numGumBalls;
state = NO_QUARTER;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("Mighty Gumball, Inc.");
result.append("Java-enabled Standing Gumball Model");
result.append("Inventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\nMachine is ");
if (state == SOLD_OUT) {
result.append("sold out");
} else if (state == NO_QUARTER) {
result.append("waiting for quarter");
} else if (state == HAS_QUARTER) {
result.append("waiting for turn of crank");
} else if (state == SOLD) {
result.append("delivering a gumball");
}
result.append("\n");
return result.toString();
}
}
變化
客戶要求當曲柄轉動時,有10%的機率掉下來的是兩顆糖果(多送你一顆),對着上面的代碼不妨停下來想一想。
按照Head First的劇情是增加一個新的狀態,贏家狀態。之後在上面的每個方法裏面加上贏家的判斷,更糟糕的是轉動曲柄方法,因爲首先要檢查這個顧客是不是贏家。
發放糖果的時候假如剩最後一顆糖果,是不是就不能隨機贏家了?
這種改法明顯改的太多,不利於維護。
正解
新的代碼,其中的精髓我覺的就在於,所有的狀態都繼承相同的接口,這樣狀態相互切換,沒有任何影響,也就是抽象成同類的事物,不同的執行內容。例如在HasQuarterState狀態,調用turnCrank()方法,在方法體裏就設置Context到WinnerState狀態,接下來Context執行request【開篇的類圖】操作時,就又調的是WinnerState狀態了。
public interface State
{
void insertQuater();//插入25分錢
void ejectQuater(); //退回25分錢
void turnCrank(); //轉動曲柄
void dispense(); //彈出糖果
}
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!");
}
// 我們在這裏釋放出兩顆糖果,讓你和進入NoQuarterState或SoldOutState
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
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 HasQuarterState implements State {
// 首先,我們增加一個隨機數產生器,產生10%贏的機會
Random randomWinner = new Random(System.currentTimeMillis());
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
// 然後決定這個顧客是否贏了
public void turnCrank() {
System.out.println("You turned...");
int winner = randomWinner.nextInt(10);
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
public void dispense() {
System.out.println("No gumball dispensed");
}
}
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);
// 多重複幾次,就可以看是否中獎了
}
}
public class GumballMachine {
//狀態實例
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;
// 實例變量state,初始化爲糖果售罄狀態
State state = soldOutState;
// 記錄機器內裝有糖果的數目,開始機器是沒有裝糖果的
int count=0;
// 構造器取得糖果的初始數目並把它放在一個實例變量count中
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;
// 若超過0顆糖果,就將狀態設置爲NoQuarterState
if(numberGumballs > 0) {
state = noQuarterState;
}
}
// 取得機器內的糖果數目
public int getCount() {
return count;
}
// 取得糖果售罄狀態
// ……
// 投入25分錢
public void insertQuarter(){
state.insertQuarter();
}
// 拒絕25分錢
public void ejectQuarter(){
state.ejectQuarter();
}
// 轉動曲柄
public void turnCrank(){
state.turnCrank();
state.dispense();
}
// 設置狀態
public void setState(State state){
this.state=state;
}
// 糖果滾出來一個
public void releaseBall(){
System.out.println("A gumball comes rolling out of the solt...");
if(count!=0){
count--;
}
}
}