<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                第26章 狀態模式 26.1 城市的縱向發展功臣——電梯 現在城市發展很快,百萬級人口的城市很多,那其中有兩個東西的發明在城市的發展中起到非常重要的作用:一個是汽車,另一個是電梯。汽車讓城市可以橫向擴展,電梯讓城市可以縱向延伸,向空中伸展。汽車對城市的發展我們就不說了,電梯,你想想看,如果沒有電梯,每天你需要爬15層樓梯,你是不是會累壞了?建筑師設計了一個沒有電梯的建筑,投資者肯定不愿意投資,那也是建筑師的恥辱,今天我們就用程序表現一下這個電梯是怎么運作的。 我們每天都在乘電梯,那我們來看看電梯有哪些動作(映射到Java中就是有多少方法):開門、關門、運行、停止。好,我們就用程序來實現一下電梯的動作,先看類圖設計,如圖26-1所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036ab3f53.jpg) 圖26-1 電梯的類圖 非常簡單的類圖,定義一個接口,然后是一個實現類,然后業務場景類Client就可以調用,并運行起來,簡單也要實現出來。看看該程序的接口,如代碼清單26-1所示。 代碼清單26-1 電梯接口 public?interface?ILift?{ ?????//首先電梯門開啟動作 ?????public?void?open(); ?????//電梯門可以開啟,那當然也就有關閉了 ?????public?void?close(); ?????//電梯要能上能下 ?????public?void?run(); ?????//電梯還要能停下來 ?????public?void?stop(); } 接口有了,再來看實現類,如代碼清單26-2所示。 代碼清單26-2 電梯實現類 public?class?Lift?implements?ILift?{ ?????//電梯門關閉 ?????public?void?close()?{ ?????????????System.out.println("電梯門關閉..."); ?????} ?????//電梯門開啟 ?????public?void?open()?{ ?????????????System.out.println("電梯門開啟..."); ?????} ?????//電梯開始運行起來 ?????public?void?run()?{ ?????????????System.out.println("電梯上下運行起來..."); ?????} ?????//電梯停止 ?????public?void?stop()?{ ?????????????System.out.println("電梯停止了..."); ?????} } 電梯的開、關、運行、停都實現了,再看看場景類是怎么調用的,如代碼清單26-3所示。 代碼清單26-3 場景類 public?class?Client?{ ?????public?static?void?main(String[]?args)?{ ?????????????ILift?lift?=?new?Lift(); ?????????????//首先是電梯門開啟,人進去 ?????????????lift.open(); ?????????????//然后電梯門關閉 ?????????????lift.close(); ?????????????//再然后,電梯運行起來,向上或者向下 ?????????????lift.run(); ?????????????//最后到達目的地,電梯停下來 ?????????????lift.stop(); ?????} } 運行的結果如下所示: 電梯門開啟... 電梯門關閉... 電梯上下運行起來... 電梯停止了... 太簡單的程序了!每個程序員都會寫這個程序,這么簡單的程序還拿出來顯擺,是不是太小看我們的智商了?非也,非也,我們繼續往下分析,這個程序有什么問題?你想想,電梯門可以打開,但不是隨時都可以開,是有前提條件的。你不可能電梯在運行的時候突然開門吧?!電梯也不會出現停止了但是不開門的情況吧?!那要是有也是事故嘛,再仔細想想,電梯的這4個動作的執行都有前置條件,具體點說就是在特定狀態下才能做特定事,那我們來分析一下電梯有哪些特定狀態。 ● 敞門狀態 按了電梯上下按鈕,電梯門開,這中間大概有10秒的時間,那就是敞門狀態。在這個狀態下電梯只能做的動作是關門動作。 ● 閉門狀態 電梯門關閉了,在這個狀態下,可以進行的動作是:開門(我不想坐電梯了)、停止(忘記按路層號了)、運行。 ● 運行狀態 電梯正在跑,上下竄,在這個狀態下,電梯只能做的是停止。 ● 停止狀態 電梯停止不動,在這個狀態下,電梯有兩個可選動作:繼續運行和開門動作。 我們用一張表來表示電梯狀態和動作之間的關系,如圖26-2所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036acc41d.jpg) 圖26-2 電梯狀態和動作對應表(○表示不允許,☆表示允許動作) 看到這張表后,我們才發覺,哦,我們的程序做得很不嚴謹,好,我們來修改一下,如圖26-3所示。 在接口中定義了4個常量,分別表示電梯的4個狀態:敞門狀態、閉門狀態、運行狀態、停止狀態,然后在實現類中電梯的每一次動作發生都要對狀態進行判斷,判斷是否可以執行,也就是動作的執行是否符合業務邏輯,實現類中有4個私有方法是僅僅實現電梯的動作,沒有任何前置條件,因此這4個方法是不能為外部類調用的,設置為私有方法。我們先看接口的改變,如代碼清單26-4所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036adf841.jpg) 圖26-3 增加了狀態的類圖 代碼清單26-4 電梯接口 public?interface?ILift?{ ?????//電梯的4個狀態 ?????public?final?static?int?OPENING_STATE?=?1;??//敞門狀態 ?????public?final?static?int?CLOSING_STATE?=?2;??//閉門狀態 ?????public?final?static?int?RUNNING_STATE?=?3;??//運行狀態 ?????public?final?static?int?STOPPING_STATE?=?4;?//停止狀態 ?????//設置電梯的狀態 ?????public?void?setState(int?state); ?????//首先電梯門開啟動作 ?????public?void?open(); ?????//電梯門可以開啟,那當然也就有關閉了 ?????public?void?close(); ?????//電梯要能上能下,運行起來 ?????public?void?run(); ?????//電梯還要能停下來 ?????public?void?stop(); } 這里增加了4個靜態常量,并增加了一個方法setState,設置電梯的狀態。我們再來看實現類是如何實現的,如代碼清單26-5所示。 代碼清單26-5 電梯實現類 public?class?Lift?implements?ILift?{ ?????private?int?state; ?????public?void?setState(int?state)?{ ?????????????this.state?=?state; ?????} ?????//電梯門關閉 ?????public?void?close()?{ ?????????????//電梯在什么狀態下才能關閉 ?????????????switch(this.state){ ?????????????????????case?OPENING_STATE:??//可以關門,同時修改電梯狀態 ??????????????????????????this.closeWithoutLogic();?? ??????????????????????????this.setState(CLOSING_STATE); ??????????????????????????break; ?????????????????????case?CLOSING_STATE:??//電梯是關門狀態,則什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?RUNNING_STATE:?//正在運行,門本來就是關閉的,也什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?STOPPING_STATE:??//停止狀態,門也是關閉的,什么也不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????} ?????} ?????//電梯門開啟 ?????public?void?open()?{ ?????????????//電梯在什么狀態才能開啟 ?????????????switch(this.state){ ?????????????????????case?OPENING_STATE:?//閉門狀態,什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?CLOSING_STATE:?//閉門狀態,則可以開啟 ??????????????????????????this.openWithoutLogic(); ??????????????????????????this.setState(OPENING_STATE); ??????????????????????????break; ?????????????????????case?RUNNING_STATE:?//運行狀態,則不能開門,什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?STOPPING_STATE:?//停止狀態,當然要開門了 ??????????????????????????this.openWithoutLogic(); ??????????????????????????this.setState(OPENING_STATE); ??????????????????????????break; ?????????????} ?????} ?????//電梯開始運行起來 ?????public?void?run()?{ ?????????????switch(this.state){ ?????????????????????case?OPENING_STATE:?//敞門狀態,什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?CLOSING_STATE:?//閉門狀態,則可以運行 ??????????????????????????this.runWithoutLogic(); ??????????????????????????this.setState(RUNNING_STATE); ??????????????????????????break; ?????????????????????case?RUNNING_STATE:?//運行狀態,則什么都不做 ??????????????????????????//do?nothing; ??????????????????????????break; ?????????????????????case?STOPPING_STATE:?//停止狀態,可以運行 ??????????????????????????this.runWithoutLogic(); ??????????????????????????this.setState(RUNNING_STATE); ????????????????} ?????} ?????//電梯停止 ?????public?void?stop()?{ ????????????????switch(this.state){ ????????????????case?OPENING_STATE:?//敞門狀態,要先停下來的,什么都不做 ?????????????????????//do?nothing; ?????????????????????break; ????????????????case?CLOSING_STATE:?//閉門狀態,則當然可以停止了 ?????????????????????this.stopWithoutLogic(); ?????????????????????this.setState(CLOSING_STATE); ?????????????????????break; ????????????????case?RUNNING_STATE:?//運行狀態,有運行當然那也就有停止了 ?????????????????????this.stopWithoutLogic(); ?????????????????????this.setState(CLOSING_STATE); ?????????????????????break; ????????????????case?STOPPING_STATE:?//停止狀態,什么都不做 ?????????????????????//do?nothing; ?????????????????????break; ?????????????} ?????} ?????//純粹的電梯關門,不考慮實際的邏輯 ?????private?void?closeWithoutLogic(){ ?????????????System.out.println("電梯門關閉..."); ?????} ?????//純粹的電梯開門,不考慮任何條件 ?????private?void?openWithoutLogic(){ ?????????????System.out.println("電梯門開啟..."); ?????} ?????//純粹的運行,不考慮其他條件 ?????private?void?runWithoutLogic(){ ?????????????System.out.println("電梯上下運行起來..."); ?????} ?????//單純的停止,不考慮其他條件 ?????private?void?stopWithoutLogic(){ ?????????????System.out.println("電梯停止了..."); ?????} } 程序有點長,但是還是很簡單的,就是在每一個接口定義的方法中使用switch...case來判斷它是否符合業務邏輯,然后運行指定的動作。我們重新編寫一個場景類來描述一下該環境,如代碼清單26-6所示。 代碼清單26-6 場景類 public?class?Client?{ ?????public?static?void?main(String[]?args)?{ ?????????????ILift?lift?=?new?Lift(); ?????????????//電梯的初始條件應該是停止狀態 ?????????????lift.setState(ILift.STOPPING_STATE); ?????????????//首先是電梯門開啟,人進去 ?????????????lift.open();????? ?????????????//然后電梯門關閉 ?????????????lift.close(); ?????????????//再然后,電梯運行起來,向上或者向下 ?????????????lift.run(); ?????????????//最后到達目的地,電梯停下來 ?????????????lift.stop(); ?????} } 在業務調用的方法中增加了電梯狀態判斷,電梯要不是隨時都可以開的,必須滿足一定條件才能開門,人才能走進去,我們設置電梯的起始是停止狀態,運行結果如下所示: 電梯門開啟... 電梯門關閉... 電梯上下運行起來... 電梯停止了... 我們來想一下,這段程序有什么問題。 ● 電梯實現類Lift有點長 長的原因是我們在程序中使用了大量的switch...case這樣的判斷(if...else也是一樣),程序中只要有這樣的判斷就避免不了加長程序,而且在業務復雜的情況下,程序會更長,這就不是一個很好的習慣了,較長的方法和類無法帶來良好的維護性,畢竟,程序首先是給人閱讀的,然后才是機器執行。 ● 擴展性非常差勁 大家來想想,電梯還有兩個狀態沒有加,是什么?通電狀態和斷電狀態,你要是在程序增加這兩個方法,你看看Open()、Close()、Run()、Stop()這4個方法都要增加判斷條件,也就是說switch判斷體中還要增加case項,這與開閉原則相違背。 ● 非常規狀態無法實現 我們來思考我們的業務,電梯在門敞開狀態下就不能上下運行了嗎?電梯有沒有發生過只有運行沒有停止狀態呢(從40層直接墜到1層嘛)?電梯故障嘛,還有電梯在檢修的時候,可以在stop狀態下不開門,這也是正常的業務需求呀,你想想看,如果加上這些判斷條件,上面的程序有多少需要修改?雖然這些都是電梯的業務邏輯,但是一個類有且僅有一個原因引起類的變化,單一職責原則,看看我們的類,業務任務上一個小小的增加或改動都使得我們這個電梯類產生了修改,這在項目開發上是有很大風險的。 既然我們已經發現程序中有以上問題,我們怎么來修改呢?剛剛我們是從電梯的方法以及這些方法執行的條件去分析,現在我們換個角度來看問題。我們來想,電梯在具有這些狀態的時候能夠做什么事情,也就是說在電梯處于某個具體狀態時,我們來思考這個狀態是由什么動作觸發而產生的,以及在這個狀態下電梯還能做什么事情。例如,電梯在停止狀態時,我們來思考兩個問題: ● 停止狀態是怎么來的,那當然是由于電梯執行了stop方法而來的。 ● 在停止狀態下,電梯還能做什么動作?繼續運行?開門?當然都可以了。 我們再來分析其他3個狀態,也都是一樣的結果,我們只要實現電梯在一個狀態下的兩個任務模型就可以了:這個狀態是如何產生的,以及在這個狀態下還能做什么其他動作(也就是這個狀態怎么過渡到其他狀態),既然我們以狀態為參考模型,那我們就先定義電梯的狀態接口,類圖如圖26-4所示。 ![](https://box.kancloud.cn/2016-08-14_57b0036b02d19.jpg) 圖26-4 以狀態作為導向的類圖 在類圖中,定義了一個LiftState抽象類,聲明了一個受保護的類型Context變量,這個是串聯各個狀態的封裝類。封裝的目的很明顯,就是電梯對象內部狀態的變化不被調用類知曉,也就是迪米特法則了(我的類內部情節你知道得越少越好),并且還定義了4個具體的實現類,承擔的是狀態的產生以及狀態間的轉換過渡,我們先來看LiftState代碼,如代碼清單26-7所示。 代碼清單26-7 抽象電梯狀態 public?abstract?class?LiftState{ ?????//定義一個環境角色,也就是封裝狀態的變化引起的功能變化 ?????protected?Context?context; ?????public?void?setContext(Context?_context){ ?????????????this.context?=?_context; ?????} ?????//首先電梯門開啟動作 ?????public?abstract?void?open(); ?????//電梯門有開啟,那當然也就有關閉了 ?????public?abstract?void?close(); ?????//電梯要能上能下,運行起來 ?????public?abstract?void?run(); ?????//電梯還要能停下來 ?????public?abstract?void?stop(); } 抽象類比較簡單,我們先看一個具體的實現——敞門狀態的實現類,如代碼清單26-8所示。 代碼清單26-8 敞門狀態 public?class?OpenningState?extends?LiftState?{ ?????//開啟當然可以關閉了,我就想測試一下電梯門開關功能 ?????@Override ?????public?void?close()?{ ?????????????//狀態修改 ?????????????super.context.setLiftState(Context.closeingState); ?????????????//動作委托為CloseState來執行 ?????????????super.context.getLiftState().close(); ?????} ?????//打開電梯門 ?????@Override ?????public?void?open()?{ ?????????????System.out.println("電梯門開啟..."); ?????} ?????//門開著時電梯就運行跑,這電梯,嚇死你! ?????@Override ?????public?void?run()?{ ?????????????//do?nothing; ?????} ?????//開門還不停止? ?????public?void?stop()?{ ?????????????//do?nothing; ?????} } 我來解釋一下這個類的幾個方法,Openning狀態是由open()方法產生的,因此,在這個方法中有一個具體的業務邏輯,我們是用print來代替了。在Openning狀態下,電梯能過渡到其他什么狀態呢?按照現在的定義的是只能過渡到Closing狀態,因此我們在Close()中定義了狀態變更,同時把Close這個動作也委托了給CloseState類下的Close方法執行,這個可能不好理解,我們再看看Context類可能好理解一點,如代碼清單26-9所示。 代碼清單26-9 上下文類 public?class?Context?{ ?????//定義出所有的電梯狀態 ?????public?final?static?OpenningState?openningState?=?new?OpenningState(); ?????public?final?static?ClosingState?closeingState?=?new?ClosingState(); ?????public?final?static?RunningState?runningState?=?new?RunningState(); ?????public?final?static?StoppingState?stoppingState?=?new?StoppingState(); ?????//定義一個當前電梯狀態 ?????private?LiftState?liftState; ?????public?LiftState?getLiftState()?{ ?????????????return?liftState; ?????} ?????public?void?setLiftState(LiftState?liftState)?{ ?????????????this.liftState?=?liftState; ?????????????//把當前的環境通知到各個實現類中 ?????????????this.liftState.setContext(this); ?????} ?????public?void?open(){ ?????????????this.liftState.open(); ?????} ?????public?void?close(){ ?????????????this.liftState.close(); ?????} ?????public?void?run(){ ?????????????this.liftState.run(); ?????} ?????public?void?stop(){ ?????????????this.liftState.stop(); ?????} } 結合以上3個類,我們可以這樣理解:Context是一個環境角色,它的作用是串聯各個狀態的過渡,在LiftSate抽象類中我們定義并把這個環境角色聚合進來,并傳遞到子類,也就是4個具體的實現類中自己根據環境來決定如何進行狀態的過渡。關閉狀態如代碼清單26-10所示。 代碼清單26-10 關閉狀態 public?class?ClosingState?extends?LiftState?{ ?????//電梯門關閉,這是關閉狀態要實現的動作 ?????@Override ?????public?void?close()?{ ?????????????System.out.println("電梯門關閉..."); ?????} ?????//電梯門關了再打開 ?????@Override ?????public?void?open()?{ ?????????????super.context.setLiftState(Context.openningState);??//置為敞門狀態 ?????????????super.context.getLiftState().open(); ?????} ?????//電梯門關了就運行,這是再正常不過了 ?????@Override ?????public?void?run()?{ ?????????????super.context.setLiftState(Context.runningState);?//設置為運行狀態 ?????????????super.context.getLiftState().run(); ?????} ?????//電梯門關著,我就不按樓層 ?????@Override ?????public?void?stop()?{ ?????????????super.context.setLiftState(Context.stoppingState);??//設置為停止狀態 ?????????????super.context.getLiftState().stop(); ?????} } 運行狀態如代碼清單26-11所示。 代碼清單26-11 運行狀態 public?class?RunningState?extends?LiftState?{ ?????//電梯門關閉?這是肯定的 ?????@Override ?????public?void?close()?{ ?????????????//do?nothing ?????} ?????//運行的時候開電梯門?你瘋了!電梯不會給你開的 ?????@Override ?????public?void?open()?{ ?????????????//do?nothing ?????} ?????//這是在運行狀態下要實現的方法 ?????@Override ?????public?void?run()?{ ?????????????System.out.println("電梯上下運行..."); ?????} ?????//這絕對是合理的,只運行不停止還有誰敢坐這個電梯?!估計只有上帝了 ?????@Override ?????public?void?stop()?{ ?????????????super.context.setLiftState(Context.stoppingState);//環境設置為停止狀態 ?????????????super.context.getLiftState().stop(); ?????} } 停止狀態如代碼清單26-12所示。 代碼清單26-12 停止狀態 public?class?StoppingState?extends?LiftState?{ ?????//停止狀態關門?電梯門本來就是關著的! ?????@Override ?????public?void?close()?{ ?????????????//do?nothing; ?????} ?????//停止狀態,開門,那是要的! ?????@Override ?????public?void?open()?{ ?????????????super.context.setLiftState(Context.openningState); ?????????????super.context.getLiftState().open(); ?????} ?????//停止狀態再運行起來,正常得很 ?????@Override ?????public?void?run()?{ ?????????????super.context.setLiftState(Context.runningState); ?????????????super.context.getLiftState().run(); ?????} ?????//停止狀態是怎么發生的呢?當然是停止方法執行了 ?????@Override ?????public?void?stop()?{ ?????????????System.out.println("電梯停止了..."); ?????} } 業務邏輯都已經實現了,我們看看怎么來模擬場景類,如代碼清單26-13所示。 代碼清單26-13 場景類 public?class?Client?{ ?????public?static?void?main(String[]?args)?{ ?????????????Context?context?=?new?Context(); ?????????????context.setLiftState(new?ClosingState()); ?????????????context.open(); ?????????????context.close(); ?????????????context.run(); ?????????????context.stop(); ?????} } Client場景類太簡單了,只要定義一個電梯的初始狀態,然后調用相關的方法,就完成了,完全不用考慮狀態的變更,運行結果完全相同,不再贅述。 我們再來回顧一下我們剛剛批判的上一段代碼。首先是代碼太長,這個問題已經解決了,通過各個子類來實現,每個子類的代碼都很短,而且也取消了switch...case條件的判斷。其次是不符合開閉原則,那如果在我們這個例子中要增加兩個狀態應該怎么做呢?增加兩個子類,一個是通電狀態,另一個是斷電狀態,同時修改其他實現類的相應方法,因為狀態要過渡,那當然要修改原有的類,只是在原有類中的方法上增加,而不去做修改。再次是不符合迪米特法則,我們現在的各個狀態是單獨的類,只有與這個狀態有關的因素修改了,這個類才修改,符合迪米特法則,非常完美!這就是狀態模式。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看