《Head First 設計模式》第十章-狀態模式 狀態模式(headcount)
狀態模式

策略模式和狀態模式是雙胞胎,在出生時才分開。你已經知道,策略模式是圍繞可以互換的算法來創建成功業務的,然而,狀態走的是更崇高的路,它通過改變對象內部的狀態來幫助對象控制自己的行為。
定義狀態模式
先看看定義:狀態模式允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它的類
例題
自動糖果售賣機,糖果機的控制器需要的工作流程如下圖
從上面的狀態圖中可以找到所有的狀態:
我們可以創建一個實例變量來持有目前的狀態,然后定義每個狀態的值:
1
2
3
4
5
6
7
//每個狀態用不同的值表示
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分錢”這個動作來說,我們可以把對應方法寫成下面的樣子:
1
2
3
4
5
6
7
8
9
10
11
12
13
public void insertQuarter(){
if(state==HAS_QUARTER){
//每個狀態對應的行為
......
}else if(state==SOLD_OUT){
......
}else if(state ==SOLD){
......
}else if(state==NO_QUARTER){
state=HAS_QUARTER;//狀態轉換
......
}
}
初步代碼
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;//存儲糖果數量
public? GumballMachine(int count){
this.count=count;
if(count>0){
state=NO_QUARTER;
}
}
//當有25分錢投入,就會執行這個方法
public void insertQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果已投入過25分錢,我們就告訴顧客");
}else if(state==NO_QUARTER){
state=HAS_QUARTER;
System.out.println("如果是在“沒有25分錢”的狀態下,我們就接收25分錢," +"并將狀態轉換到“有25分錢”的狀態");
}else if(state ==SOLD_OUT){
System.out.println("如果糖果已經售罄,我們就拒絕收錢");
}else if(state==SOLD){
System.out.println("如果顧客剛才買了糖果,就需要稍等一下,好讓狀態轉換完畢。" +"恢復到“沒有25分錢”的狀態");
state=NO_QUARTER;
}
}
//如果顧客試著退回25分錢就執行這個方法
public void ejectQuarter(){
if(state==HAS_QUARTER){
System.out.println("如果有25分錢,我們就把錢退出來,回到“沒有25分錢”的狀態");
state=NO_QUARTER;
}else if(state==NO_QUARTER){
System.out.println("如果沒有25分錢的話,當然不能退出25分錢");
}else if(state ==SOLD){
System.out.println("顧客已經轉動曲柄就不能再退錢了,他已經拿到糖果了");
}else if(state==SOLD_OUT){
System.out.println("如果糖果售罄,就不能接受25分錢,當然也不可能退錢");
}
}
//顧客試著轉動曲柄
public void turnCrank(){
if(state==SOLD){
System.out.println("別想騙過機器拿兩次糖果");
}else if(state==NO_QUARTER){
System.out.println("我們需要先投入25分錢");
}else if(state ==SOLD_OUT){
System.out.println("我們不能給糖果,已經沒有任何糖果了");
}else if(state==HAS_QUARTER){
System.out.println("成功,他們拿到糖果了," +"改變狀態到“售出糖果”然后調用機器的disoense()方法");
state=SOLD;
dispense();
}
}
//調用此方法,發放糖果
public void dispense(){
if(state==SOLD){
System.out.println("我們正在“出售糖果”狀態,給他們糖果");
count=count-1;
/*
我們在這里處理“糖果售罄”的情況,如果這是最后一個糖果,將機器的狀態設置到“糖果售罄”否則就回到“沒有25分錢”的狀態
*/
if(count==0){
System.out.println();
state=SOLD_OUT;
}else{
state=NO_QUARTER;
}
}else if(state==SOLD_OUT){
System.out.println("這些都不應該發生,但是如果做了,就得到錯誤提示");
}else if(state ==HAS_QUARTER){
System.out.println("這些都不應該發生,但是如果做了,就得到錯誤提示");
}else if(state==NO_QUARTER){
System.out.println("這些都不應該發生,但是如果做了,就得到錯誤提示");
}
}
}
盡管程序完美運行,但還是躲不掉需求變更的命運
現在糖果公司要求:當曲柄被轉動時,有10%的幾率掉下來的是兩個糖果。(氪金扭蛋)
再回看一下我們的初步代碼,想要實現新的需求將會變得非常麻煩:
必須新增一個中獎的“贏家”狀態。
必須在每一個方法添加新的判斷條件來處理“贏家”狀態。
轉動把手的方法中還需要檢查目前狀態是否是“贏家”再決定切換到“贏家”狀態行為還是正常出售行為。
在現有代碼基礎上做增加將會很麻煩,也不利與以后的維護,擴展性差。
回顧一下第一章的策略模式中的設計原則:
找出應用中可能需要變化之處,把他們獨立出來
將狀態獨立出來,封裝成一個類,都實現State接口,類圖如下:
新的設計想法如下:
首先,我們定義一個State接口,在這個接口內,糖果機的每個動作都有一個對應的方法
然后為機器的每個狀態實現狀態類,這些類將負責在對應的狀態下進行機器的行為
最后,我們要擺脫舊的條件代碼,取而代之的方式是,將動作委托到狀態類
代碼
定義一個State接口
1
2
3
4
5
6
public interface State {
public void insertQuarter();//投幣
public void ejectQuarter();//退幣
public void turnCrank();//轉動出貨把手
public void dispense();//出售
}
為機器的每個狀態實現狀態類:
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
//未投幣狀態
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 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("已轉動把手,糖果出售中。。。。");
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 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();//出貨,糖果-1
if(gumballMachine.getCount()>0)
gumballMachine.setState(gumballMachine.getNoQuarterState());
else {
System.out.println("糖果已售完");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
//售罄狀態
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 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();//出貨,糖果-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());
}
}
}
}
糖果機類:
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;//先判斷條件再改變狀態
}
//將動作委托到狀態類
public void insterQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
//獲取當前狀態
public State getHasQuarterState() {
return hasQuarterState;
}
//改變狀態
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;
}
//獲取糖果機中糖果數量
public int getCount() {
return count;
}
public State getWinnerState() {
return winnerState;
}
public String toString() {
// TODO 自動生成的方法存根
String s="剩余糖果:"+count;
return s;
}
}
以上就是用狀態模式實現的,仔細觀察你會發現狀態模式其實和策略模式很像,來看看狀態模式的類圖:
狀態模式的類圖其實和策略模式完全一樣!
狀態模式與策略模式
這兩個模式的差別在于它們的“意圖”
以狀態模式而言,我們將一群行為封裝在狀態對象中,context的行為隨時可委托到那些狀態對象中的一個,隨著時間而流逝,當前狀態在狀態對象集合中游走改變,以反映出context內部的狀態,因此,context的行為也會跟著改變,但是context的客戶對于狀態對象了解不多,甚至根本是渾然不覺。
以策略模式而言,客戶通常主動指定Context所要組合的策略對象時哪一個。現在,固然策略模式讓我們具有彈性,能夠在運行時改變策略,但對于某個context對象來說,通常都只有一個最適當的策略對象。
一般的,我們把策略模式想成是除了繼承之外的一種彈性替代方案,如果你使用繼承定義了一個類的行為,你將被這個行為困住,是指要修改它都很難,有了策略模式,你可以通過組合不同的對象來改變行為。
我們把狀態模式想成是不用在context中放置許多條件判斷的替代方案,通過將行為包裝進狀態對象中,你可以通過在context內簡單地改變狀態對象來改變context的行為。
模式區分
狀態模式:封裝基于狀態的行為,并將行為委托到當前狀態
策略模式:將可以互換的行為封裝起來。然后使用委托的方法,覺得使用哪一個行為
模板方法模式:由子類決定如何實現算法中的某些步驟
要點
(1)狀態模式允許一個對象基于內部狀態而擁有不同的行為。
(2)和程序狀態機(PSM)不同,狀態模式用類來表示狀態。
(3)Context會將行為委托給當前狀態對象。
(4)通過將每一個狀態封裝進一個類,我們把以后需要做的任何改變局部化了。
(5)狀態模式和策略模式有相同的類圖,但是他們的意圖不同。
(6)策略模式通常會用行為或算法配置Context類。
(7)狀態模式允許Context隨著狀態的改變而改變行為。
(8)狀態轉換可以有State類或Context類控制。
(9)使用狀態模式通常會導致設計中類的數目大量增加。
(10)狀態欄可以被多個Context實例共享。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。