經過改變對象內部狀態幫助對象控制行爲
以一個簡單例子說明,假設咱們要模擬製造一臺糖果機器,對方給你的機器流程圖以下
編程
ok,咱們如今簡單分析這張狀態圖,可將狀態提取出來:有硬幣,無硬幣,售出糖果,糖果售空四個狀態,行爲動做提取出來:投入一個硬幣,退回一個硬幣,轉動曲柄,發放糖果四個行爲,固然還有一些特殊狀況,具體狀況參考代碼設計模式
代碼以下(參考)app
GumballMachinedom
public class GumballMachine { /** * 狀態模式 糖果機的狀態都用一個不一樣整數表示 * */ final static int SOLD_OUT = 0;// 糖果售空 final static int NO_QUARTER = 1;// 沒有投25分錢 final static int HAS_QUARTER = 2;// 投了25分錢 final static int SOLD = 3;// 糖果售出 // 當前狀態 int state = SOLD_OUT; // 用來追蹤糖果數量 int count = 0; public GumballMachine(int count) { this.count = count; if (count > 0) { // 若是有糖果,機器變爲沒有投25分錢狀態(待購買狀態) state = NO_QUARTER; } } // 投入25分錢方法 public void insertQuarter() { if (state == HAS_QUARTER) { System.out.println("已經投過幣了,不能再投了"); } else if (state == NO_QUARTER) { state = HAS_QUARTER; System.out.println("請投入一枚硬幣"); } else if (state == SOLD_OUT) { System.out.println("不能再投幣了,機器已經售空了"); } else if (state == SOLD) { System.out.println("請稍等,正在爲你出糖果"); } } // 退出25分錢方法 public void ejectQuarter() { // 1\當客戶要退錢是 if (state == HAS_QUARTER) { System.out.println("零錢退回"); state = NO_QUARTER;// 進入沒投幣狀態 } else if (state == NO_QUARTER) { System.out.println("你沒有投入硬幣"); } else if (state == SOLD) { System.out.println("對不起,你已經轉動了曲軸,沒法退幣了"); } else if (state == SOLD_OUT) { System.out.println("糖果售空,沒法退幣"); } } // 轉動曲軸方法(顧客) public void turnCrank() { if (state == HAS_QUARTER) { System.out.println("請轉動曲軸"); state = SOLD;// 狀態變爲售出狀態 dispense();// 調用發放糖果方法 } else if (state == SOLD) { System.out.println("轉動兩次也不給你糖果"); } else if (state == NO_QUARTER) { System.out.println("請先投硬幣"); } else if (state == SOLD_OUT) { System.out.println("機器售空"); } } // 發放糖果方法 private void dispense() { if (state == SOLD) { System.out.println("一包糖果出來了"); count -= 1; // 當糖果數量0時候呢? if (count == 0) { System.out.println("哎呀,糖果售空了~~"); state = SOLD_OUT; } else { state = NO_QUARTER; } } else if (state == NO_QUARTER) { System.out.println("你須要投入硬幣"); } else if (state == SOLD_OUT) { System.out.println("沒有糖果了"); } else if (state == HAS_QUARTER) { System.out.println("沒有糖果了"); } } // 重寫toString()方法輸入糖果機信息 @Override public String toString() { StringBuffer result = new StringBuffer(); result.append("歡迎使用糖果機\n"); result.append("糖果數量:" + count + "\n"); result.append("當前糖果機狀態:" + state); return result.toString(); } }
TestMainide
public class TestMain { public static void main(String[] args) { // 裝了5個糖 GumballMachine gumballMachine = new GumballMachine(5); System.out.println(gumballMachine + "\n");// 打印糖果機信息 gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.turnCrank();// 轉動曲柄 System.out.println(gumballMachine + "\n");// 打印糖果機信息 gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.ejectQuarter();// 要求退幣 gumballMachine.turnCrank();// 轉動曲柄,拿不到糖果 System.out.println(gumballMachine + "\n");// 打印糖果機信息 gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.turnCrank();// 轉動曲柄(拿到糖果) gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.turnCrank();// 轉動曲柄(拿到糖果) gumballMachine.ejectQuarter();// 要求機器退錢 System.out.println(gumballMachine + "\n");// 打印糖果機信息 gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.insertQuarter();// 投入硬幣 gumballMachine.turnCrank();// 轉動曲柄(拿到糖果) // 下面開始壓力測試 //gumballMachine.insertQuarter();// 投入硬幣 //gumballMachine.turnCrank();// 轉動曲軸 //gumballMachine.insertQuarter();// 投入硬幣 //gumballMachine.turnCrank();// 轉動曲軸 System.out.println(gumballMachine + "\n");// 打印糖果機信息 } }
效果圖(無壓力測試)
測試
效果圖(壓力測試)
this
效果能夠快速實現,但這樣的代碼顯然還有不足之處,假設有個新的需求:增長一個幸運用戶,就是購買者有10%的機率能夠一次買到兩顆糖果,這又要如何實現呢?先看流程圖
spa
按照以前的1.0代碼加新中獎者功能,顯然不合適,要在每一個方法裏寫入,顯然太麻煩,這裏就須要重構代碼了。咱們能夠試着行爲封裝起來,也能夠把糖果機器具體一下.咱們要作的是以下:
一、首先定義一個State接口,在這個接口內,糖果機的每一個動做都有一個對應的方法。
二、而後爲機器中的每一個狀態實現類。這些類將負責在對應的狀態下進行機器的行爲。
三、重構舊代碼,取而代之方式是將動做委託給狀態類。設計
具體實現請看代碼
結構圖
code
State
package Interface; public interface State { /** * 將四種狀態抽象出來成基類 */ // 投入硬幣 public void insertQuarter(); // 退回硬幣 public void ejectQuarter(); // 轉動曲柄 public void turnCrank(); // 發放糖果 public void dispense(); }
GumballMachine
package Machine; public class GumballMachine { /** * 不在使用靜態整數,都是用對象 * */ State soldOutState;// 糖果售空 State noQuarterState;// 沒有投幣 State hasQuarterState;// 投了幣 State soldState;// 糖果售出 State winnerState;// 中獎狀態 // 當前狀態,持有的是(糖果售空)對象 State state = soldOutState; int count = 0; // numberGumball構造器取得糖果的初始數目後,並把它存放在一個實例變量中 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(); } // 變化狀態 public void setState(State state) { this.state = state; } // 糖果出貨 public void releaseBall() { System.out.println("一包糖果出來了"); if (count != 0) { count = count - 1; } } public int getCount() { return count; } 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("歡迎使用糖果機\n"); result.append("糖果數量:" + count + "\n"); result.append("當前糖果機狀態: " + state + "\n"); return result.toString(); } }
各個行爲類實現
HasQuarterState
package State_Implements; public class HasQuarterState implements State { /** * 投幣的實現類 * * @param gumballMachine */ // 增長一個隨機數產生器,10%機會 Random randomWinner = new Random(System.currentTimeMillis()); GumballMachine gumballMachine; public HasQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("你已經投過幣了,不能再投了"); } public void ejectQuarter() { System.out.println("硬幣退出"); // 退出硬幣後,狀態變爲沒有硬幣(待購買)狀態 gumballMachine.setState(gumballMachine.getNoQuarterState()); } public void turnCrank() { System.out.println("轉動....."); int winner = randomWinner.nextInt(10);// 產生0-9隨機數,當是0而且還有糖果的時候中獎了 if ((winner == 0) && (gumballMachine.getCount() > 1)) { gumballMachine.setState(gumballMachine.getWinnerState()); } else { gumballMachine.setState(gumballMachine.getSoldState()); } } public void dispense() { System.out.println("沒有糖果出來"); } public String toString() { return "等待使用曲柄"; } }
NoQuarterState
package State_Implements; /** * 沒有投幣狀態實現, * 都統統要實現狀態基類 * @author Joy * */ public class NoQuarterState implements State { GumballMachine gumballMachine; public NoQuarterState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("你投了一個硬幣"); gumballMachine.setState(gumballMachine.getHasQuarterState()); } public void ejectQuarter() { System.out.println("你沒有投幣,沒法退幣~~"); } public void turnCrank() { System.out.println("你沒有投幣,沒法繼續~~"); } public void dispense() { System.out.println("你須要投幣才能買糖果"); } public String toString() { return "正在運營"; } }
SoldOutState
package State_Implements; /** * 售空狀態 * * @author Joy * */ public class SoldOutState implements State { GumballMachine gumballMachine; public SoldOutState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("抱歉,你不能在投幣了,糖果售空了"); } public void ejectQuarter() { System.out.println("抱歉,糖果售空,沒法退幣"); } public void turnCrank() { System.out.println("抱歉,轉動曲柄無效,糖果售空了"); } public void dispense() { System.out.println("糖果售空了"); } public String toString() { return "糖果售空"; } }
SoldState
package State_Implements; /** * 賣出糖果狀態 * * @author Joy * */ public class SoldState implements State { GumballMachine gumballMachine; public SoldState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("請稍等,糖果正出貨"); } public void ejectQuarter() { System.out.println("抱歉,你已使用曲柄,沒法退幣"); } public void turnCrank() { System.out.println("曲柄不可重複使用"); } public void dispense() { // 調用糖果出貨方法 gumballMachine.releaseBall(); if (gumballMachine.getCount() > 0) { gumballMachine.setState(gumballMachine.getNoQuarterState()); } else { System.out.println("哎呀,糖果售空了"); gumballMachine.setState(gumballMachine.getSoldOutState()); } } public String toString() { return "一個糖果已出貨"; } }
WinnerState
package State_Implements; public class WinnerState implements State { GumballMachine gumballMachine; public WinnerState(GumballMachine gumballMachine) { this.gumballMachine = gumballMachine; } public void insertQuarter() { System.out.println("不能投幣"); } public void ejectQuarter() { System.out.println("不能投幣"); } public void turnCrank() { System.out.println("不能使用曲柄"); } public void dispense() { System.out.println("恭喜中獎了,你獲得兩個糖果~~"); gumballMachine.releaseBall(); // 此時糖果機器裏只有一顆時候,那麼第二顆就出不來,狀態變爲售空狀態 if (gumballMachine.getCount() == 0) { gumballMachine.setState(gumballMachine.getSoldOutState()); } else { gumballMachine.releaseBall(); if (gumballMachine.getCount() > 0) { gumballMachine.setState(gumballMachine.getNoQuarterState()); } else { System.out.println("哎呀,糖果售空了"); gumballMachine.setState(gumballMachine.getSoldOutState()); } } } public String toString() { return "你是中獎者,獲得兩個糖果"; } }
GumballMachineTestDrive 測試類
package TestMain; import Machine.GumballMachine; public class GumballMachineTestDrive { public static void main(String[] args) { // 一開始5顆糖 GumballMachine gumballMachine = new GumballMachine(5); System.out.println(gumballMachine + "\n"); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); System.out.println(gumballMachine + "\n");// 輸出狀態 gumballMachine.insertQuarter(); gumballMachine.turnCrank(); gumballMachine.insertQuarter(); gumballMachine.turnCrank(); System.out.println(gumballMachine + "\n");// 輸出狀態 } }
效果圖
狀態模式定義:容許對象在內部狀態改變時改變它的行爲,對象看起來好像修改了它的類。
注:這個模式是將狀態封裝成爲獨立地類,並將動做委託給表明當前狀態的對象。
狀態模式類圖以下,後來本人在回顧時發現狀態模式和策略模式類圖很類似,有興趣朋友能夠將二者去比較不一樣
感謝你看到這裏,狀態模式到這裏就結束了,本人文筆隨便,如有不足或錯誤之處望給予指點,90度彎腰~~~很快我會發佈下一個設計模式的內容,生命不息,編程不止!
參考書籍:《Head First 設計模式》