「補(bǔ)課」進(jìn)行時:設(shè)計模式(19)——狀態(tài)模式

1. 前文匯總
2. LOL 中的狀態(tài)
感覺我天天在用 LOL 舉例子,沒辦法,都已經(jīng) S11 了,而我依然在玩這個游戲。
LOL 中的英雄有很多狀態(tài),有正常狀態(tài),有吃了偉哥一樣的加速狀態(tài),有被對方套了虛弱的虛弱狀態(tài),還有被對方控制的眩暈狀態(tài)。
下面來看下,在 LOL 中,初始的英雄狀態(tài):
public?class?Hero?{
????//正常狀態(tài)
????public?static?final?int?COMMON?=?1;
????//加速狀態(tài)
????public?static?final?int?SPEED_UP?=?2;
????//減速狀態(tài)
????public?static?final?int?SPEED_DOWN?=?3;
????//眩暈狀態(tài)
????public?static?final?int?SWIM?=?4;
????//默認(rèn)是正常狀態(tài)
????private?int?state?=?COMMON;
????//跑動線程
????private?Thread?runThread;
????//設(shè)置狀態(tài)
????public?void?setState(int?state)?{
????????this.state?=?state;
????}
????//停止跑動
????public?void?stopRun()?{
????????if?(isRunning())?runThread.interrupt();
????????System.out.println("--------------停止跑動---------------");
????}
????//開始跑動
????public?void?startRun()?{
????????if?(isRunning())?{
????????????return;
????????}
????????final?Hero?hero?=?this;
????????runThread?=?new?Thread(new?Runnable()?{
????????????public?void?run()?{
????????????????while?(!runThread.isInterrupted())?{
????????????????????try?{
????????????????????????hero.run();
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????break;
????????????????????}
????????????????}
????????????}
????????});
????????System.out.println("--------------開始跑動---------------");
????????runThread.start();
????}
????private?boolean?isRunning(){
????????return?runThread?!=?null?&&?!runThread.isInterrupted();
????}
????//英雄類開始奔跑
????private?void?run()?throws?InterruptedException{
????????if?(state?==?SPEED_UP)?{
????????????System.out.println("--------------加速跑動---------------");
????????????Thread.sleep(2000);//假設(shè)加速持續(xù)2秒
????????????state?=?COMMON;
????????????System.out.println("------加速狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????????}else?if?(state?==?SPEED_DOWN)?{
????????????System.out.println("--------------減速跑動---------------");
????????????Thread.sleep(2000);//假設(shè)減速持續(xù)2秒
????????????state?=?COMMON;
????????????System.out.println("------減速狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????????}else?if?(state?==?SWIM)?{
????????????System.out.println("--------------不能跑動---------------");
????????????Thread.sleep(1000);//假設(shè)眩暈持續(xù)2秒
????????????state?=?COMMON;
????????????System.out.println("------眩暈狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????????}else?{
????????????//正常跑動則不打印內(nèi)容
????????}
????}
}
場景類:
public?class?Client?{
????public?static?void?main(String[]?args)?throws?InterruptedException?{
????????Hero?hero?=?new?Hero();
????????hero.startRun();
????????hero.setState(Hero.SPEED_UP);
????????Thread.sleep(2000);
????????hero.setState(Hero.SPEED_DOWN);
????????Thread.sleep(2000);
????????hero.setState(Hero.SWIM);
????????Thread.sleep(2000);
????????hero.stopRun();
????}
}
可以看到,我們的英雄在跑動過程中隨著狀態(tài)的改變,我們的英雄會以不同的狀態(tài)進(jìn)行跑動。
但是問題也隨之而來,我們的英雄類當(dāng)中有明顯的 if else 結(jié)構(gòu),這并不是我們希望看到的,接下來,我們看下狀態(tài)模式。
3. 狀態(tài)模式
3.1 定義
狀態(tài)模式的定義如下:
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(當(dāng)一個對象內(nèi)在狀態(tài)改變時允許其改變行為, 這個對象看起來像改變了其類。)
3.2 通用類圖

State 抽象狀態(tài)角色:接口或抽象類, 負(fù)責(zé)對象狀態(tài)定義, 并且封裝環(huán)境角色以實(shí)現(xiàn)狀態(tài)切換。 ConcreteState 具體狀態(tài)角色:每一個具體狀態(tài)必須完成兩個職責(zé):本狀態(tài)的行為管理以及趨向狀態(tài)處理, 通俗地說,就是本狀態(tài)下要做的事情, 以及本狀態(tài)如何過渡到其他狀態(tài)。 Context 環(huán)境角色:定義客戶端需要的接口, 并且負(fù)責(zé)具體狀態(tài)的切換。
狀態(tài)模式從類圖上看比較簡單,實(shí)際上還是比較復(fù)雜的,它提供了一種對物質(zhì)運(yùn)動的另一個觀察視角, 通過狀態(tài)變更促使行為的變化。
類似水的狀態(tài)變更一樣, 一碗水的初始狀態(tài)是液態(tài), 通過加熱轉(zhuǎn)變?yōu)?、氣態(tài), 狀態(tài)的改變同時也引起體積的擴(kuò)大, 然后就產(chǎn)生了一個新的行為:鳴笛或頂起壺蓋,瓦特就是這么發(fā)明蒸汽機(jī)的。
3.3 通用代碼:
抽象環(huán)境角色:
public?abstract?class?State?{
????//?定義一個環(huán)境角色,提供子類訪問
????protected?Context?context;
????//?設(shè)置環(huán)境資源
????public?void?setContext(Context?context)?{
????????this.context?=?context;
????}
????//?行為1
????abstract?void?handle1();
????//?行為2
????abstract?void?handle2();
}
具體環(huán)境角色:
public?class?ConcreteState1?extends?State?{
????@Override
????void?handle1()?{
????????//本狀態(tài)下必須處理的邏輯
????}
????@Override
????void?handle2()?{
????????//設(shè)置當(dāng)前狀態(tài)為stat2
????????super.context.setCurrentState(Context.STATE2);
????????//過渡到state2狀態(tài),?由Context實(shí)現(xiàn)
????????super.context.handle2();
????}
}
public?class?ConcreteState2?extends?State?{
????@Override
????void?handle1()?{
????????//設(shè)置當(dāng)前狀態(tài)為stat2
????????super.context.setCurrentState(Context.STATE1);
????????//過渡到state2狀態(tài),?由Context實(shí)現(xiàn)
????????super.context.handle1();
????}
????@Override
????void?handle2()?{
????????//?本狀態(tài)下必須處理的邏輯
????}
}
具體環(huán)境角色:
public?class?Context?{
????final?static?State?STATE1?=?new?ConcreteState1();
????final?static?State?STATE2?=?new?ConcreteState2();
????private?State?concreteState;
????public?State?getCurrentState()?{
????????return?concreteState;
????}
????//設(shè)置當(dāng)前狀態(tài)
????public?void?setCurrentState(State?currentState)?{
????????this.concreteState?=?currentState;
????????//切換狀態(tài)
????????this.concreteState.setContext(this);
????}
????public?void?handle1(){
????????this.concreteState.handle1();
????}
????public?void?handle2(){
????????this.concreteState.handle2();
????}
}
環(huán)境角色有兩個不成文的約束:
把狀態(tài)對象聲明為靜態(tài)常量, 有幾個狀態(tài)對象就聲明幾個靜態(tài)常量。 環(huán)境角色具有狀態(tài)抽象角色定義的所有行為, 具體執(zhí)行使用委托方式。
public?class?Client?{
????public?static?void?main(String[]?args)?{
????????//定義環(huán)境角色
????????Context?context?=?new?Context();
????????//初始化狀態(tài)
????????context.setCurrentState(new?ConcreteState1());
????????//行為執(zhí)行
????????context.handle1();
????????context.handle2();
????}
}
這里我們已經(jīng)隱藏了狀態(tài)的變化過程, 它的切換引起了行為的變化。對外來說, 我們只看到行為的發(fā)生改變, 而不用知道是狀態(tài)變化引起的。
3.4 優(yōu)點(diǎn)
避免了過多的 if ? else ?語句的使用,避免了程序的復(fù)雜性,提高系統(tǒng)的可維護(hù)性。 使用多態(tài)代替了條件判斷,這樣我們代碼的擴(kuò)展性更強(qiáng),比如要增加一些狀態(tài),會非常的容易。 狀態(tài)是可以被共享的,狀態(tài)都是由 static final 進(jìn)行修飾的。
3.5 缺點(diǎn)
有優(yōu)點(diǎn)的同事也會產(chǎn)生缺點(diǎn),有時候,優(yōu)點(diǎn)和缺點(diǎn)的產(chǎn)生其實(shí)是同一個事實(shí):
狀態(tài)模式最主要的一個缺點(diǎn)是:子類會太多,也就是類膨脹。因?yàn)橐粋€事物有很多個狀態(tài)也不稀奇,如果完全使用狀態(tài)模式就會有太多的子類,不好管理。
4. 案例完善
前面那個 LOL 的例子,如果使用狀態(tài)模式重寫一下,會是這樣的:
首先創(chuàng)建一個跑動的接口:
public?interface?RunState?{
????void?run(Hero?hero);
}
接下來是4個實(shí)現(xiàn)類,分別實(shí)現(xiàn)不同狀態(tài)的跑動結(jié)果:
public?class?CommonState?implements?RunState?{
????@Override
????public?void?run(Hero?hero)?{
????????//?正常跑動則不打印內(nèi)容,否則會刷屏
????}
}
public?class?SpeedUpState?implements?RunState{
????@Override
????public?void?run(Hero?hero)?{
????????System.out.println("--------------加速跑動---------------");
????????try?{
????????????Thread.sleep(2000);//假設(shè)加速持續(xù)2秒
????????}?catch?(InterruptedException?e)?{}
????????hero.setState(Hero.COMMON);
????????System.out.println("------加速狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????}
}
public?class?SpeedDownState?implements?RunState{
????@Override
????public?void?run(Hero?hero)?{
????????System.out.println("--------------減速跑動---------------");
????????try?{
????????????Thread.sleep(2000);//假設(shè)減速持續(xù)2秒
????????}?catch?(InterruptedException?e)?{}
????????hero.setState(Hero.COMMON);
????????System.out.println("------減速狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????}
}
public?class?SwimState?implements?RunState?{
????@Override
????public?void?run(Hero?hero)?{
????????System.out.println("--------------不能跑動---------------");
????????try?{
????????????Thread.sleep(1000);//假設(shè)眩暈持續(xù)1秒
????????}?catch?(InterruptedException?e)?{}
????????hero.setState(Hero.COMMON);
????????System.out.println("------眩暈狀態(tài)結(jié)束,變?yōu)檎顟B(tài)------");
????}
}
最后是一個 Hero(Context) 類:
public?class?Hero?{
????public?static?final?RunState?COMMON?=?new?CommonState();//正常狀態(tài)
????public?static?final?RunState?SPEED_UP?=?new?SpeedUpState();//加速狀態(tài)
????public?static?final?RunState?SPEED_DOWN?=?new?SpeedDownState();//減速狀態(tài)
????public?static?final?RunState?SWIM?=?new?SwimState();//眩暈狀態(tài)
????private?RunState?state?=?COMMON;//默認(rèn)是正常狀態(tài)
????private?Thread?runThread;//跑動線程
????//設(shè)置狀態(tài)
????public?void?setState(RunState?state)?{
????????this.state?=?state;
????}
????//停止跑動
????public?void?stopRun()?{
????????if?(isRunning())?runThread.interrupt();
????????System.out.println("--------------停止跑動---------------");
????}
????//開始跑動
????public?void?startRun()?{
????????if?(isRunning())?{
????????????return;
????????}
????????final?Hero?hero?=?this;
????????runThread?=?new?Thread(new?Runnable()?{
????????????public?void?run()?{
????????????????while?(!runThread.isInterrupted())?{
????????????????????state.run(hero);
????????????????}
????????????}
????????});
????????System.out.println("--------------開始跑動---------------");
????????runThread.start();
????}
????private?boolean?isRunning(){
????????return?runThread?!=?null?&&?!runThread.isInterrupted();
????}
}
可以看到,這段代碼和開頭那段代碼雖然完成了一樣的功能,但是整個代碼的復(fù)雜度缺以肉眼可見的級別提高了,一般而言,我們犧牲復(fù)雜性去換取的高可維護(hù)性和擴(kuò)展性是相當(dāng)值得的,除非增加了復(fù)雜性以后,對于后者的提升會乎其微。

