《Head First 设计模式》第十章-状态模式 状态模式
狀態(tài)模式
策略模式和狀態(tài)模式是雙胞胎,在出生時才分開。你已經(jīng)知道,策略模式是圍繞可以互換的算法來創(chuàng)建成功業(yè)務(wù)的,然而,狀態(tài)走的是更崇高的路,它通過改變對象內(nèi)部的狀態(tài)來幫助對象控制自己的行為。
定義狀態(tài)模式
先看看定義:狀態(tài)模式允許對象在內(nèi)部狀態(tài)改變時改變它的行為,對象看起來好像修改了它的類
例題
自動糖果售賣機(jī),糖果機(jī)的控制器需要的工作流程如下圖
從上面的狀態(tài)圖中可以找到所有的狀態(tài):
我們可以創(chuàng)建一個實例變量來持有目前的狀態(tài),然后定義每個狀態(tài)的值:
| 1 2 3 4 5 6 7 | //每個狀態(tài)用不同的值表示 final static int SOLD_OUT=0;//售罄 final static int NO_QUARTER=1;//沒有投幣 final static int HAS_QUARTER=2;//已投幣 final static int SOLD=3;//售出糖果 //實例變量持有當(dāng)前狀態(tài),只要改變變量值狀態(tài)也會隨之改變 int state =SOLD_OUT; |
現(xiàn)在,我們將所有系統(tǒng)中可以發(fā)生的動作整合起來:
“投入25分錢”,“退回25分錢”,“轉(zhuǎn)動曲柄”,“發(fā)放糖果”
這些動作是糖果機(jī)的接口,這是你能對糖果機(jī)做的事情,
調(diào)用任何一個動作都會造成狀態(tài)的轉(zhuǎn)換,
發(fā)放糖果更多是糖果機(jī)的內(nèi)部動作,機(jī)器自己調(diào)用自己。
我們創(chuàng)建一個類,它的作用就像是一個狀態(tài)機(jī),每一個動作,我們都創(chuàng)建了一個對應(yīng)的方法,這些方法利用條件語句來決定在每個狀態(tài)內(nèi)什么行為是恰當(dāng)?shù)摹1热鐚Α巴度?5分錢”這個動作來說,我們可以把對應(yīng)方法寫成下面的樣子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public void insertQuarter(){ ????if(state==HAS_QUARTER){ ????????//每個狀態(tài)對應(yīng)的行為 ?????????...... ????}else if(state==SOLD_OUT){ ????????...... ????}else if(state ==SOLD){ ????????...... ????}else if(state==NO_QUARTER){ ????????state=HAS_QUARTER;//狀態(tài)轉(zhuǎn)換 ????????...... ????} } |
初步代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | 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;//存儲糖果數(shù)量 ????public? GumballMachine(int count){ ????????this.count=count; ????????if(count>0){ ????????????state=NO_QUARTER; ????????} ????} ????//當(dāng)有25分錢投入,就會執(zhí)行這個方法 ????public void insertQuarter(){ ????????if(state==HAS_QUARTER){ ????????????System.out.println("如果已投入過25分錢,我們就告訴顧客"); ????????}else if(state==NO_QUARTER){ ????????????state=HAS_QUARTER; ????????????System.out.println("如果是在“沒有25分錢”的狀態(tài)下,我們就接收25分錢," +"并將狀態(tài)轉(zhuǎn)換到“有25分錢”的狀態(tài)"); ????????}else if(state ==SOLD_OUT){ ????????????System.out.println("如果糖果已經(jīng)售罄,我們就拒絕收錢"); ????????}else if(state==SOLD){ ????????????System.out.println("如果顧客剛才買了糖果,就需要稍等一下,好讓狀態(tài)轉(zhuǎn)換完畢。" +"恢復(fù)到“沒有25分錢”的狀態(tài)"); ????????????state=NO_QUARTER; ????????} ????} ????//如果顧客試著退回25分錢就執(zhí)行這個方法 ????public void ejectQuarter(){ ????????if(state==HAS_QUARTER){ ????????????System.out.println("如果有25分錢,我們就把錢退出來,回到“沒有25分錢”的狀態(tài)"); ????????????state=NO_QUARTER; ????????}else if(state==NO_QUARTER){ ????????????System.out.println("如果沒有25分錢的話,當(dāng)然不能退出25分錢"); ????????}else if(state ==SOLD){ ????????????System.out.println("顧客已經(jīng)轉(zhuǎn)動曲柄就不能再退錢了,他已經(jīng)拿到糖果了"); ????????}else if(state==SOLD_OUT){ ????????????System.out.println("如果糖果售罄,就不能接受25分錢,當(dāng)然也不可能退錢"); ????????} ????} ????//顧客試著轉(zhuǎn)動曲柄 ????public void turnCrank(){ ????????if(state==SOLD){ ????????????System.out.println("別想騙過機(jī)器拿兩次糖果"); ????????}else if(state==NO_QUARTER){ ????????????System.out.println("我們需要先投入25分錢"); ????????}else if(state ==SOLD_OUT){ ????????????System.out.println("我們不能給糖果,已經(jīng)沒有任何糖果了"); ????????}else if(state==HAS_QUARTER){ ????????????System.out.println("成功,他們拿到糖果了," +"改變狀態(tài)到“售出糖果”然后調(diào)用機(jī)器的disoense()方法"); ????????????state=SOLD; ????????????dispense(); ????????} ????} ????//調(diào)用此方法,發(fā)放糖果 ????public void dispense(){ ????????if(state==SOLD){ ????????????System.out.println("我們正在“出售糖果”狀態(tài),給他們糖果"); ????????????count=count-1; ????????????/* ????????????我們在這里處理“糖果售罄”的情況,如果這是最后一個糖果,將機(jī)器的狀態(tài)設(shè)置到“糖果售罄”否則就回到“沒有25分錢”的狀態(tài) ?????????????*/ ????????????if(count==0){ ????????????????System.out.println(); ????????????????state=SOLD_OUT; ????????????}else{ ????????????????state=NO_QUARTER; ????????????} ????????}else if(state==SOLD_OUT){ ????????????System.out.println("這些都不應(yīng)該發(fā)生,但是如果做了,就得到錯誤提示"); ????????}else if(state ==HAS_QUARTER){ ????????????System.out.println("這些都不應(yīng)該發(fā)生,但是如果做了,就得到錯誤提示"); ????????}else if(state==NO_QUARTER){ ????????????System.out.println("這些都不應(yīng)該發(fā)生,但是如果做了,就得到錯誤提示"); ????????} ????} } |
盡管程序完美運(yùn)行,但還是躲不掉需求變更的命運(yùn)
現(xiàn)在糖果公司要求:當(dāng)曲柄被轉(zhuǎn)動時,有10%的幾率掉下來的是兩個糖果。(氪金扭蛋)
再回看一下我們的初步代碼,想要實現(xiàn)新的需求將會變得非常麻煩:
- 必須新增一個中獎的“贏家”狀態(tài)。
- 必須在每一個方法添加新的判斷條件來處理“贏家”狀態(tài)。
- 轉(zhuǎn)動把手的方法中還需要檢查目前狀態(tài)是否是“贏家”再決定切換到“贏家”狀態(tài)行為還是正常出售行為。
在現(xiàn)有代碼基礎(chǔ)上做增加將會很麻煩,也不利與以后的維護(hù),擴(kuò)展性差。
回顧一下第一章的策略模式中的設(shè)計原則:
找出應(yīng)用中可能需要變化之處,把他們獨立出來
將狀態(tài)獨立出來,封裝成一個類,都實現(xiàn)State接口,類圖如下:
新的設(shè)計想法如下:
代碼
定義一個State接口
| 1 2 3 4 5 6 | public interface State { ????public void insertQuarter();//投幣 ????public void ejectQuarter();//退幣 ????public void turnCrank();//轉(zhuǎn)動出貨把手 ????public void dispense();//出售 } |
為機(jī)器的每個狀態(tài)實現(xiàn)狀態(tài)類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | //未投幣狀態(tài) public class NoQuarterState?implements State { ????GumballMachine gumballMachine; ????public NoQuarterState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("你投入一枚硬幣"); ????????gumballMachine.setState(gumballMachine.getHasQuarterState());//狀態(tài)轉(zhuǎn)換為已投幣狀態(tài) ????} ????public void ejectQuarter() { ????????System.out.println("你未投幣,無法退錢"); ????} ????public void turnCrank() { ????????System.out.println("未投幣,請先投幣"); ????} ????public void dispense() { ????????System.out.println("請先投幣"); ????} } ? //已投幣狀態(tài) 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("已投幣,無法再接收投幣"); ????} ????public void ejectQuarter() { ????????System.out.println("已退幣"); ????????gumballMachine.setState(gumballMachine.getNoQuarterState()); ????} ????public void turnCrank() { ????????System.out.println("已轉(zhuǎn)動把手,糖果出售中。。。。"); ????????int winner=randomWinner.nextInt(10);//隨機(jī)數(shù)生成,用以標(biāo)記“贏家”狀態(tài) ????????if((winner==0)&&(gumballMachine.getCount()>1)) ????????????gumballMachine.setState(gumballMachine.getWinnerState()); ????????else ????????????gumballMachine.setState(gumballMachine.getSoldState()); ????} ????public void dispense() { ????????System.out.println("機(jī)器中已經(jīng)沒有糖果可以出售了!"); ????} } ? //出售狀態(tài) public class SoldState?implements State { ????GumballMachine gumballMachine; ????public SoldState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("請等候,正在初始化機(jī)器中"); ????} ????public void ejectQuarter() { ????????System.out.println("抱歉,您已轉(zhuǎn)動把手獲得了糖果,無法退幣"); ????} ????public void turnCrank() { ????????System.out.println("您重復(fù)轉(zhuǎn)動把手,無法再獲取更多糖果"); ????} ????public void dispense() { ????????gumballMachine.releaseBall();//出貨,糖果-1 ????????if(gumballMachine.getCount()>0) ????????????gumballMachine.setState(gumballMachine.getNoQuarterState()); ????????else { ????????????System.out.println("糖果已售完"); ????????????gumballMachine.setState(gumballMachine.getSoldOutState()); ????????}??? ????} } ? //售罄狀態(tài) public class SoldOutState?implements State { ????GumballMachine gumballMachine; ????public SoldOutState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("此機(jī)器的糖果已售完,不接收投幣"); ????} ????public void ejectQuarter() { ????????System.out.println("未投幣,退幣失敗"); ????} ????public void turnCrank() { ????????System.out.println("糖果已售完,轉(zhuǎn)動把手也不會有糖果出來的"); ????} ????public void dispense() { ????????System.out.println("機(jī)器中已無糖果"); ????} } ? //贏家狀態(tài) public class WinnerState?implements State { ????GumballMachine gumballMachine; ????public WinnerState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("請等候,正在初始化機(jī)器中"); ????} ????public void ejectQuarter() { ????????System.out.println("抱歉,您已轉(zhuǎn)動把手獲得了糖果"); ????} ????public void turnCrank() { ????????System.out.println("您重復(fù)轉(zhuǎn)動把手,無法再獲取更多糖果"); ????} ????public void dispense() { ????????System.out.println("恭喜你成為幸運(yùn)兒,你將額外獲得一個免費(fèi)糖果"); ????????gumballMachine.releaseBall();//出貨,糖果-1 ????????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()); ????????????} ????????} ????} } |
糖果機(jī)類:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | 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;//先判斷條件再改變狀態(tài) ????} ????//將動作委托到狀態(tài)類 ????public void insterQuarter() { ????????state.insertQuarter(); ????} ????public void ejectQuarter() { ????????state.ejectQuarter(); ????} ????public void turnCrank() { ????????state.turnCrank(); ????????state.dispense(); ????} ????//獲取當(dāng)前狀態(tài) ????public State getHasQuarterState() { ????????return hasQuarterState; ????} ????//改變狀態(tài) ????public void setState(State state) { ????????this.state=state; ????} ????public void releaseBall() { ????????System.out.println("糖果從出口售出"); ????????if(count!=0) ????????????count-=1; ????} ????public State getSoldOutState() { ????????return soldOutState; ????} ????public State getNoQuarterState() { ????????return noQuarterState; ????} ????public State getSoldState() { ????????return soldState; ????} ????//獲取糖果機(jī)中糖果數(shù)量 ????public int getCount() { ????????return count; ????} ? ????public State getWinnerState() { ????????return winnerState; ????} ????public String toString() { ????????// TODO 自動生成的方法存根 ????????String s="剩余糖果:"+count; ????????return s; ????} } |
以上就是用狀態(tài)模式實現(xiàn)的,仔細(xì)觀察你會發(fā)現(xiàn)狀態(tài)模式其實和策略模式很像,來看看狀態(tài)模式的類圖:
狀態(tài)模式的類圖其實和策略模式完全一樣!
狀態(tài)模式與策略模式
這兩個模式的差別在于它們的“意圖”
- 以狀態(tài)模式而言,我們將一群行為封裝在狀態(tài)對象中,context的行為隨時可委托到那些狀態(tài)對象中的一個,隨著時間而流逝,當(dāng)前狀態(tài)在狀態(tài)對象集合中游走改變,以反映出context內(nèi)部的狀態(tài),因此,context的行為也會跟著改變,但是context的客戶對于狀態(tài)對象了解不多,甚至根本是渾然不覺。
- 以策略模式而言,客戶通常主動指定Context所要組合的策略對象時哪一個。現(xiàn)在,固然策略模式讓我們具有彈性,能夠在運(yùn)行時改變策略,但對于某個context對象來說,通常都只有一個最適當(dāng)?shù)牟呗詫ο蟆?/li>
- 一般的,我們把策略模式想成是除了繼承之外的一種彈性替代方案,如果你使用繼承定義了一個類的行為,你將被這個行為困住,是指要修改它都很難,有了策略模式,你可以通過組合不同的對象來改變行為。
- 我們把狀態(tài)模式想成是不用在context中放置許多條件判斷的替代方案,通過將行為包裝進(jìn)狀態(tài)對象中,你可以通過在context內(nèi)簡單地改變狀態(tài)對象來改變context的行為。
模式區(qū)分
狀態(tài)模式:封裝基于狀態(tài)的行為,并將行為委托到當(dāng)前狀態(tài)
策略模式:將可以互換的行為封裝起來。然后使用委托的方法,覺得使用哪一個行為
模板方法模式:由子類決定如何實現(xiàn)算法中的某些步驟
要點
(1)狀態(tài)模式允許一個對象基于內(nèi)部狀態(tài)而擁有不同的行為。
(2)和程序狀態(tài)機(jī)(PSM)不同,狀態(tài)模式用類來表示狀態(tài)。
(3)Context會將行為委托給當(dāng)前狀態(tài)對象。
(4)通過將每一個狀態(tài)封裝進(jìn)一個類,我們把以后需要做的任何改變局部化了。
(5)狀態(tài)模式和策略模式有相同的類圖,但是他們的意圖不同。
(6)策略模式通常會用行為或算法配置Context類。
(7)狀態(tài)模式允許Context隨著狀態(tài)的改變而改變行為。
(8)狀態(tài)轉(zhuǎn)換可以有State類或Context類控制。
(9)使用狀態(tài)模式通常會導(dǎo)致設(shè)計中類的數(shù)目大量增加。
(10)狀態(tài)欄可以被多個Context實例共享。
總結(jié)
以上是生活随笔為你收集整理的《Head First 设计模式》第十章-状态模式 状态模式的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode414. 第三大的数
- 下一篇: redis——数据结构(字典、链表、字符