设计模式之状态模式详解
1 概述
【例】通過按鈕來控制一個電梯的狀態,一個電梯有開門狀態,關門狀態,停止狀態,運行狀態。每一種狀態改變,都有可能要根據其他狀態來更新處理。例如,如果電梯門現在處于運行時狀態,就不能進行開門操作,而如果電梯門是停止狀態,就可以執行開門操作。
類圖如下:
代碼如下:
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(); }public class Lift implements ILift {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 class Client {public static void main(String[] args) {Lift lift = new Lift();lift.setState(ILift.STOPPING_STATE);//電梯是停止的lift.open();//開門lift.close();//關門lift.run();//運行lift.stop();//停止} }問題分析:
- 使用了大量的switch…case這樣的判斷(if…else也是一樣),使程序的可閱讀性變差。
- 擴展性以及維護性都很差。如果新加了斷電的狀態,我們需要修改上面判斷邏輯
為了解決這些問題,我們可以使用狀態模式,在狀態模式中,我們將對象在每一個狀態下的行為和狀態轉移語句封裝在一個個狀態類中,通過這些狀態類來分散冗長的條件轉移語句,讓系統具有更好的靈活性和可擴展性,狀態模式可以在一定程度上解決上述問題。
也就是說 狀態模式用于解決系統中復雜對象的狀態轉換以及不同狀態下行為的封裝問題。當系統中某個對象存在多個狀態,這些狀態之間可以進行轉換,而且對象在不同狀態下行為不相同時可以使用狀態模式。
狀態模式將一個對象的狀態從該對象中分離出來,封裝到專門的狀態類中,使得對象狀態可以靈活變化,對于客戶端而言,無須關心對象狀態的轉換以及對象所處的當前狀態,無論對于何種狀態的對象,客戶端都可以一致處理。
**基于與策略模式相同的 考慮,**首先將以上的類中每個狀態的功能單獨封裝在一個類中。這樣,就得到一個和策略模式相 同結構的類的結構體,稱為狀態模式。
狀態模式定義:
對有狀態的對象,把復雜的“判斷邏輯”提取到不同的狀態對象中,允許狀態對象在其內部狀態發生改變時改變其行為。
6.5.2 結構
狀態模式包含以下主要角色。
-
環境(Context)角色:也叫環境類, 又稱為上下文類,它是擁有多種狀態的對象。由于環境類的狀態存在多樣性且在不同狀態下對象的行為有所不同,因此將狀態獨立出去形成單獨的狀態類。在環境類中維護一個抽象狀態類State的實例,這個實例定義當前狀態,在具體實現時,它是一個State子類的對象。
環境類實際上是真正擁有狀態的對象,我們只是將環境類中與狀態有關的代碼提取出來封裝到專門的狀態類中。
-
抽象狀態(State)角色: 它用于定義一個接口以封裝與環境類的一個特定狀態相關的行為,在抽象狀態類中聲明了各種不同狀態對應的方法,而在其子類中實現類這些方法,由于不同狀態下對象的行為可能不同,因此在不同子類中方法的實現可能存在不同,相同的方法可以寫在抽象狀態類中。
-
具體狀態(Concrete State)角色: 它是抽象狀態類的子類,每一個子類實現一個與環境類的一個狀態相關的行為,每一個具體狀態類對應環境的一個具體狀態,不同的具體狀態類其行為有所不同。
在狀態模式中,我們將對象在不同狀態下的行為封裝到不同的狀態類中,為了讓系統具有更好的靈活性和可擴展性,同時對各狀態下的共有行為進行封裝,我們需要對狀態進行抽象,引入了抽象狀態類角色 。
6.5.3 案例實現
對上述電梯的案例使用狀態模式進行改進。類圖如下:
代碼如下:
//抽象狀態類 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(); }//開啟狀態 public class OpenningState extends LiftState {//開啟當然可以關閉了,我就想測試一下電梯門開關功能@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} }//運行狀態 public class RunningState extends LiftState {//運行的時候開電梯門?你瘋了!電梯不會給你開的@Overridepublic void open() {//do nothing}//電梯門關閉?這是肯定了@Overridepublic void close() {//雖然可以關門,但這個動作不歸我執行//do nothing}//這是在運行狀態下要實現的方法@Overridepublic void run() {System.out.println("電梯正在運行...");}//這個事絕對是合理的,光運行不停止還有誰敢做這個電梯?!估計只有上帝了@Overridepublic void stop() {super.context.setLiftState(Context.stoppingState);super.context.stop();} }//停止狀態 public class StoppingState extends LiftState {//停止狀態,開門,那是要的!@Overridepublic void open() {//狀態修改super.context.setLiftState(Context.openningState);//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作super.context.getLiftState().open();}@Overridepublic void close() {//雖然可以關門,但這個動作不歸我執行//狀態修改super.context.setLiftState(Context.closeingState);//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作super.context.getLiftState().close();}//停止狀態再跑起來,正常的很@Overridepublic void run() {//狀態修改super.context.setLiftState(Context.runningState);//動作委托為CloseState來執行,也就是委托給了ClosingState子類執行這個動作super.context.getLiftState().run();}//停止狀態是怎么發生的呢?當然是停止方法執行了@Overridepublic void stop() {System.out.println("電梯停止了...");} }//關閉狀態 public class ClosingState extends LiftState {@Override//電梯門關閉,這是關閉狀態要實現的動作public void close() {System.out.println("電梯門關閉...");}//電梯門關了再打開,逗你玩呢,那這個允許呀@Overridepublic void open() {super.context.setLiftState(Context.openningState);super.context.open();}//電梯門關了就跑,這是再正常不過了@Overridepublic void run() {super.context.setLiftState(Context.runningState);super.context.run();}//電梯門關著,我就不按樓層@Overridepublic void stop() {super.context.setLiftState(Context.stoppingState);super.context.stop();} }//環境角色 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 this.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();} }//測試類 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();} }6.5.4 優缺點
狀態模式將一個對象在不同狀態下的不同行為封裝在一個個狀態類中,通過設置不同的狀態對象可以讓環境對象擁有不同的行為,而狀態轉換的細節對于客戶端而言是透明的,方便了客戶端的使用。在實際開發中,狀態模式具有較高的使用頻率,在工作流和游戲開發中狀態模式都得到了廣泛的應用,例如公文狀態的轉換、游戲中角色的升級等。
1,優點:
- 封裝了狀態的轉換規則,在狀態模式中可以將狀態的轉換代碼封裝在環境類或者具體狀態類中,可以對狀態轉換代碼進行集中管理,而不是分散在一個個業務方法中。
- 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
- 可以****讓多個環境對象共享一個狀態對象****,從而減少系統中對象的個數。
2,缺點:
- 狀態模式的使用必然會增加系統類和對象的個數。
- 狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
- 狀態模式對"開閉原則"的支持并不太好。 增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法轉換到新增狀態;而且修改某個狀態類的行為也需修改對應類的源代碼。
6.5.5 使用場景
- 對象的行為依賴于它的狀態(如某些屬性值),狀態的改變將導致行為的變化。
- 在代碼中包含大量與對象狀態有關的條件語句,這些條件語句的出現,會導致代碼的可維護性和靈活性變差,不能方便地增加和刪除狀態,并且導致客戶類與類庫之間的耦合增強。
策略模式和狀態模式的區別
策略模式用來處理一組具有相同目的但是實現方法不同的算法,這些算法方案之間一般來 說沒有狀態變遷,并且用戶總是從幾個算法中間選取一個。例如,以不同的格式保存文件,以不 同的算法壓縮文件 以不同的算法排序,以不同的格式繪制同樣數據的圖形,以不同的方法顯示 信息 等等。
狀態模式則有所不同,它實現的一個概念可以叫做動態繼承,也就是繼承的( 代表一個狀 態)子類都可以發生變化。狀態的變化可以由一個狀態遷移圖表示。在實現中,如果狀態的變 化由狀態類完成,則在整個變化過程中某些狀態之間需要插入一個新的狀態或者狀態的順序之 間發生變化, 客戶程序是根本不用改變的,即對客戶是不可見的。
一般地說,使用狀態模式要比使用策略模式在設計與實現方面會更加復雜一些,原因是用戶 需要仔細地考慮由誰負責狀態轉換問題,是由 Context 類負責還是由狀態類的超類負責 還是由 狀態類的子類負責等。
總結
以上是生活随笔為你收集整理的设计模式之状态模式详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 完全卸载 Minikube/Uninst
- 下一篇: asp.net ajax控件工具集 Au