在軟件開發(fā)過程中伤柄,應用程序中的部分對象可能會根據(jù)不同的情況做出不同的行為绊困,我們把這種對象稱為有狀態(tài)的對象,而把影響對象行為的一個或多個動態(tài)變化的屬性稱為狀態(tài)适刀。當有狀態(tài)的對象與外部事件產(chǎn)生互動時秤朗,其內部狀態(tài)就會發(fā)生改變,從而使其行為也發(fā)生改變笔喉。如人都有高興和傷心的時候取视,不同的情緒有不同的行為,當然外界也會影響其情緒變化常挚。
對這種有狀態(tài)的對象編程作谭,傳統(tǒng)的解決方案是:將這些所有可能發(fā)生的情況全都考慮到,然后使用 if-else 或 switch-case 語句來做狀態(tài)判斷奄毡,再進行不同情況的處理折欠。但是顯然這種做法對復雜的狀態(tài)判斷存在天然弊端,條件判斷語句會過于臃腫吼过,可讀性差锐秦,且不具備擴展性,維護難度也大那先。且增加新的狀態(tài)時要添加新的 if-else 語句农猬,這違背了“開閉原則”,不利于程序的擴展售淡。
以上問題如果采用“狀態(tài)模式”就能很好地得到解決斤葱。狀態(tài)模式的解決思想是:當控制一個對象狀態(tài)轉換的條件表達式過于復雜時,把相關“判斷邏輯”提取出來揖闸,用各個不同的類進行表示揍堕,系統(tǒng)處于哪種情況,直接使用相應的狀態(tài)類對象進行處理汤纸,這樣能把原來復雜的邏輯判斷簡單化衩茸,消除了 if-else、switch-case 等冗余語句贮泞,代碼更有層次性楞慈,并且具備良好的擴展力幔烛。
狀態(tài)模式的定義與特點
狀態(tài)(State)模式的定義:對有狀態(tài)的對象,把復雜的“判斷邏輯”提取到不同的狀態(tài)對象中囊蓝,允許狀態(tài)對象在其內部狀態(tài)發(fā)生改變時改變其行為饿悬。
狀態(tài)模式是一種對象行為型模式,其主要優(yōu)點如下聚霜。
- 結構清晰狡恬,狀態(tài)模式將與特定狀態(tài)相關的行為局部化到一個狀態(tài)中,并且將不同狀態(tài)的行為分割開來蝎宇,滿足“單一職責原則”弟劲。
- 將狀態(tài)轉換顯示化,減少對象間的相互依賴姥芥。將不同的狀態(tài)引入獨立的對象中會使得狀態(tài)轉換變得更加明確兔乞,且減少對象間的相互依賴。
- 狀態(tài)類職責明確撇眯,有利于程序的擴展报嵌。通過定義新的子類很容易地增加新的狀態(tài)和轉換。
狀態(tài)模式的主要缺點如下熊榛。
- 狀態(tài)模式的使用必然會增加系統(tǒng)的類與對象的個數(shù)锚国。
- 狀態(tài)模式的結構與實現(xiàn)都較為復雜,如果使用不當會導致程序結構和代碼的混亂玄坦。
- 狀態(tài)模式對開閉原則的支持并不太好血筑,對于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負責狀態(tài)轉換的源碼煎楣,否則無法切換到新增狀態(tài)豺总,而且修改某個狀態(tài)類的行為也需要修改對應類的源碼。
狀態(tài)模式的結構與實現(xiàn)
狀態(tài)模式把受環(huán)境改變的對象行為包裝在不同的狀態(tài)對象里择懂,其意圖是讓一個對象在其內部狀態(tài)改變的時候喻喳,其行為也隨之改變。現(xiàn)在我們來分析其基本結構和實現(xiàn)方法困曙。
1. 模式的結構
狀態(tài)模式包含以下主要角色表伦。
- 環(huán)境類(Context)角色:也稱為上下文,它定義了客戶端需要的接口慷丽,內部維護一個當前狀態(tài)蹦哼,并負責具體狀態(tài)的切換。
- 抽象狀態(tài)(State)角色:定義一個接口要糊,用以封裝環(huán)境對象中的特定狀態(tài)所對應的行為纲熏,可以有一個或多個行為。
- 具體狀態(tài)(Concrete State)角色:實現(xiàn)抽象狀態(tài)所對應的行為,并且在需要的情況下進行狀態(tài)切換局劲。
其結構圖如圖 1 所示勺拣。
圖1 狀態(tài)模式的結構圖
2. 模式的實現(xiàn)
狀態(tài)模式的實現(xiàn)代碼如下:
public class StatePatternClient {
public static void main(String[] args) {
Context context = new Context(); //創(chuàng)建環(huán)境
context.Handle(); //處理請求
context.Handle();
context.Handle();
context.Handle();
}
}
//環(huán)境類
class Context {
private State state;
//定義環(huán)境類的初始狀態(tài)
public Context() {
this.state = new ConcreteStateA();
}
//設置新狀態(tài)
public void setState(State state) {
this.state = state;
}
//讀取狀態(tài)
public State getState() {
return (state);
}
//對請求做處理
public void Handle() {
state.Handle(this);
}
}
//抽象狀態(tài)類
abstract class State {
public abstract void Handle(Context context);
}
//具體狀態(tài)A類
class ConcreteStateA extends State {
public void Handle(Context context) {
System.out.println("當前狀態(tài)是 A.");
context.setState(new ConcreteStateB());
}
}
//具體狀態(tài)B類
class ConcreteStateB extends State {
public void Handle(Context context) {
System.out.println("當前狀態(tài)是 B.");
context.setState(new ConcreteStateA());
}
}
程序運行結果如下:
當前狀態(tài)是 A.
當前狀態(tài)是 B.
當前狀態(tài)是 A.
當前狀態(tài)是 B.
狀態(tài)模式的應用實例
【例1】用“狀態(tài)模式”設計一個學生成績的狀態(tài)轉換程序。
分析:本實例包含了“不及格”“中等”和“優(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ù)情況轉換。
最后扮碧,定義環(huán)境類(ScoreContext)趟章,其中包含了當前狀態(tài)對象和加減分的方法 add(int score),客戶類通過該方法來改變成績狀態(tài)慎王。圖 2 所示是其結構圖蚓土。
圖2 學生成績的狀態(tài)轉換程序的結構圖
程序代碼如下:
public class ScoreStateTest {
public static void main(String[] args) {
ScoreContext account = new ScoreContext();
System.out.println("學生成績狀態(tài)測試:");
account.add(30);
account.add(40);
account.add(25);
account.add(-15);
account.add(-25);
}
}
//環(huán)境類
class ScoreContext {
private AbstractState state;
ScoreContext() {
state = new LowState(this);
}
public void setState(AbstractState state) {
this.state = state;
}
public AbstractState getState() {
return state;
}
public void add(int score) {
state.addScore(score);
}
}
//抽象狀態(tài)類
abstract class AbstractState {
protected ScoreContext hj; //環(huán)境
protected String stateName; //狀態(tài)名
protected int score; //分數(shù)
public abstract void checkState(); //檢查當前狀態(tài)
public void addScore(int x) {
score += x;
System.out.print("加上:" + x + "分,\t當前分數(shù):" + score);
checkState();
System.out.println("分赖淤,\t當前狀態(tài):" + hj.getState().stateName);
}
}
//具體狀態(tài)類:不及格
class LowState extends AbstractState {
public LowState(ScoreContext h) {
hj = h;
stateName = "不及格";
score = 0;
}
public LowState(AbstractState state) {
hj = state.hj;
stateName = "不及格";
score = state.score;
}
public void checkState() {
if (score >= 90) {
hj.setState(new HighState(this));
} else if (score >= 60) {
hj.setState(new MiddleState(this));
}
}
}
//具體狀態(tài)類:中等
class MiddleState extends AbstractState {
public MiddleState(AbstractState state) {
hj = state.hj;
stateName = "中等";
score = state.score;
}
public void checkState() {
if (score < 60) {
hj.setState(new LowState(this));
} else if (score >= 90) {
hj.setState(new HighState(this));
}
}
}
//具體狀態(tài)類:優(yōu)秀
class HighState extends AbstractState {
public HighState(AbstractState state) {
hj = state.hj;
stateName = "優(yōu)秀";
score = state.score;
}
public void checkState() {
if (score < 60) {
hj.setState(new LowState(this));
} else if (score < 90) {
hj.setState(new MiddleState(this));
}
}
}
程序運行結果如下:
學生成績狀態(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):不及格
【例2】用“狀態(tài)模式”設計一個多線程的狀態(tài)轉換程序。
分析:多線程存在 5 種狀態(tài),分別為新建狀態(tài)赘阀、就緒狀態(tài)益缠、運行狀態(tài)、阻塞狀態(tài)和死亡狀態(tài)基公,各個狀態(tài)當遇到相關方法調用或事件觸發(fā)時會轉換到其他狀態(tài)幅慌,其狀態(tài)轉換規(guī)律如圖 3 所示。
圖3 線程狀態(tài)轉換圖
現(xiàn)在先定義一個抽象狀態(tài)類(TheadState)轰豆,然后為圖 3 所示的每個狀態(tài)設計一個具體狀態(tài)類胰伍,它們是新建狀態(tài)(New)、就緒狀態(tài)(Runnable )酸休、運行狀態(tài)(Running)骂租、阻塞狀態(tài)(Blocked)和死亡狀態(tài)(Dead),每個狀態(tài)中有觸發(fā)它們轉變狀態(tài)的方法斑司,環(huán)境類(ThreadContext)中先生成一個初始狀態(tài)(New)渗饮,并提供相關觸發(fā)方法,圖 4 所示是線程狀態(tài)轉換程序的結構圖宿刮。
圖4 線程狀態(tài)轉換程序的結構圖
程序代碼如下:
public class ScoreStateTest {
public static void main(String[] args) {
ThreadContext context = new ThreadContext();
context.start();
context.getCPU();
context.suspend();
context.resume();
context.getCPU();
context.stop();
}
}
//環(huán)境類
class ThreadContext {
private ThreadState state;
ThreadContext() {
state = new New();
}
public void setState(ThreadState state) {
this.state = state;
}
public ThreadState getState() {
return state;
}
public void start() {
((New) state).start(this);
}
public void getCPU() {
((Runnable) state).getCPU(this);
}
public void suspend() {
((Running) state).suspend(this);
}
public void stop() {
((Running) state).stop(this);
}
public void resume() {
((Blocked) state).resume(this);
}
}
//抽象狀態(tài)類:線程狀態(tài)
abstract class ThreadState {
protected String stateName; //狀態(tài)名
}
//具體狀態(tài)類:新建狀態(tài)
class New extends ThreadState {
public New() {
stateName = "新建狀態(tài)";
System.out.println("當前線程處于:新建狀態(tài).");
}
public void start(ThreadContext hj) {
System.out.print("調用start()方法-->");
if (stateName.equals("新建狀態(tài)")) {
hj.setState(new Runnable());
} else {
System.out.println("當前線程不是新建狀態(tài)互站,不能調用start()方法.");
}
}
}
//具體狀態(tài)類:就緒狀態(tài)
class Runnable extends ThreadState {
public Runnable() {
stateName = "就緒狀態(tài)";
System.out.println("當前線程處于:就緒狀態(tài).");
}
public void getCPU(ThreadContext hj) {
System.out.print("獲得CPU時間-->");
if (stateName.equals("就緒狀態(tài)")) {
hj.setState(new Running());
} else {
System.out.println("當前線程不是就緒狀態(tài),不能獲取CPU.");
}
}
}
//具體狀態(tài)類:運行狀態(tài)
class Running extends ThreadState {
public Running() {
stateName = "運行狀態(tài)";
System.out.println("當前線程處于:運行狀態(tài).");
}
public void suspend(ThreadContext hj) {
System.out.print("調用suspend()方法-->");
if (stateName.equals("運行狀態(tài)")) {
hj.setState(new Blocked());
} else {
System.out.println("當前線程不是運行狀態(tài)僵缺,不能調用suspend()方法.");
}
}
public void stop(ThreadContext hj) {
System.out.print("調用stop()方法-->");
if (stateName.equals("運行狀態(tài)")) {
hj.setState(new Dead());
} else {
System.out.println("當前線程不是運行狀態(tài)胡桃,不能調用stop()方法.");
}
}
}
//具體狀態(tài)類:阻塞狀態(tài)
class Blocked extends ThreadState {
public Blocked() {
stateName = "阻塞狀態(tài)";
System.out.println("當前線程處于:阻塞狀態(tài).");
}
public void resume(ThreadContext hj) {
System.out.print("調用resume()方法-->");
if (stateName.equals("阻塞狀態(tài)")) {
hj.setState(new Runnable());
} else {
System.out.println("當前線程不是阻塞狀態(tài),不能調用resume()方法.");
}
}
}
//具體狀態(tài)類:死亡狀態(tài)
class Dead extends ThreadState {
public Dead() {
stateName = "死亡狀態(tài)";
System.out.println("當前線程處于:死亡狀態(tài).");
}
}
程序運行結果如下:
當前線程處于:新建狀態(tài).
調用start()方法-->當前線程處于:就緒狀態(tài).
獲得CPU時間-->當前線程處于:運行狀態(tài).
調用suspend()方法-->當前線程處于:阻塞狀態(tài).
調用resume()方法-->當前線程處于:就緒狀態(tài).
獲得CPU時間-->當前線程處于:運行狀態(tài).
調用stop()方法-->當前線程處于:死亡狀態(tài).
狀態(tài)模式的應用場景
通常在以下情況下可以考慮使用狀態(tài)模式谤饭。
- 當一個對象的行為取決于它的狀態(tài)标捺,并且它必須在運行時根據(jù)狀態(tài)改變它的行為時,就可以考慮使用狀態(tài)模式揉抵。
- 一個操作中含有龐大的分支結構亡容,并且這些分支決定于對象的狀態(tài)時。
狀態(tài)模式的擴展
在有些情況下冤今,可能有多個環(huán)境對象需要共享一組狀態(tài)闺兢,這時需要引入享元模式,將這些具體狀態(tài)對象放在集合中供程序共享戏罢,其結構圖如圖 5 所示屋谭。
圖5 共享狀態(tài)模式的結構圖
分析:共享狀態(tài)模式的不同之處是在環(huán)境類中增加了一個 HashMap 來保存相關狀態(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");
}
//設置新狀態(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"));
}
}
程序運行結果如下:
當前狀態(tài)是: 狀態(tài)1
當前狀態(tài)是: 狀態(tài)2
當前狀態(tài)是: 狀態(tài)1
當前狀態(tài)是: 狀態(tài)2
拓展
狀態(tài)模式與責任鏈模式的區(qū)別
狀態(tài)模式和責任鏈模式都能消除 if-else 分支過多的問題桐磁。但在某些情況下,狀態(tài)模式中的狀態(tài)可以理解為責任讲岁,那么在這種情況下我擂,兩種模式都可以使用衬以。
從定義來看,狀態(tài)模式強調的是一個對象內在狀態(tài)的改變校摩,而責任鏈模式強調的是外部節(jié)點對象間的改變看峻。
從代碼實現(xiàn)上來看,兩者最大的區(qū)別就是狀態(tài)模式的各個狀態(tài)對象知道自己要進入的下一個狀態(tài)對象衙吩,而責任鏈模式并不清楚其下一個節(jié)點處理對象互妓,因為鏈式組裝由客戶端負責。
狀態(tài)模式與策略模式的區(qū)別
狀態(tài)模式和策略模式的 UML 類圖架構幾乎完全一樣坤塞,但兩者的應用場景是不一樣的冯勉。策略模式的多種算法行為擇其一都能滿足,彼此之間是獨立的尺锚,用戶可自行更換策略算法珠闰,而狀態(tài)模式的各個狀態(tài)間存在相互關系,彼此之間在一定條件下存在自動切換狀態(tài)的效果瘫辩,并且用戶無法指定狀態(tài),只能設置初始狀態(tài)坛悉。