1. 「補課」進行時:設(shè)計模式(19)——狀態(tài)模式

        共 7835字,需瀏覽 16分鐘

         ·

        2020-12-17 17:56

        1. 前文匯總

        「補課」進行時:設(shè)計模式系列

        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)進行跑動。

        但是問題也隨之而來,我們的英雄類當(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)境角色以實現(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)模式從類圖上看比較簡單,實際上還是比較復(fù)雜的,它提供了一種對物質(zhì)運動的另一個觀察視角, 通過狀態(tài)變更促使行為的變化。

        類似水的狀態(tài)變更一樣, 一碗水的初始狀態(tài)是液態(tài), 通過加熱轉(zhuǎn)變?yōu)椤鈶B(tài), 狀態(tài)的改變同時也引起體積的擴大, 然后就產(chǎn)生了一個新的行為:鳴笛或頂起壺蓋,瓦特就是這么發(fā)明蒸汽機的。

        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實現(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實現(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)點

        • 避免了過多的 if ? else ?語句的使用,避免了程序的復(fù)雜性,提高系統(tǒng)的可維護性。
        • 使用多態(tài)代替了條件判斷,這樣我們代碼的擴展性更強,比如要增加一些狀態(tài),會非常的容易。
        • 狀態(tài)是可以被共享的,狀態(tài)都是由 static final 進行修飾的。

        3.5 缺點

        有優(yōu)點的同事也會產(chǎn)生缺點,有時候,優(yōu)點和缺點的產(chǎn)生其實是同一個事實:

        狀態(tài)模式最主要的一個缺點是:子類會太多,也就是類膨脹。因為一個事物有很多個狀態(tài)也不稀奇,如果完全使用狀態(tài)模式就會有太多的子類,不好管理。

        4. 案例完善

        前面那個 LOL 的例子,如果使用狀態(tài)模式重寫一下,會是這樣的:

        首先創(chuàng)建一個跑動的接口:

        public?interface?RunState?{
        ????void?run(Hero?hero);
        }

        接下來是4個實現(xiàn)類,分別實現(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ù)雜性去換取的高可維護性和擴展性是相當(dāng)值得的,除非增加了復(fù)雜性以后,對于后者的提升會乎其微。





        感謝閱讀



        瀏覽 25
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 伊人春色网 | 亚洲秘 无码一区二区三区密桃 | 蜜乳一区二区三区免费 | 来操逼 | 亚洲最大中文字幕 |