《Head First 设计模式》第十章-状态模式 状态模式
狀態(tài)模式
策略模式和狀態(tài)模式是雙胞胎,在出生時(shí)才分開(kāi)。你已經(jīng)知道,策略模式是圍繞可以互換的算法來(lái)創(chuàng)建成功業(yè)務(wù)的,然而,狀態(tài)走的是更崇高的路,它通過(guò)改變對(duì)象內(nèi)部的狀態(tài)來(lái)幫助對(duì)象控制自己的行為。
定義狀態(tài)模式
先看看定義:狀態(tài)模式允許對(duì)象在內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來(lái)好像修改了它的類
例題
自動(dòng)糖果售賣(mài)機(jī),糖果機(jī)的控制器需要的工作流程如下圖
從上面的狀態(tài)圖中可以找到所有的狀態(tài):
我們可以創(chuàng)建一個(gè)實(shí)例變量來(lái)持有目前的狀態(tài),然后定義每個(gè)狀態(tài)的值:
| 1 2 3 4 5 6 7 | //每個(gè)狀態(tài)用不同的值表示 final static int SOLD_OUT=0;//售罄 final static int NO_QUARTER=1;//沒(méi)有投幣 final static int HAS_QUARTER=2;//已投幣 final static int SOLD=3;//售出糖果 //實(shí)例變量持有當(dāng)前狀態(tài),只要改變變量值狀態(tài)也會(huì)隨之改變 int state =SOLD_OUT; |
現(xiàn)在,我們將所有系統(tǒng)中可以發(fā)生的動(dòng)作整合起來(lái):
“投入25分錢(qián)”,“退回25分錢(qián)”,“轉(zhuǎn)動(dòng)曲柄”,“發(fā)放糖果”
這些動(dòng)作是糖果機(jī)的接口,這是你能對(duì)糖果機(jī)做的事情,
調(diào)用任何一個(gè)動(dòng)作都會(huì)造成狀態(tài)的轉(zhuǎn)換,
發(fā)放糖果更多是糖果機(jī)的內(nèi)部動(dòng)作,機(jī)器自己調(diào)用自己。
我們創(chuàng)建一個(gè)類,它的作用就像是一個(gè)狀態(tài)機(jī),每一個(gè)動(dòng)作,我們都創(chuàng)建了一個(gè)對(duì)應(yīng)的方法,這些方法利用條件語(yǔ)句來(lái)決定在每個(gè)狀態(tài)內(nèi)什么行為是恰當(dāng)?shù)摹1热鐚?duì)“投入25分錢(qián)”這個(gè)動(dòng)作來(lái)說(shuō),我們可以把對(duì)應(yīng)方法寫(xiě)成下面的樣子:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | public void insertQuarter(){ ????if(state==HAS_QUARTER){ ????????//每個(gè)狀態(tài)對(duì)應(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;//存儲(chǔ)糖果數(shù)量 ????public? GumballMachine(int count){ ????????this.count=count; ????????if(count>0){ ????????????state=NO_QUARTER; ????????} ????} ????//當(dāng)有25分錢(qián)投入,就會(huì)執(zhí)行這個(gè)方法 ????public void insertQuarter(){ ????????if(state==HAS_QUARTER){ ????????????System.out.println("如果已投入過(guò)25分錢(qián),我們就告訴顧客"); ????????}else if(state==NO_QUARTER){ ????????????state=HAS_QUARTER; ????????????System.out.println("如果是在“沒(méi)有25分錢(qián)”的狀態(tài)下,我們就接收25分錢(qián)," +"并將狀態(tài)轉(zhuǎn)換到“有25分錢(qián)”的狀態(tài)"); ????????}else if(state ==SOLD_OUT){ ????????????System.out.println("如果糖果已經(jīng)售罄,我們就拒絕收錢(qián)"); ????????}else if(state==SOLD){ ????????????System.out.println("如果顧客剛才買(mǎi)了糖果,就需要稍等一下,好讓狀態(tài)轉(zhuǎn)換完畢。" +"恢復(fù)到“沒(méi)有25分錢(qián)”的狀態(tài)"); ????????????state=NO_QUARTER; ????????} ????} ????//如果顧客試著退回25分錢(qián)就執(zhí)行這個(gè)方法 ????public void ejectQuarter(){ ????????if(state==HAS_QUARTER){ ????????????System.out.println("如果有25分錢(qián),我們就把錢(qián)退出來(lái),回到“沒(méi)有25分錢(qián)”的狀態(tài)"); ????????????state=NO_QUARTER; ????????}else if(state==NO_QUARTER){ ????????????System.out.println("如果沒(méi)有25分錢(qián)的話,當(dāng)然不能退出25分錢(qián)"); ????????}else if(state ==SOLD){ ????????????System.out.println("顧客已經(jīng)轉(zhuǎn)動(dòng)曲柄就不能再退錢(qián)了,他已經(jīng)拿到糖果了"); ????????}else if(state==SOLD_OUT){ ????????????System.out.println("如果糖果售罄,就不能接受25分錢(qián),當(dāng)然也不可能退錢(qián)"); ????????} ????} ????//顧客試著轉(zhuǎn)動(dòng)曲柄 ????public void turnCrank(){ ????????if(state==SOLD){ ????????????System.out.println("別想騙過(guò)機(jī)器拿兩次糖果"); ????????}else if(state==NO_QUARTER){ ????????????System.out.println("我們需要先投入25分錢(qián)"); ????????}else if(state ==SOLD_OUT){ ????????????System.out.println("我們不能給糖果,已經(jīng)沒(méi)有任何糖果了"); ????????}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; ????????????/* ????????????我們?cè)谶@里處理“糖果售罄”的情況,如果這是最后一個(gè)糖果,將機(jī)器的狀態(tài)設(shè)置到“糖果售罄”否則就回到“沒(méi)有25分錢(qián)”的狀態(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ā)生,但是如果做了,就得到錯(cuò)誤提示"); ????????}else if(state ==HAS_QUARTER){ ????????????System.out.println("這些都不應(yīng)該發(fā)生,但是如果做了,就得到錯(cuò)誤提示"); ????????}else if(state==NO_QUARTER){ ????????????System.out.println("這些都不應(yīng)該發(fā)生,但是如果做了,就得到錯(cuò)誤提示"); ????????} ????} } |
盡管程序完美運(yùn)行,但還是躲不掉需求變更的命運(yùn)
現(xiàn)在糖果公司要求:當(dāng)曲柄被轉(zhuǎn)動(dòng)時(shí),有10%的幾率掉下來(lái)的是兩個(gè)糖果。(氪金扭蛋)
再回看一下我們的初步代碼,想要實(shí)現(xiàn)新的需求將會(huì)變得非常麻煩:
- 必須新增一個(gè)中獎(jiǎng)的“贏家”狀態(tài)。
- 必須在每一個(gè)方法添加新的判斷條件來(lái)處理“贏家”狀態(tài)。
- 轉(zhuǎn)動(dòng)把手的方法中還需要檢查目前狀態(tài)是否是“贏家”再?zèng)Q定切換到“贏家”狀態(tài)行為還是正常出售行為。
在現(xiàn)有代碼基礎(chǔ)上做增加將會(huì)很麻煩,也不利與以后的維護(hù),擴(kuò)展性差。
回顧一下第一章的策略模式中的設(shè)計(jì)原則:
找出應(yīng)用中可能需要變化之處,把他們獨(dú)立出來(lái)
將狀態(tài)獨(dú)立出來(lái),封裝成一個(gè)類,都實(shí)現(xiàn)State接口,類圖如下:
新的設(shè)計(jì)想法如下:
代碼
定義一個(gè)State接口
| 1 2 3 4 5 6 | public interface State { ????public void insertQuarter();//投幣 ????public void ejectQuarter();//退幣 ????public void turnCrank();//轉(zhuǎn)動(dòng)出貨把手 ????public void dispense();//出售 } |
為機(jī)器的每個(gè)狀態(tài)實(shí)現(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("你未投幣,無(wú)法退錢(qián)"); ????} ????public void turnCrank() { ????????System.out.println("未投幣,請(qǐng)先投幣"); ????} ????public void dispense() { ????????System.out.println("請(qǐng)先投幣"); ????} } ? //已投幣狀態(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("已投幣,無(wú)法再接收投幣"); ????} ????public void ejectQuarter() { ????????System.out.println("已退幣"); ????????gumballMachine.setState(gumballMachine.getNoQuarterState()); ????} ????public void turnCrank() { ????????System.out.println("已轉(zhuǎn)動(dòng)把手,糖果出售中。。。。"); ????????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)沒(méi)有糖果可以出售了!"); ????} } ? //出售狀態(tài) public class SoldState?implements State { ????GumballMachine gumballMachine; ????public SoldState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("請(qǐng)等候,正在初始化機(jī)器中"); ????} ????public void ejectQuarter() { ????????System.out.println("抱歉,您已轉(zhuǎn)動(dòng)把手獲得了糖果,無(wú)法退幣"); ????} ????public void turnCrank() { ????????System.out.println("您重復(fù)轉(zhuǎn)動(dòng)把手,無(wú)法再獲取更多糖果"); ????} ????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)動(dòng)把手也不會(huì)有糖果出來(lái)的"); ????} ????public void dispense() { ????????System.out.println("機(jī)器中已無(wú)糖果"); ????} } ? //贏家狀態(tài) public class WinnerState?implements State { ????GumballMachine gumballMachine; ????public WinnerState(GumballMachine gumballMachine) { ????????this.gumballMachine=gumballMachine; ????} ????public void insertQuarter() { ????????System.out.println("請(qǐng)等候,正在初始化機(jī)器中"); ????} ????public void ejectQuarter() { ????????System.out.println("抱歉,您已轉(zhuǎn)動(dòng)把手獲得了糖果"); ????} ????public void turnCrank() { ????????System.out.println("您重復(fù)轉(zhuǎn)動(dòng)把手,無(wú)法再獲取更多糖果"); ????} ????public void dispense() { ????????System.out.println("恭喜你成為幸運(yùn)兒,你將額外獲得一個(gè)免費(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) ????} ????//將動(dòng)作委托到狀態(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 自動(dòng)生成的方法存根 ????????String s="剩余糖果:"+count; ????????return s; ????} } |
以上就是用狀態(tài)模式實(shí)現(xiàn)的,仔細(xì)觀察你會(huì)發(fā)現(xiàn)狀態(tài)模式其實(shí)和策略模式很像,來(lái)看看狀態(tài)模式的類圖:
狀態(tài)模式的類圖其實(shí)和策略模式完全一樣!
狀態(tài)模式與策略模式
這兩個(gè)模式的差別在于它們的“意圖”
- 以狀態(tài)模式而言,我們將一群行為封裝在狀態(tài)對(duì)象中,context的行為隨時(shí)可委托到那些狀態(tài)對(duì)象中的一個(gè),隨著時(shí)間而流逝,當(dāng)前狀態(tài)在狀態(tài)對(duì)象集合中游走改變,以反映出context內(nèi)部的狀態(tài),因此,context的行為也會(huì)跟著改變,但是context的客戶對(duì)于狀態(tài)對(duì)象了解不多,甚至根本是渾然不覺(jué)。
- 以策略模式而言,客戶通常主動(dòng)指定Context所要組合的策略對(duì)象時(shí)哪一個(gè)。現(xiàn)在,固然策略模式讓我們具有彈性,能夠在運(yùn)行時(shí)改變策略,但對(duì)于某個(gè)context對(duì)象來(lái)說(shuō),通常都只有一個(gè)最適當(dāng)?shù)牟呗詫?duì)象。
- 一般的,我們把策略模式想成是除了繼承之外的一種彈性替代方案,如果你使用繼承定義了一個(gè)類的行為,你將被這個(gè)行為困住,是指要修改它都很難,有了策略模式,你可以通過(guò)組合不同的對(duì)象來(lái)改變行為。
- 我們把狀態(tài)模式想成是不用在context中放置許多條件判斷的替代方案,通過(guò)將行為包裝進(jìn)狀態(tài)對(duì)象中,你可以通過(guò)在context內(nèi)簡(jiǎn)單地改變狀態(tài)對(duì)象來(lái)改變context的行為。
模式區(qū)分
狀態(tài)模式:封裝基于狀態(tài)的行為,并將行為委托到當(dāng)前狀態(tài)
策略模式:將可以互換的行為封裝起來(lái)。然后使用委托的方法,覺(jué)得使用哪一個(gè)行為
模板方法模式:由子類決定如何實(shí)現(xiàn)算法中的某些步驟
要點(diǎn)
(1)狀態(tài)模式允許一個(gè)對(duì)象基于內(nèi)部狀態(tài)而擁有不同的行為。
(2)和程序狀態(tài)機(jī)(PSM)不同,狀態(tài)模式用類來(lái)表示狀態(tài)。
(3)Context會(huì)將行為委托給當(dāng)前狀態(tài)對(duì)象。
(4)通過(guò)將每一個(gè)狀態(tài)封裝進(jìn)一個(gè)類,我們把以后需要做的任何改變局部化了。
(5)狀態(tài)模式和策略模式有相同的類圖,但是他們的意圖不同。
(6)策略模式通常會(huì)用行為或算法配置Context類。
(7)狀態(tài)模式允許Context隨著狀態(tài)的改變而改變行為。
(8)狀態(tài)轉(zhuǎn)換可以有State類或Context類控制。
(9)使用狀態(tài)模式通常會(huì)導(dǎo)致設(shè)計(jì)中類的數(shù)目大量增加。
(10)狀態(tài)欄可以被多個(gè)Context實(shí)例共享。
總結(jié)
以上是生活随笔為你收集整理的《Head First 设计模式》第十章-状态模式 状态模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: leetcode414. 第三大的数
- 下一篇: 《Head First设计模式》第三章笔