在軟件開發(fā)過程中,應用程序中的有些對象可能會根據(jù)不同的情況做出不同的行為经柴,我們把這種對象稱為有狀態(tài)的對象猴娩,而把影響對象行為的一個或多個動態(tài)變化的屬性稱為狀態(tài)。當有狀態(tài)的對象與外部事件產(chǎn)生互動時饿肺,其內(nèi)部狀態(tài)會發(fā)生改變蒋困,從而使得其行為也隨之發(fā)生改變。如人的情緒有高興的時候和傷心的時候敬辣,不同的情緒有不同的行為雪标,當然外界也會影響其情緒變化零院。
對這種有狀態(tài)的對象編程,傳統(tǒng)的解決方案是:將這些所有可能發(fā)生的情況全都考慮到村刨,然后使用 if-else 語句來做狀態(tài)判斷告抄,再進行不同情況的處理。但當對象的狀態(tài)很多時嵌牺,程序會變得很復雜打洼。而且增加新的狀態(tài)要添加新的 if-else 語句,這違背了“開閉原則”逆粹,不利于程序的擴展拟蜻。
以上問題如果采用“狀態(tài)模式”就能很好地得到解決。狀態(tài)模式的解決思想是:當控制一個對象狀態(tài)轉(zhuǎn)換的條件表達式過于復雜時枯饿,把相關(guān)“判斷邏輯”提取出來酝锅,放到一系列的狀態(tài)類當中,這樣可以把原來復雜的邏輯判斷簡單化奢方。
狀態(tài)模式的定義與特點
狀態(tài)(State)模式的定義:對有狀態(tài)的對象搔扁,把復雜的“判斷邏輯”提取到不同的狀態(tài)對象中,允許狀態(tài)對象在其內(nèi)部狀態(tài)發(fā)生改變時改變其行為蟋字。
狀態(tài)模式是一種對象行為型模式稿蹲,其主要優(yōu)點如下。
- 狀態(tài)模式將與特定狀態(tài)相關(guān)的行為局部化到一個狀態(tài)中鹊奖,并且將不同狀態(tài)的行為分割開來苛聘,滿足“單一職責原則”。
- 減少對象間的相互依賴忠聚。將不同的狀態(tài)引入獨立的對象中會使得狀態(tài)轉(zhuǎn)換變得更加明確设哗,且減少對象間的相互依賴。
- 有利于程序的擴展两蟀。通過定義新的子類很容易地增加新的狀態(tài)和轉(zhuǎn)換网梢。
狀態(tài)模式的主要缺點如下。
- 狀態(tài)模式的使用必然會增加系統(tǒng)的類與對象的個數(shù)赂毯。
- 狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)都較為復雜战虏,如果使用不當會導致程序結(jié)構(gòu)和代碼的混亂。
狀態(tài)模式的結(jié)構(gòu)與實現(xiàn)
狀態(tài)模式把受環(huán)境改變的對象行為包裝在不同的狀態(tài)對象里党涕,其意圖是讓一個對象在其內(nèi)部狀態(tài)改變的時候烦感,其行為也隨之改變。現(xiàn)在我們來分析其基本結(jié)構(gòu)和實現(xiàn)方法膛堤。
1. 模式的結(jié)構(gòu)
狀態(tài)模式包含以下主要角色手趣。
- 環(huán)境(Context)角色:也稱為上下文,它定義了客戶感興趣的接口骑祟,維護一個當前狀態(tài)回懦,并將與狀態(tài)相關(guān)的操作委托給當前狀態(tài)對象來處理气笙。
- 抽象狀態(tài)(State)角色:定義一個接口,用以封裝環(huán)境對象中的特定狀態(tài)所對應的行為怯晕。
- 具體狀態(tài)(Concrete State)角色:實現(xiàn)抽象狀態(tài)所對應的行為潜圃。
其結(jié)構(gòu)圖如圖 1 所示。
圖1 狀態(tài)模式的結(jié)構(gòu)圖
2. 模式的實現(xiàn)
狀態(tài)模式的實現(xiàn)代碼如下:
1. package state;
2. public class StatePatternClient
3. {
4. public static void main(String[] args)
5. {
6. Context context=new Context(); //創(chuàng)建環(huán)境
7. context.Handle(); //處理請求
8. context.Handle();
9. context.Handle();
10. context.Handle();
11. }
12. }
13. //環(huán)境類
14. class Context
15. {
16. private State state;
17. //定義環(huán)境類的初始狀態(tài)
18. public Context()
19. {
20. this.state=new ConcreteStateA();
21. }
22. //設(shè)置新狀態(tài)
23. public void setState(State state)
24. {
25. this.state=state;
26. }
27. //讀取狀態(tài)
28. public State getState()
29. {
30. return(state);
31. }
32. //對請求做處理
33. public void Handle()
34. {
35. state.Handle(this);
36. }
37. }
38. //抽象狀態(tài)類
39. abstract class State
40. {
41. public abstract void Handle(Context context);
42. }
43. //具體狀態(tài)A類
44. class ConcreteStateA extends State
45. {
46. public void Handle(Context context)
47. {
48. System.out.println("當前狀態(tài)是 A.");
49. context.setState(new ConcreteStateB());
50. }
51. }
52. //具體狀態(tài)B類
53. class ConcreteStateB extends State
54. {
55. public void Handle(Context context)
56. {
57. System.out.println("當前狀態(tài)是 B.");
58. context.setState(new ConcreteStateA());
59. }
60. }
程序運行結(jié)果如下:
<pre class="info-box">當前狀態(tài)是 A.
當前狀態(tài)是 B.
當前狀態(tài)是 A.
當前狀態(tài)是 B.</pre>
狀態(tài)模式的應用實例
【例1】用“狀態(tài)模式”設(shè)計一個學生成績的狀態(tài)轉(zhuǎn)換程序舟茶。
分析:本實例包含了“不及格”“中等”和“優(yōu)秀” 3 種狀態(tài)谭期,當學生的分數(shù)小于 60 分時為“不及格”狀態(tài),當分數(shù)大于等于 60 分且小于 90 分時為“中等”狀態(tài)吧凉,當分數(shù)大于等于 90 分時為“優(yōu)秀”狀態(tài)隧出,我們用狀態(tài)模式來實現(xiàn)這個程序。
首先阀捅,定義一個抽象狀態(tài)類(AbstractState)胀瞪,其中包含了環(huán)境屬性、狀態(tài)名屬性和當前分數(shù)屬性饲鄙,以及加減分方法 addScore(intx) 和檢查當前狀態(tài)的抽象方法 checkState()凄诞;然后,定義“不及格”狀態(tài)類 LowState忍级、“中等”狀態(tài)類 MiddleState 和“優(yōu)秀”狀態(tài)類 HighState帆谍,它們是具體狀態(tài)類,實現(xiàn) checkState() 方法轴咱,負責檢査自己的狀態(tài)汛蝙,并根據(jù)情況轉(zhuǎn)換;最后朴肺,定義環(huán)境類(ScoreContext)窖剑,其中包含了當前狀態(tài)對象和加減分的方法 add(int score),客戶類通過該方法來改變成績狀態(tài)宇挫。圖 2 所示是其結(jié)構(gòu)圖苛吱。
圖2 學生成績的狀態(tài)轉(zhuǎn)換程序的結(jié)構(gòu)圖
程序代碼如下:
1. package state;
2. public class ScoreStateTest
3. {
4. public static void main(String[] args)
5. {
6. ScoreContext account=new ScoreContext();
7. System.out.println("學生成績狀態(tài)測試:");
8. account.add(30);
9. account.add(40);
10. account.add(25);
11. account.add(-15);
12. account.add(-25);
13. }
14. }
15. //環(huán)境類
16. class ScoreContext
17. {
18. private AbstractState state;
19. ScoreContext()
20. {
21. state=new LowState(this);
22. }
23. public void setState(AbstractState state)
24. {
25. this.state=state;
26. }
27. public AbstractState getState()
28. {
29. return state;
30. }
31. public void add(int score)
32. {
33. state.addScore(score);
34. }
35. }
36. //抽象狀態(tài)類
37. abstract class AbstractState
38. {
39. protected ScoreContext hj; //環(huán)境
40. protected String stateName; //狀態(tài)名
41. protected int score; //分數(shù)
42. public abstract void checkState(); //檢查當前狀態(tài)
43. public void addScore(int x)
44. {
45. score+=x;
46. System.out.print("加上:"+x+"分,\t當前分數(shù):"+score );
47. checkState();
48. System.out.println("分器瘪,\t當前狀態(tài):"+hj.getState().stateName);
49. }
50. }
51. //具體狀態(tài)類:不及格
52. class LowState extends AbstractState
53. {
54. public LowState(ScoreContext h)
55. {
56. hj=h;
57. stateName="不及格";
58. score=0;
59. }
60. public LowState(AbstractState state)
61. {
62. hj=state.hj;
63. stateName="不及格";
64. score=state.score;
65. }
66. public void checkState()
67. {
68. if(score>=90)
69. {
70. hj.setState(new HighState(this));
71. }
72. else if(score>=60)
73. {
74. hj.setState(new MiddleState(this));
75. }
76. }
77. }
78. //具體狀態(tài)類:中等
79. class MiddleState extends AbstractState
80. {
81. public MiddleState(AbstractState state)
82. {
83. hj=state.hj;
84. stateName="中等";
85. score=state.score;
86. }
87. public void checkState()
88. {
89. if(score<60)
90. {
91. hj.setState(new LowState(this));
92. }
93. else if(score>=90)
94. {
95. hj.setState(new HighState(this));
96. }
97. }
98. }
99. //具體狀態(tài)類:優(yōu)秀
100. class HighState extends AbstractState
101. {
102. public HighState(AbstractState state)
103. {
104. hj=state.hj;
105. stateName="優(yōu)秀";
106. score=state.score;
107. }
108. public void checkState()
109. {
110. if(score<60)
111. {
112. hj.setState(new LowState(this));
113. }
114. else if(score<90)
115. {
116. hj.setState(new MiddleState(this));
117. }
118. }
119. }
程序運行結(jié)果如下:
<pre class="info-box">學生成績狀態(tài)測試:
加上:30分, 當前分數(shù):30分绘雁, 當前狀態(tài):不及格
加上:40分橡疼, 當前分數(shù):70分, 當前狀態(tài):中等
加上:25分庐舟, 當前分數(shù):95分欣除, 當前狀態(tài):優(yōu)秀
加上:-15分, 當前分數(shù):80分挪略, 當前狀態(tài):中等
加上:-25分历帚, 當前分數(shù):55分滔岳, 當前狀態(tài):不及格</pre>
【例2】用“狀態(tài)模式”設(shè)計一個多線程的狀態(tài)轉(zhuǎn)換程序。
分析:多線程存在 5 種狀態(tài)挽牢,分別為新建狀態(tài)谱煤、就緒狀態(tài)、運行狀態(tài)禽拔、阻塞狀態(tài)和死亡狀態(tài)刘离,各個狀態(tài)當遇到相關(guān)方法調(diào)用或事件觸發(fā)時會轉(zhuǎn)換到其他狀態(tài),其狀態(tài)轉(zhuǎn)換規(guī)律如圖 3 所示睹栖。
圖3 線程狀態(tài)轉(zhuǎn)換圖
現(xiàn)在先定義一個抽象狀態(tài)類(TheadState)硫惕,然后為圖 3 所示的每個狀態(tài)設(shè)計一個具體狀態(tài)類,它們是新建狀態(tài)(New)野来、就緒狀態(tài)(Runnable )恼除、運行狀態(tài)(Running)、阻塞狀態(tài)(Blocked)和死亡狀態(tài)(Dead)曼氛,每個狀態(tài)中有觸發(fā)它們轉(zhuǎn)變狀態(tài)的方法缚柳,環(huán)境類(ThreadContext)中先生成一個初始狀態(tài)(New),并提供相關(guān)觸發(fā)方法搪锣,圖 4 所示是線程狀態(tài)轉(zhuǎn)換程序的結(jié)構(gòu)圖秋忙。
圖4 線程狀態(tài)轉(zhuǎn)換程序的結(jié)構(gòu)圖
程序代碼如下:
1. package state;
2. public class ThreadStateTest
3. {
4. public static void main(String[] args)
5. {
6. ThreadContext context=new ThreadContext();
7. context.start();
8. context.getCPU();
9. context.suspend();
10. context.resume();
11. context.getCPU();
12. context.stop();
13. }
14. }
15. //環(huán)境類
16. class ThreadContext
17. {
18. private ThreadState state;
19. ThreadContext()
20. {
21. state=new New();
22. }
23. public void setState(ThreadState state)
24. {
25. this.state=state;
26. }
27. public ThreadState getState()
28. {
29. return state;
30. }
31. public void start()
32. {
33. ((New) state).start(this);
34. }
35. public void getCPU()
36. {
37. ((Runnable) state).getCPU(this);
38. }
39. public void suspend()
40. {
41. ((Running) state).suspend(this);
42. }
43. public void stop()
44. {
45. ((Running) state).stop(this);
46. }
47. public void resume()
48. {
49. ((Blocked) state).resume(this);
50. }
51. }
52. //抽象狀態(tài)類:線程狀態(tài)
53. abstract class ThreadState
54. {
55. protected String stateName; //狀態(tài)名
56. }
57. //具體狀態(tài)類:新建狀態(tài)
58. class New extends ThreadState
59. {
60. public New()
61. {
62. stateName="新建狀態(tài)";
63. System.out.println("當前線程處于:新建狀態(tài).");
64. }
65. public void start(ThreadContext hj)
66. {
67. System.out.print("調(diào)用start()方法-->");
68. if(stateName.equals("新建狀態(tài)"))
69. {
70. hj.setState(new Runnable());
71. }
72. else
73. {
74. System.out.println("當前線程不是新建狀態(tài),不能調(diào)用start()方法.");
75. }
76. }
77. }
78. //具體狀態(tài)類:就緒狀態(tài)
79. class Runnable extends ThreadState
80. {
81. public Runnable()
82. {
83. stateName="就緒狀態(tài)";
84. System.out.println("當前線程處于:就緒狀態(tài).");
85. }
86. public void getCPU(ThreadContext hj)
87. {
88. System.out.print("獲得CPU時間-->");
89. if(stateName.equals("就緒狀態(tài)"))
90. {
91. hj.setState(new Running());
92. }
93. else
94. {
95. System.out.println("當前線程不是就緒狀態(tài)构舟,不能獲取CPU.");
96. }
97. }
98. }
99. //具體狀態(tài)類:運行狀態(tài)
100. class Running extends ThreadState
101. {
102. public Running()
103. {
104. stateName="運行狀態(tài)";
105. System.out.println("當前線程處于:運行狀態(tài).");
106. }
107. public void suspend(ThreadContext hj)
108. {
109. System.out.print("調(diào)用suspend()方法-->");
110. if(stateName.equals("運行狀態(tài)"))
111. {
112. hj.setState(new Blocked());
113. }
114. else
115. {
116. System.out.println("當前線程不是運行狀態(tài)灰追,不能調(diào)用suspend()方法.");
117. }
118. }
119. public void stop(ThreadContext hj)
120. {
121. System.out.print("調(diào)用stop()方法-->");
122. if(stateName.equals("運行狀態(tài)"))
123. {
124. hj.setState(new Dead());
125. }
126. else
127. {
128. System.out.println("當前線程不是運行狀態(tài),不能調(diào)用stop()方法.");
129. }
130. }
131. }
132. //具體狀態(tài)類:阻塞狀態(tài)
133. class Blocked extends ThreadState
134. {
135. public Blocked()
136. {
137. stateName="阻塞狀態(tài)";
138. System.out.println("當前線程處于:阻塞狀態(tài).");
139. }
140. public void resume(ThreadContext hj)
141. {
142. System.out.print("調(diào)用resume()方法-->");
143. if(stateName.equals("阻塞狀態(tài)"))
144. {
145. hj.setState(new Runnable());
146. }
147. else
148. {
149. System.out.println("當前線程不是阻塞狀態(tài)狗超,不能調(diào)用resume()方法.");
150. }
151. }
152. }
153. //具體狀態(tài)類:死亡狀態(tài)
154. class Dead extends ThreadState
155. {
156. public Dead()
157. {
158. stateName="死亡狀態(tài)";
159. System.out.println("當前線程處于:死亡狀態(tài).");
160. }
161. }
程序運行結(jié)果如下:
<pre class="info-box">當前線程處于:新建狀態(tài).
調(diào)用start()方法-->當前線程處于:就緒狀態(tài).
獲得CPU時間-->當前線程處于:運行狀態(tài).
調(diào)用suspend()方法-->當前線程處于:阻塞狀態(tài).
調(diào)用resume()方法-->當前線程處于:就緒狀態(tài).
獲得CPU時間-->當前線程處于:運行狀態(tài).
調(diào)用stop()方法-->當前線程處于:死亡狀態(tài).</pre>
狀態(tài)模式的應用場景
通常在以下情況下可以考慮使用狀態(tài)模式弹澎。
- 當一個對象的行為取決于它的狀態(tài),并且它必須在運行時根據(jù)狀態(tài)改變它的行為時努咐,就可以考慮使用狀態(tài)模式苦蒿。
- 一個操作中含有龐大的分支結(jié)構(gòu),并且這些分支決定于對象的狀態(tài)時渗稍。
狀態(tài)模式的擴展
在有些情況下佩迟,可能有多個環(huán)境對象需要共享一組狀態(tài),這時需要引入享元模式竿屹,將這些具體狀態(tài)對象放在集合中供程序共享报强,其結(jié)構(gòu)圖如圖 5 所示。
圖5 共享狀態(tài)模式的結(jié)構(gòu)圖
分析:共享狀態(tài)模式的不同之處是在環(huán)境類中增加了一個 HashMap 來保存相關(guān)狀態(tài)拱燃,當需要某種狀態(tài)時可以從中獲取秉溉,其程序代碼如下:
package state;
import java.util.HashMap;
public class FlyweightStatePattern
{
public static void main(String[] args)
{
ShareContext context=new ShareContext(); //創(chuàng)建環(huán)境
context.Handle(); //處理請求
context.Handle();
context.Handle();
context.Handle();
}
}
//環(huán)境類
class ShareContext
{
private ShareState state;
private HashMap<String, ShareState> stateSet=new HashMap<String, ShareState>();
public ShareContext()
{
state=new ConcreteState1();
stateSet.put("1", state);
state=new ConcreteState2();
stateSet.put("2", state);
state=getState("1");
}
//設(shè)置新狀態(tài)
public void setState(ShareState state)
{
this.state=state;
}
//讀取狀態(tài)
public ShareState getState(String key)
{
ShareState s=(ShareState)stateSet.get(key);
return s;
}
//對請求做處理
public void Handle()
{
state.Handle(this);
}
}
//抽象狀態(tài)類
abstract class ShareState
{
public abstract void Handle(ShareContext context);
}
//具體狀態(tài)1類
class ConcreteState1 extends ShareState
{
public void Handle(ShareContext context)
{
System.out.println("當前狀態(tài)是: 狀態(tài)1");
context.setState(context.getState("2"));
}
}
//具體狀態(tài)2類
class ConcreteState2 extends ShareState
{
public void Handle(ShareContext context)
{
System.out.println("當前狀態(tài)是: 狀態(tài)2");
context.setState(context.getState("1"));
}
}
程序運行結(jié)果如下:
當前狀態(tài)是: 狀態(tài)1
當前狀態(tài)是: 狀態(tài)2
當前狀態(tài)是: 狀態(tài)1
當前狀態(tài)是: 狀態(tài)2