在介紹狀態模式之前,我們先來看這樣一個實例:你公司力排萬難終於獲得某個酒店的系統開發項目,並且最終落到了你的頭上。下圖是他們系統的主要工作(夠簡單)。
當你第一眼看到這個系統的時候你就看出來了這是一個狀態圖,每個框框都代表了房間的狀態,箭頭表示房間狀態的轉換。分析如下:房間有三個狀態:空閑、已預訂、已入住,狀態與狀態之間可以根據客戶的動作來進行轉換。定義每個狀態的值。
public static final int FREEMTIME_STATE = 0; //空閑狀態
public static final int BOOKED_STATE = 1; //已預訂狀態
public static final int CHECKIN_STATE = 2; //入住狀態
int state = FREEMTIME_STATE; //初始狀態
通過客戶的動作將每個狀態整合起來,對於這個“最簡單”的方式肯定是if…else if…else啦!所以這里我們就通過動作將所有的狀態全面整合起來。分析得這里有四個動作:預訂、入住、退訂、退房。如下:
/** * @desc 預訂 * @return void */ public void bookRoom(){ if(state == FREEMTIME_STATE){ //空閑可預訂 if(count > 0){ System.out.println("空閑房間,完成預訂..."); state = BOOKED_STATE; //改變狀態:已預訂 count --; //房間預訂完了,提示客戶沒有房源了 if(count == 0){ System.out.println("不好意思,房間已經預訂完,歡迎您下次光臨..."); } } else{ System.out.println("不好意思,已經沒有房間了...."); } } else if(state == BOOKED_STATE){ System.out.println("該房間已經被預訂了..."); } else if(state == CHECKIN_STATE){ System.out.println("該房間已經有人入住了..."); } } /** * @desc 入住 * @return void */ public void checkInRoom(){ if(state == FREEMTIME_STATE){ if(count > 0){ System.out.println("空閑房間,入住..."); state = CHECKIN_STATE; //改變狀態:已預訂 count --; //房間預訂完了,提示客戶沒有房源了 if(count == 0){ System.out.println("不好意思,房間已經預訂完,歡迎您下次光臨..."); } } else{ System.out.println("不好意思,已經沒有房間了...."); } } else if(state == BOOKED_STATE){ if("如果該房間是您預訂的"){ System.out.println("入住...."); state = CHECKIN_STATE; } else{ System.out.println("您沒有預訂該房間,請先預訂..."); } } else if(state == CHECKIN_STATE){ System.out.println("該房間已經入住了..."); } } /** * @desc 退訂 * @return void */ public void unsubscribeRoom(){ if(state == FREEMTIME_STATE){ } else if(state == CHECKIN_STATE){ } else if(state == BOOKED_STATE){ System.out.println("已退訂房間..."); state = FREEMTIME_STATE; count ++; } } /** * @desc 退房 * @return void */ public void checkOutRoom(){ if(state == FREEMTIME_STATE){ } else if(state == BOOKED_STATE){ } else if(state == CHECKIN_STATE){ System.out.println("已退房.."); state = FREEMTIME_STATE; count++; } }
對於上面的代碼你是否滿意呢?滿意那么你就沒有必要往下看了,不滿意我們接着講。
正當你完成這個“復雜”if..else if …else時(我都寫了一會兒),你客戶說,我們需要將某些房間保留下來以作為備用(standbyState),於是你發現你悲劇了,因為你發現你要在所有的操作里都要判斷該房間是否為備用房間。當你老大經過你身邊的時候發現你正在糾結怎么改的時候,你老大就問你為什么不換一個角度思考以狀態為原子來改變它的行為,而不是通過行為來改變狀態呢?於是你就學到了狀態模式。
一、模式定義
在很多情況下,一個對象的行為取決於它的一個或多個變化的屬性,這些屬性我們稱之為狀態,這個對象稱之為狀態對象。對於狀態對象而已,它的行為依賴於它的狀態,比如你要預訂房間,那么只有當該房間為空閑時你才能預訂,你想入住該房間也只有當你預訂了該房間或者該房間為空閑時。對於這樣的一個對象,當它在於外部事件產生互動的時候,其內部狀態就會發生改變,從而使得他的行為也隨之發生改變。
那么何為狀態模式呢?所謂狀態模式就是允許對象在內部狀態發生改變時改變它的行為,對象看起來好像修改了它的類。
二、模式結構
下圖為狀態模式的UML圖。
狀態模式包含如下角色:
Context: 環境類。可以包括一些內部狀態。
State: 抽象狀態類。State定義了一個所有具體狀態的共同接口,任何狀態都實現這個相同的接口,這樣一來,狀態之間就可以互相轉換了。
ConcreteState: 具體狀態類。具體狀態類,用於處理來自Context的請求,每一個ConcreteState都提供了它對自己請求的實現,所以,當Context改變狀態時行為也會跟着改變。
三、模式實現
依然是上面那個酒店的實例。對於該實例的UML圖如下:
首先是狀態接口:State
public interface State { /** * @desc 預訂房間 * @return void */ public void bookRoom(); /** * @desc 退訂房間 * @return void */ public void unsubscribeRoom(); /** * @desc 入住 * @return void */ public void checkInRoom(); /** * @desc 退房 * @return void */ public void checkOutRoom(); }
然后是房間類
public class Room { /* * 房間的三個狀態 */ State freeTimeState; //空閑狀態 State checkInState; //入住狀態 State bookedState; //預訂狀態 State state ; public Room(){ freeTimeState = new FreeTimeState(this); checkInState = new CheckInState(this); bookedState = new BookedState(this); state = freeTimeState ; //初始狀態為空閑 } /** * @desc 預訂房間 * @return void */ public void bookRoom(){ state.bookRoom(); } /** * @desc 退訂房間 * @return void */ public void unsubscribeRoom(){ state.unsubscribeRoom(); } /** * @desc 入住 * @return void */ public void checkInRoom(){ state.checkInRoom(); } /** * @desc 退房 * @return void */ public void checkOutRoom(){ state.checkOutRoom(); } public String toString(){ return "該房間的狀態是:"+getState().getClass().getName(); } /* * getter和setter方法 */ public State getFreeTimeState() { return freeTimeState; } public void setFreeTimeState(State freeTimeState) { this.freeTimeState = freeTimeState; } public State getCheckInState() { return checkInState; } public void setCheckInState(State checkInState) { this.checkInState = checkInState; } public State getBookedState() { return bookedState; } public void setBookedState(State bookedState) { this.bookedState = bookedState; } public State getState() { return state; } public void setState(State state) { this.state = state; } }
然后是3個狀態類,這個三個狀態分別對於這:空閑、預訂、入住。其中空閑可以完成預訂和入住兩個動作,預訂可以完成入住和退訂兩個動作,入住可以退房。
/** * @project: design_state * @author chenssy * @date 2013-8-24 * @Description: 空閑狀態只能預訂和入住 */ public class FreeTimeState implements State { Room hotelManagement; public FreeTimeState(Room hotelManagement){ this.hotelManagement = hotelManagement; } public void bookRoom() { System.out.println("您已經成功預訂了..."); hotelManagement.setState(hotelManagement.getBookedState()); //狀態變成已經預訂 } public void checkInRoom() { System.out.println("您已經成功入住了..."); hotelManagement.setState(hotelManagement.getCheckInState()); //狀態變成已經入住 } public void checkOutRoom() { //不需要做操作 } public void unsubscribeRoom() { //不需要做操作 } }
/** * @project: design_state * @author chenssy * @date 2013-8-24 * @Description: 入住狀態房間只能退房 */ public class BookedState implements State { Room hotelManagement; public BookedState(Room hotelManagement) { this.hotelManagement = hotelManagement; } public void bookRoom() { System.out.println("該房間已近給預定了..."); } public void checkInRoom() { System.out.println("入住成功..."); hotelManagement.setState(hotelManagement.getCheckInState()); //狀態變成入住 } public void checkOutRoom() { //不需要做操作 } public void unsubscribeRoom() { System.out.println("退訂成功,歡迎下次光臨..."); hotelManagement.setState(hotelManagement.getFreeTimeState()); //變成空閑狀態 } }
/** * @project: design_state * @author chenssy * @date 2013-8-24 * @Description: 入住可以退房 */ public class CheckInState implements State { Room hotelManagement; public CheckInState(Room hotelManagement) { this.hotelManagement = hotelManagement; } public void bookRoom() { System.out.println("該房間已經入住了..."); } public void checkInRoom() { System.out.println("該房間已經入住了..."); } public void checkOutRoom() { System.out.println("退房成功...."); hotelManagement.setState(hotelManagement.getFreeTimeState()); //狀態變成空閑 } public void unsubscribeRoom() { //不需要做操作 } }
最后是測試類
public class Test { public static void main(String[] args) { //有3間房 Room[] rooms = new Room[2]; //初始化 for(int i = 0 ; i < rooms.length ; i++){ rooms[i] = new Room(); } //第一間房 rooms[0].bookRoom(); //預訂 rooms[0].checkInRoom(); //入住 rooms[0].bookRoom(); //預訂 System.out.println(rooms[0]); System.out.println("---------------------------"); //第二間房 rooms[1].checkInRoom(); rooms[1].bookRoom(); rooms[1].checkOutRoom(); rooms[1].bookRoom(); System.out.println(rooms[1]); } }
運行結果
四、模式優缺點
優點
1、封裝了轉換規則。
2、枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類。
3、將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為。
4、允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊。
5、可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數。
缺點
1、狀態模式的使用必然會增加系統類和對象的個數。
2、狀態模式的結構與實現都較為復雜,如果使用不當將導致程序結構和代碼的混亂。
3、狀態模式對“開閉原則”的支持並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源代碼,否則無法切換到新增狀態;而且修改某個狀態類的行為也需修改對應類的源代碼。
五、模式適用場景
1、對象的行為依賴於它的狀態(屬性)並且可以根據它的狀態改變而改變它的相關行為。
2、代碼中包含大量與對象狀態有關的條件語句
六、模式總結
1、狀態模式允許一個對象基於內部狀態而擁有不同的行為。
2、Context會將行為委托給當前狀態對象。
3、狀態模式對“開閉原則”支持不是很好。