理解状态模式笔记
看完《設計模式之禪》這本書,讓我對前輩的智慧更加佩服,23種設計模式與設計模式之間的配合,足以讓開發人員針對特定的場景下寫出近乎完美的邏輯,真正的做到
的擁抱變化。
23種設計模式中,并不都是晦澀難懂,有的簡單,有的還需要理解,其中讓我印象最深的是狀態模式。當初理解這個模式還是廢了一番周折,所以
這篇文章的目的還是梳理一下思路并記錄一下,如果能幫到其他人就更好了。
讓我們來看看狀態模式的定義:當一個對象內在狀態改變時允許其改變行為,這個對象看起來像改變了其類。
乍一看定義,并不是很容易讓人理解,什么又是改變內在狀態,有又改變了行為,又改變了類的(我當初看的也是很懵懂),接下來通過一個例子來理解它。
城市的縱向發展離不開電梯,就以電梯為例。
電梯的狀態有停止、運行、開門和關門等狀態。而且每個狀態還都要有特定的行為,比如在開門的狀態下,電梯只能關門,而不能運行;在關門狀態下,電梯可以運行、開門等。用一張表來表示這個關系:
| 開門狀態 | X | O | X | X |
| 關門狀態 | O | X | O | O |
| 運行狀態 | X | X | X | O |
| 停止狀態 | O | X | O | X |
這種情況下,一般做法是先根據依賴倒置原則定義出接口,電梯一共有四種狀態,所以定義了四個常量表示這幾種狀態,實現類中電梯的每一次動作發生都要對狀態進行判斷,看是否可以執行動作。類圖如下(借用書中類圖):
電梯接口:
``` 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(); } ```實現類負責狀態的實現與狀態過渡:
private int state;@Overridepublic void setState(int state) {this.state = state;}//執行關門動作@Overridepublic void close() {switch (this.state) {case OPENING_STATE:System.out.println("電梯關門了。。。");//只有開門狀態可以關閉電梯門,可以對應電梯狀態表來看this.setState(CLOSING_STATE);//關門之后電梯就是關閉狀態了break;case CLOSING_STATE://do nothing //已經是關門狀態,不能關門break;case RUNNING_STATE://do nothing //運行時電梯門是關著的,不能關門break;case STOPPING_STATE://do nothing //停止時電梯也是關著的,不能關門break;}}//執行開門動作@Overridepublic void open() {switch (this.state) {case OPENING_STATE://門已經開了,不能再開門了//do nothingbreak;case CLOSING_STATE://關門狀態,門打開:System.out.println("電梯門打開了。。。");this.setState(OPENING_STATE);break;case RUNNING_STATE://do nothing 運行時電梯不能開門break;case STOPPING_STATE:System.out.println("電梯門開了。。。");//電梯停了,可以開門了this.setState(OPENING_STATE);break;}}//執行運行動作@Overridepublic void run() {switch (this.state) {case OPENING_STATE://電梯不能開著門就走//do nothingbreak;case CLOSING_STATE://門關了,可以運行了System.out.println("電梯開始運行了。。。");this.setState(RUNNING_STATE);//現在是運行狀態break;case RUNNING_STATE://do nothing 已經是運行狀態了break;case STOPPING_STATE:System.out.println("電梯開始運行了。。。");this.setState(RUNNING_STATE);break;}}//執行停止動作@Overridepublic void stop() {switch (this.state) {case OPENING_STATE: //開門的電梯已經是是停止的了(正常情況下)//do nothingbreak;case CLOSING_STATE://關門時才可以停止System.out.println("電梯停止了。。。");this.setState(STOPPING_STATE);break;case RUNNING_STATE://運行時當然可以停止了System.out.println("電梯停止了。。。");this.setState(STOPPING_STATE);break;case STOPPING_STATE://do nothingbreak;}} }具體的解釋在注釋中都有,當然上面這段程序也可以再優化一下,將重復執行語句封裝成一個方法(不考慮狀態切換規則的狀態方法)
場景類:
public static void main(String[] agres) {Lift lift = new Lift();lift.setState(ILift.STOPPING_STATE);//電梯是停止的lift.open();//開門lift.close();//關門lift.run();//運行lift.stop();//停止} }運行一下看結果:
實現邏輯非常完美,但還有些問題:
- Lift中每個動作執行都要進行switch判斷,造成代碼過長,代碼閱讀困難。
- 擴展性非常差,如果電梯新增一個狀態,接口要改,實現方法也要大量修改,與開閉原則違背。
要解決問題首先要搞清問題,以上需求主要分為兩個:狀態和動作。
狀態是如何產生的,以及這個狀態怎么過渡到其他狀態(執行動作)
使用狀態模式,首先以狀態為參考模型:
將電梯的每個狀態都封裝了起來,再通過場景類Context來切換。
定義一個LiftState抽象類,聲明了一個受保護的類型Context變量,這個是串聯各個狀態的封裝類。封裝的目的很明顯,就是電梯對象內部狀態的變化不被調用類知 曉,也就是迪米特法則了(我的類內部情節你知道得越少越好):
電梯開門的實現類(其他具體環境實現類似):
//開啟當然可以關閉了,我就想測試一下電梯門開關功能@Overridepublic void open() {System.out.println("電梯門開啟...");}@Overridepublic void close() {//雖然可以關門,但這個動作不歸我執行//狀態修改super.context.setLiftState(Context.closeingState);//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作super.context.getLiftState().close();}//電梯門不能開著就跑,這里什么也不做@Overridepublic void run() {//do nothing}//開門狀態已經是停止的了@Overridepublic void stop() {//do nothing} }Openning狀態是由open()方法產生的,因此,在這個 方法中有一個具體的業務邏輯,在Openning狀態下,電梯能過渡到 其他什么狀態呢?按照現在的定義的是只能過渡到Closing狀態,因此我們在Close()中定義了 狀態變更,同時把Close這個動作也委托了給CloseState類下的Close方法執行,這個可能不好 理解,我們再看看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 Listate liftState;public Listate getLiftState() {return this.liftState;}public void setLiftState(Listate 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();} }這就是狀態模式,書中講的很清楚。
總的來說,狀態模式由三個角色組成:抽象狀態角色、具體狀態角色和環境角色。具體狀態角色只負責處理本狀態必須完成的任務和決定是否可以過渡到其他狀態兩個職責,Context負責狀態的過渡,這樣將過渡與狀態分離,提高了程序可擴展性和代碼的可閱讀性。
####狀態模式優缺點:
- 優點:
● 結構清晰,避免了程序的復雜,提高了系統可維護性。
● 遵循了設計原則。很要的體現了開閉原則和單一職責原則,每個狀態都是一個子類,與單一職責原則高度符合,擴展狀態只需增加子類,正是開閉原則的體現。封裝性與迪米特法則也非常吻合。 - 缺點:
只有一個缺點,如果一個事務有很多個狀態,就會造成子類太多的問題。需要在項目使用時來衡量是否使用狀態模式。
####使用場景:
● 行為隨狀態改變而改變的的場景,例如權限設計,人員對象權限不同執行相同的行為其結果也會不同。
● 條件、分支判斷語句替代者,通過擴展子類實現條件判斷處理。
*注意:狀態模式適用于當某個對象在它的狀態發生改變時,它的行為也隨著發生比較大的變化,也就是說在行為受狀態約束的情況下可以使用狀態模式,而且使用時對象的狀態最好不 要超過5個。
最后貼個狀態模式通用類圖:
組成狀態模式三元素的關系非常清晰,具體狀態由ConcreteState實現,具體過渡由Context實現,State負責對所有狀態的封裝,也是依賴倒置原則的體現。
總結
- 上一篇: vba中定时器的用法
- 下一篇: se服务器系统,使用CloneZilla