《HeadFirst設計模式》第十章-狀態模式

1.聲明

設計模式中的設計思想、圖片和部分代碼參考自《Head First設計模式》作者Eric Freeman & Elisabeth Freeman & Kathy Siezza & Bert Bates。

在這裏我只是對這本書進行學習閱讀,並向大家分享一些心得體會。

2.需求

有一家糖果機公司想讓我們幫他們做一套軟件系統供糖果機使用,並且他們想要達成如下效果。

這是一張狀態圖,每個圓圈表示糖果機的一種狀態,每個箭頭表示狀態的轉換過程。例如對於糖果機來說,我們投入25分錢,那麼糖果機就由"沒有25分錢" ——> "有25分錢"這個狀態。

如何由狀態圖得到真正的代碼

第一步:

列出糖果機所有可能的狀態。經過分析,可以得到如下四種狀態:

沒有25分錢
有25分錢
糖果售空
售出糖果

第二步:

爲每個狀態都賦予一個常量值,並且再定義一個變量,表示當前所處的狀態:

//售空
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;

第三步:

列出糖果所可能接收到一切動作(指令):

投入25分錢
退回25分錢
轉動曲柄
發放糖果

第四步:

創建一個類,這個類的作用就像是一個狀態機。對每一個動作( 投入25分錢 ),都需要創建一個對應的方法( insertQuarter() )。

但是糖果機的狀態不同,即使接收到相同的動作,那麼結果也是不同的。所以在每個方法中,都需要用if分支做判斷,根據當前所處的狀態,來判斷接收到此指令後下個狀態是什麼。

//投入硬幣
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");
	}
}

3.最初的代碼設計

糖果機:

//糖果機
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");
		}
	}
 
	//轉動曲柄
	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("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004\n");
		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();
	}
}

測試方法:

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

輸出結果:

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
Quarter returned
You turned but there's no quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 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
You haven't inserted a quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

4.萬惡的需求變更

糖果公司果然進行了需求變更(基本操作、意料之中),由於糖果公司使用了我們的系統之後,程序果然正常運轉了,糖果公司頓時不爽,怒變需求。

修改的需求是:轉動曲柄獲取糖果時,有10%的機率掉出兩個糖果。

雖然我們設計的系統周詳、穩定,但是這並不意味着我們的系統就容易擴展。

如果在現有系統上實現,那麼就得變成這樣:

變更分析:

很遺憾,我們必須得重構代碼了,堅持用現有代碼,會增加程序bug的機率。

我們應該使者局部化每個狀態的行爲,這樣一來,如果我們針對某個狀態做了改變,就不會影響其他代碼。我們需要遵守"封裝變化"的原則。如果我們將每個變化的行爲都放在各自的類中,那麼每個狀態只要實現它自己的動作就可以了。

這樣增加新狀態時,雖然我們仍然需要修改代碼,但是我們將修改侷限在一個很小的範圍內,加入一個新狀態,我們只需加入一個類還有可能改變一些轉換即可。

5.新的設計

  1. 首先定義一個State接口。在這個接口內,糖果機的每個動作都有一個對應的方法。
  2. 然後爲機器中的每個狀態實現狀態類。這些類將負責在對應的狀態下進行機器行爲。
  3. 最後,擺脫舊的條件代碼,取而代之的方式是,將動作委託到狀態類。

定義狀態接口和類

定義狀態接口:

public interface State {
 
	//投入硬幣
	public void insertQuarter();
	//退出硬幣
	public void ejectQuarter();
	//轉動曲柄
	public void turnCrank();
	//發放糖果
	public void dispense();
	
	//充值
	public void refill();
}

定義狀態類-無硬幣狀態:

public class NoQuarterState implements State {
	
	//糖果機
    GumballMachine gumballMachine;
 
    public NoQuarterState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		//在"沒有硬幣"的狀態下,投入硬幣,糖果機設置新狀態爲"有硬幣"
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
 
	public void ejectQuarter() {
		System.out.println("You haven't inserted a quarter");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there's no quarter");
	 }
 
	public void dispense() {
		System.out.println("You need to pay first");
	} 
	
	public void refill() { }
 
	public String toString() {
		return "waiting for quarter";
	}
}

定義狀態類-有硬幣狀態:

public class HasQuarterState implements State {
	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...");
		gumballMachine.setState(gumballMachine.getSoldState());
	}

    public void dispense() {
        System.out.println("No gumball dispensed");
    }
    
    public void refill() { }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}

定義狀態類-售空狀態:

public class SoldOutState implements State {
    GumballMachine gumballMachine;
 
    public SoldOutState(GumballMachine gumballMachine) {
        this.gumballMachine = gumballMachine;
    }
 
	public void insertQuarter() {
		System.out.println("You can't insert a quarter, the machine is sold out");
	}
 
	public void ejectQuarter() {
		System.out.println("You can't eject, you haven't inserted a quarter yet");
	}
 
	public void turnCrank() {
		System.out.println("You turned, but there are no gumballs");
	}
 
	public void dispense() {
		System.out.println("No gumball dispensed");
	}
	
	public void refill() { 
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
 
	public String toString() {
		return "sold out";
	}
}

定義狀態類-售出狀態:

public class SoldState implements State {
 
    GumballMachine gumballMachine;
 
    public SoldState(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("Sorry, you already turned the crank");
	}
 
	public void turnCrank() {
		System.out.println("Turning twice doesn't get you another gumball!");
	}
 
	public void dispense() {
		gumballMachine.releaseBall();
		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 "dispensing a gumball";
	}
}

定義糖果機:

糖果機的狀態已不再由常量表示,而是由各種State對象表示,這些State對象也是糖果機動作的委託者。

代碼:

public class GumballMachine {
 
	//四種狀態
	State soldOutState;
	State noQuarterState;
	State hasQuarterState;
	State soldState;
 
	//糖果機當前所處狀態
	State state;
	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;
		} else {
			state = soldOutState;
		}
	}
 
	/*
	 * insertQuarter、
	 * ejectQuarter、
	 * turnCrank
	 * 都委託給當前對象state來處理
	 */
	public void insertQuarter() {
		state.insertQuarter();
	}
 
	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();
	}

	//設置機器當前的狀態
	void setState(State state) {
		this.state = state;
	}
	
    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

測試程序:

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

輸出結果:

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 5 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

You inserted a quarter
Quarter returned
You turned but there's no quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 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
You haven't inserted a quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You can't insert another quarter
You turned...
A gumball comes rolling out the slot
You inserted a quarter
You turned...
A gumball comes rolling out the slot
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

較最初版,我們做了哪些更改:

  • 將每個狀態的行爲局部化到它自己的類中。
  • 將容易產生問題的if語句刪掉,以方便日後維護。
  • 讓每個狀態”對修改關閉“,讓糖果機”對擴展開放",因爲可以加入新的狀態類。
  • 創建一個新的代碼繼承體系,State接口及其實現類,更易理解。

圖示 

在"有硬幣"的狀態下轉動曲柄:

將由"有硬幣"狀態轉換到"售出"狀態:

6.定義狀態模式

狀態模式

允許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。

這個描述的前半句:因爲這個模式將狀態封裝成爲獨立的類,並將動作委託到代表當前狀態的對象,我們知道行爲會隨着內部狀態而改變。糖果機提供了一個很好的例子:當糖果機實在"無硬幣"或者"有硬幣"的兩種狀態下,這時投入硬幣,會得到不同的行爲(機器接收硬幣或機器拒絕硬幣)。

這個描述的後半句:從客戶的視角看:如果說你使用的對象能夠完全改變它的行爲,那麼你會覺得,這個對象實際上是從別的類實例化而來的。然而,實際上,我們知道,我們是在使用組合通過簡單引用不同的狀態對象造成類改變的假象。

類圖

意圖

觀察這個類圖,我們發現和"策略模式"很是相像,但是差別在於它們的"意圖"不同。

狀態模式:

對於狀態模式而言,我們將一羣行爲封裝在狀態對象在,context(糖果機)的行爲隨時可委託到那些狀態對象中的額一個。隨着時間的流逝,當前狀態在狀態對象集合中游走改變,以反映出context內部的狀態,因此context的行爲也會跟着改變,但是context的客戶對於狀態對象瞭解不多,甚至渾然不覺。

我們把狀態模式想成是不用在context中放置許多條件判斷的替代方案。通過將行爲包裝進狀態對象中,我們可以通過在context內簡單的改變狀態對象來改變context的行爲。

策略模式:

對於策略模式而言,客戶通常主動指定Context所要組合的策略對象是哪一個。現在,固然策略模式讓我們具有彈性,能夠在運行時改變策略,但對於某個context對象來說,通常都只要一個最適當的策略對象。比方說,在第一章的策略模式中,有些鴨子(綠頭鴨)被設置成利用典型的飛翔行爲進行飛翔,而有些鴨子(例如橡皮鴨)使用的飛翔行爲只能讓他們貼地飛行。

一般來說我們把策略模式想成除了繼承之外的一種彈性替代方案。如果我們使用繼承定義了一個類的行爲,我們將被這個行爲困住,甚至要修改它都很難。有了策略模式,我們通過組合不同的對象來改變行爲。

7.問答

1)問:在GumballMachine中,狀態決定了下一個狀態應該是什麼。ConcreteState總是決定接下來狀態是什麼嗎?

答:並不總是如此,context也可以決定狀態轉換的流向。一般來說,當狀態轉換是固定的時候,就適合放在context中;然而當狀態轉換是更動態的時候,通常就會放在狀態類在(這就是本文的實現方法,例如,在GumballMachine中,由運行時糖果的數目來決定狀態要轉換到"無硬幣"還是"售空")。將狀態轉換放在狀態類中的缺點是:狀態類之間產生了依賴。在我們的GumballMachine實現中,我們試圖通過使用Context上的getter方法將依賴見到最小,而不是顯示的硬編碼具體的狀態類。狀態轉換放到哪一個類中,還會影響另一方面,就是我們究竟要對哪個類做"修改關閉",是Context還是狀態類)。

2)問:客戶會直接和狀態交互嗎?

答:不會。狀態是用在Context中代表它的內部狀態以及行爲的,所以只有Context纔會對狀態提出請求。客戶不會直接改變Context的狀態。全盤瞭解狀態時Context的工作。

3)問:如果在我們的程序中Context由許多實例,這些實例之間可以共享對象嗎?

答:絕對可以,事實上這是很常見的做法。但唯一的前提是,我們的狀態對象不能持有它們自己的內部狀態;否則就不能共享。

想要共享狀態,我們需要把每個狀態都指定到靜態的實例變量中。如果我們的狀態需要利用Context中的方法或者實例變量,我們能還需再每個handler()方法內傳入一個context的引用。

4)問:使用狀態模式似乎總是增加我們設計中類的數目。GumballMachine中,新版比舊版多了很多類。

答:是的,在個別的狀態類中封裝狀態行爲,結果總是增加這個設計中類的數目。這就是爲了要獲取彈性而付出的代價。除非我們的代碼時一次性的。可以用完就扔,但是這不可能,那麼其實狀態模式的設計是絕對值得的。其實真正重要的是我們暴露給客戶的類的數目,而且我們有辦法將這些額外的狀態全部都隱藏起來。舉例:如果我們有一個應用,它有很多狀態,但是我們決定不將這些狀態封裝在不同的對象中,那麼我們就必須設計巨大的、整塊的條件語句。這會讓我們的代碼不易維護和理解。通過使用許多對象,我們可以讓狀態變得乾淨。

8.需求變更具體實現

之前糖果公司要求有10%掉出兩個糖果,現在來實現這個需求。

GumballMachine:

public class GumballMachine {
 
	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;
		} 
	}
 
	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() {
		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();
	}

    public State getState() {
        return state;
    }

    public State getSoldOutState() {
        return soldOutState;
    }

    public State getNoQuarterState() {
        return noQuarterState;
    }

    public State getHasQuarterState() {
        return hasQuarterState;
    }

    public State getSoldState() {
        return soldState;
    }

    public State getWinnerState() {
        return winnerState;
    }
 
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

定義狀態類-人生贏家狀態:

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) {
			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 {
	
	//隨機數產生器
	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...");
		/*
		 * 顧客有10%機率獲勝
		 * 獲勝:進入認識贏家狀態
		 * 未獲勝:進入售出狀態
		 */
		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 void refill() { }
 
	public String toString() {
		return "waiting for turn of crank";
	}
}

測試代碼:

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);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);

		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();

		System.out.println(gumballMachine);
	}
}

輸出結果:

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...
A gumball comes rolling out the slot...
YOU'RE A WINNER! You got two gumballs for your quarter
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 7 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...
A gumball comes rolling out the slot...
YOU'RE A WINNER! You got two gumballs for your quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 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: 2 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...
Oops, out of gumballs!

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is sold out

9.需要改進的部分

在我們的SoldOutState和WinnerState這兩個狀態之間,有許多重複的代碼,我們可以考慮把State接口設計爲抽象類,然後將方法的默認行爲放在其中。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章