1. 程式人生 > >Head First設計模式讀書筆記九 第十章 狀態模式

Head First設計模式讀書筆記九 第十章 狀態模式

狀態模式例項

用Java設計糖果機吧 大致流程: 在這裡插入圖片描述 上圖中,有四種狀態:沒有硬幣,有硬幣,準備售出狀態以及糖果售罄狀態。而控制糖果機狀態轉換的則是各種動作(Action),這些動作分別是投入硬幣,超時判斷,轉動曲柄,判斷糖果決定是否售出。 GumballMachine類

public class GumballMachine {
	//define 4 states
	private final int  SOLD_OUT=0;
	private final int  NO_COIN=1;
	private final int  HAS_COIN=2;
	private final int  START_TO_SOLD=3;
	
	int stateNow = SOLD_OUT;
	int count = 0;
	public GumballMachine(int count) {
		if (count > 0){
			stateNow = NO_COIN;
		}
		this.count = count;
	}
	
	//define 4 actions
	public void insertCoin(){
		switch (stateNow) {
		case SOLD_OUT:
			System.out.println("機器售罄,請勿投幣"+getState());
			break;
		case NO_COIN:
			//正常case
			stateNow = HAS_COIN;
			System.out.println("你投入了硬幣"+getState());
			break;
		case HAS_COIN:
			System.out.println("你已經投過幣了,無法投幣"+getState());
			break;
		case START_TO_SOLD:
			System.out.println("請稍後投幣,機器處理中"+getState());
			break;
		default:
			System.out.println("unknown state");
			break;
		}
	}
	
	public void ejectCoin(){
		switch (stateNow) {
		case SOLD_OUT:
			System.out.println("你沒有投幣"+getState());
			break;
		case NO_COIN:
			System.out.println("你沒有投幣"+getState());
			break;
		case HAS_COIN:
			//正常case
			stateNow = NO_COIN;
			System.out.println("等待超時,退幣。或者你按下了退幣健"+getState());
			break;
		case START_TO_SOLD:
			System.out.println("你已經旋轉曲柄,機器已經準備售出,無法退幣"+getState());
			break;
		default:
			System.out.println("unknown state");
			break;
		}
	}
	
	public void turnCrank(){
		switch (stateNow) {
		case SOLD_OUT:
			System.out.println("糖果售罄,請勿操作");
			break;
		case NO_COIN:
			System.out.println("請先投幣");
			break;
		case HAS_COIN:
			//正常case
			stateNow = START_TO_SOLD;
			System.out.println("你轉動了曲柄"+getState());
			break;
		case START_TO_SOLD:
			System.out.println("機器處理中,請勿操作");
			break;
		default:
			System.out.println("unknown state");
			break;
		}
	}
	
	//發放糖果
	public void dispense(){
		switch (stateNow) {
		case SOLD_OUT:
			System.out.println("請正確操作本機");
			break;
		case NO_COIN:
			System.out.println("請先投幣");
			break;
		case HAS_COIN:
			System.out.println("先轉動曲柄");
			break;
		case START_TO_SOLD:
			//正常case
			count --;
			if(count > 0){
				stateNow = NO_COIN;
				System.out.println("售出糖果"+getState());
			}else{
				stateNow = SOLD_OUT;
				System.out.println("售出糖果"+getState());
			}
			
			break;
		default:
			System.out.println("unknown state");
			break;
		}
	}
	
	public String getState(){
		switch (stateNow) {
		case SOLD_OUT:
			return (" 目前狀態:糖果售罄狀態");
		case NO_COIN:
			return  (" 目前狀態:沒有硬幣狀態");
		case HAS_COIN:
			return (" 目前狀態:有硬幣狀態");
		case START_TO_SOLD:
			return (" 目前狀態:準備售出狀態");

		default:
			return " 目前狀態:未知狀態";
		}
	}
}

測試類

public class Test {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(3);
		gumballMachine.insertCoin();
		gumballMachine.turnCrank();
		gumballMachine.dispense();
		System.out.println("================");
		gumballMachine.dispense();
		gumballMachine.turnCrank();
		gumballMachine.insertCoin();
		System.out.println("================");

		gumballMachine.insertCoin();
		gumballMachine.turnCrank();
		gumballMachine.dispense();
		System.out.println("================");
		gumballMachine.insertCoin();
		gumballMachine.turnCrank();
		gumballMachine.dispense();
		System.out.println("================");
		gumballMachine.insertCoin();
		gumballMachine.turnCrank();
		gumballMachine.dispense();
	}
}

測試結果

你投入了硬幣 目前狀態:有硬幣狀態
你轉動了曲柄 目前狀態:準備售出狀態
售出糖果 目前狀態:沒有硬幣狀態
================
請先投幣
請先投幣
你投入了硬幣 目前狀態:有硬幣狀態
================
你已經投過幣了,無法投幣 目前狀態:有硬幣狀態
你轉動了曲柄 目前狀態:準備售出狀態
售出糖果 目前狀態:沒有硬幣狀態
================
你投入了硬幣 目前狀態:有硬幣狀態
你轉動了曲柄 目前狀態:準備售出狀態
售出糖果 目前狀態:糖果售罄狀態
================
機器售罄,請勿投幣 目前狀態:糖果售罄狀態
糖果售罄,請勿操作
請正確操作本機

新加功能,中獎機制

客戶希望給機器新增中獎機制,即使用者每次買糖果,有10%的機率獲得2個糖果. 我們可以看到GumballMachine這個類太過複雜,狀態轉換全都包含其中,因此我們需要做一些改變了。 1.定義狀態介面

public interface State {
	//define 4 actions
	public void insertCoin();
	
	public void ejectCoin();
	
	public void turnCrank();
	
	public void dispense();
	
	public String getState();
}

2.重新實現GumballMachine類

public class GumballMachine {
	// define 4 states
	State soldOutState;
	State hasCoinState;
	State noCoinState;
	State soldState;
	State winnerState;
	int count = 0;
	State stateNow = soldOutState;

	public GumballMachine(int count) {
		soldOutState = new SoldOutState(this);
		hasCoinState = new HasCoinState(this);
		noCoinState = new NoCoinState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);
		if (count > 0) {
			stateNow = noCoinState;
		}
		this.count = count;
	}

	public void insertCoin() {
		stateNow.insertCoin();
	}

	public void ejectCoin() {
		stateNow.ejectCoin();
	}

	public void turnTrunk() {
		stateNow.turnCrank();
		stateNow.dispense();
	}

	void setState(State state) {
		stateNow = state;
	}

	void releaseBall() {
		if (count > 0) {
			count--;
			System.out.println("A gumball rolling out the slot...");
		} else {
			System.out.println("release ball failed");
		}
	}

	public State getSoldOutState() {
		return soldOutState;
	}

	public State getHasCoinState() {
		return hasCoinState;
	}

	public State getNoCoinState() {
		return noCoinState;
	}

	public State getSoldState() {
		return soldState;
	}

	public State getWinnerState() {
		return winnerState;
	}

	public int getCount() {
		return count;
	}

	public State getStateNow() {
		return stateNow;
	}
	
	
}

3.實現狀態類: 將原先

	private final int  SOLD_OUT=0;
	private final int  NO_COIN=1;
	private final int  HAS_COIN=2;
	private final int  START_TO_SOLD=3;

的設計改到類中;這樣,原先我們是操作時需要判斷當前狀態。而現在我們是狀態被固定,需要看能進行哪些操作(狀態控制動作)。而且,我們不需要主動控制狀態,狀態由程式碼自動控制。

public class HasCoinState implements State {
	Random randomWinner = new Random(System.currentTimeMillis());
	GumballMachine gumballMachine;
	public HasCoinState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}

	public void insertCoin() {
		System.out.println("你已經投過幣了");
	}

	public void ejectCoin() {
		System.out.println("你請求了退幣");
		gumballMachine.setState(gumballMachine.getNoCoinState());
	}

	public void turnCrank() {
		System.out.println("你轉動了曲柄");
		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("請先轉動曲柄");
	}

	public String getState() {
		return this.getClass().getName();
	}

}
public class NoCoinState implements State {
	GumballMachine gumballMachine;
	public NoCoinState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}

	public void insertCoin() {
		System.out.println("你投幣了");
		gumballMachine.setState(gumballMachine.getHasCoinState());
	}

	public void ejectCoin() {
		System.out.println("你還沒有投幣");
	}

	public void turnCrank() {
		System.out.println("你還沒有投幣,轉杆無效");
	}

	public void dispense() {
		System.out.println("還沒有投幣,不能發放糖果");
	}

	public String getState() {
		return this.getClass().getName();
	}

}

public class SoldOutState implements State {
	GumballMachine gumballMachine;
	public SoldOutState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	public void insertCoin() {
		System.out.println("糖果售罄,請勿操作");
	}

	public void ejectCoin() {
		System.out.println("糖果售罄,請勿操作");
	}

	public void turnCrank() {
		System.out.println("糖果售罄,請勿操作");
	}

	public void dispense() {
		System.out.println("糖果售罄,請勿操作");
	}

	public String getState() {
		return this.getClass().getName();
	}

}

public class SoldState implements State {
	GumballMachine gumballMachine;
	public SoldState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}

	public void insertCoin() {
		System.out.println("機器處理中,請稍後");
	}

	public void ejectCoin() {
		System.out.println("機器已經處理,無法操作");
	}

	public void turnCrank() {
		System.out.println("你已經轉動過曲柄了");
	}

	public void dispense() {
		gumballMachine.releaseBall();
		if(gumballMachine.count>0){
			gumballMachine.setState(gumballMachine.getNoCoinState());
			System.out.println("發放糖果");
		}else{
			System.out.println("發放糖果完畢,糖果售罄");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}

	public String getState() {
		return this.getClass().getName();
	}

}

public class WinnerState implements State {
	GumballMachine gumballMachine;
	public WinnerState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}

	public void insertCoin() {
		System.out.println("機器正在處理,請稍後投幣");
	}

	public void ejectCoin() {
		System.out.println("機器正在處理,無法退幣");
	}

	public void turnCrank() {
		System.out.println("機器正在處理,無法操作");
	}

	public void dispense() {
		gumballMachine.releaseBall();
		if(gumballMachine.getCount() == 0){
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}else{
			System.out.println("你獲得了獎勵");
			gumballMachine.releaseBall();
			if(gumballMachine.getCount() == 0){
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}else{
				gumballMachine.setState(gumballMachine.getNoCoinState());
			}
		}
	}

	public String getState() {
		return null;
	}

}

4.修改測試類

public class Test {

	public static void main(String[] args) {
		GumballMachine gumballMachine = new GumballMachine(5);
		for(int i =0;i<5; i++){
			gumballMachine.insertCoin();
			gumballMachine.turnTrunk();
			System.out.println(gumballMachine.getStateNow().getState()+"---------------");
		}
	}
}

5.測試結果:

你投幣了
你轉動了曲柄
A gumball rolling out the slot...
發放糖果
bean.NoCoinState---------------
你投幣了
你轉動了曲柄
A gumball rolling out the slot...
發放糖果
bean.NoCoinState---------------
你投幣了
你轉動了曲柄
A gumball rolling out the slot...
發放糖果
bean.NoCoinState---------------
你投幣了
你轉動了曲柄
A gumball rolling out the slot...
你獲得了獎勵
A gumball rolling out the slot...
bean.SoldOutState---------------
糖果售罄,請勿操作
糖果售罄,請勿操作
糖果售罄,請勿操作
bean.SoldOutState---------------

狀態模式的簡單分析

狀態模式存在幾個簡單部分: context state Context意為上下文,在這裡即是GumballMachine,而state在這裡就是各個state的實現類了。 可以看到GumballMachine中包含了各類狀態的引用例項,而GumballMachine也存在自己當前狀態的例項 GumballMachine類的操作看上去是交給了State實現類,但實際上,State中存在Context的引用,State實現類在操作時通過GumballMachine引用來控制GumballMachine狀態。即下圖:

在這裡插入圖片描述