1. 程式人生 > >Java設計模式(二十)之行為型模式:狀態模式

Java設計模式(二十)之行為型模式:狀態模式

 在介紹狀態模式之前,我們先來看這樣一個例項:你公司力排萬難終於獲得某個酒店的系統開發專案,並且最終落到了你的頭上。下圖是他們系統的主要工作(夠簡單)。

當你第一眼看到這個系統的時候你就看出來了這是一個狀態圖,每個框框都代表了房間的狀態,箭頭表示房間狀態的轉換。分析如下:房間有三個狀態:空閒、已預訂、已入住,狀態與狀態之間可以根據客戶的動作來進行轉換。定義每個狀態的值。

    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結構圖:

狀態模式包含如下角色:

(1)Context:環境類。可以包括一些內部狀態。

(2)State:抽象狀態類。State定義了一個所有具體狀態的共同介面,任何狀態都實現這個相同的介面,這樣一來,狀態之間就尅互相轉換了。

(3)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個狀態類,這個三個狀態分別對於這:空閒、預訂、入住。其中空閒可以完成預訂和入住兩個動作,預訂可以完成入住和退訂兩個動作,入住可以退房。

/** 
 * @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() {
        //不需要做操作
    }
}
/** 
 * @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());   //變成空閒狀態
    }
}
/** 
 * @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、優點:

(1)封裝了轉換規則。 

(2)列舉可能的狀態,在列舉狀態之前需要確定狀態種類。 

(3)將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變物件狀態即可改變物件的行為。 

(4)允許狀態轉換邏輯與狀態物件合成一體,而不是某一個巨大的條件語句塊。 

(5)可以讓多個環境物件共享一個狀態物件,從而減少系統中物件的個數。

2、缺點:

(1)狀態模式的使用必然會增加系統類和物件的個數。 

(2)狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂。

(3)狀態模式對 “開閉原則” 的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的原始碼,否則無法切換到新增狀態;而且修改某個狀態類的行為也需修改對應類的原始碼。

3、使用場景:

(1)物件的行為依賴於它的狀態(屬性)並且可以根據它的狀態改變而改變它的相關行為。

(2)程式碼中包含大量與物件狀態有關的條件語句。

4、策略模式和狀態模式比較:

(1)策略模式和狀態模式的結構幾乎完全一致,但是它們的目的和本質完全不一樣。

(2)策略模式是圍繞可以互換的演算法來建立成功業務的,然而狀態模式是通過改變物件內部的狀態來幫助物件控制自己行為的。前者行為是彼此獨立、可以相互替換的,後者行為是不可以相互替換的。