對象的狀態由各個屬性的當前值構成
當我們調用某個對象的setXXX()方法時
通常表示修改它的XXX屬性
另外
對象在執行方法時
也可能修改自己的狀態
在某些情形下
例如建立事務或機器模型時
對象的狀態可能是決定其行為的關鍵因素
依賴於狀態的代碼邏輯可能遍布於類的大量方法
State模式的目標就是簡化這類代碼
把依賴於狀態的邏輯集中到一組類
每一個類代表一種不同的狀態
避免if語句嵌套過深或過於復雜
轉而依賴於多態性來調用不同的方法
狀態模型
如果對象的狀態信息很關鍵
對象會擁有一些變量來指示如何根據狀態做出相應的動作
這些變量大量地散布於復雜的多層嵌套if語句中
來描述對象如何響應可能出現的事件
用這種方式建立對象模型的最大缺點在於if語句可能變得相當復雜一旦要修改對象的狀態模型
往往有多個方法的許多if語句需要調整
以傳送帶的門為例
考慮其狀態變化過程為
傳送帶的門由單個按鈕控制
並且假設初始時處於關閉狀態
按一下按鈕門開始打開
如果在門完全打開之前再次按下按鈕
門開始關閉
一旦門完全打開
它將在
秒延時之後自動開始關閉過程
要禁止門自動關閉
可以在門打開之後按一下按鈕
圖
描述了傳送門的狀態變化情況
它是一個UML狀態機(State Machine)
其中click表示按下按鈕的動作
顯然
與純文字描述相比UML狀態機圖示更加直觀易懂
按照常規的設計思路(不使用State設計模式)
在模擬傳送帶工作過程的軟件中
可以使用一個Door
對象代表傳送門(如圖
所示)
狀態改變事件由傳送帶軟件發送給Door
對象
圖
UML狀態機
圖
狀態改變事件發送給Door
對象
Door
類從Observable派生
這樣客戶程序(例如一個GUI程序)就能夠方便地了解傳送門狀態
Door
類首先定義傳送門可能處於的狀態
代碼如下
public class Door
extends Observable {
public static final int CLOSED =
;
public static final int OPENING =
;
public static final int OPEN =
;
public static final int CLOSING =
;
public static final int STAYOPEN =
;
private int state = CLOSED;
//
}
status()方法返回傳送門狀態的文字描述
如下所示
public String status() {
switch (state) {
case OPENING :
return "正在打開";
case OPEN :
//
default :
return "已關閉";
}
}
當用戶點擊傳送帶的按鈕時
傳送帶程序調用Door
對象的click()方法
click()方法模擬圖
所示的狀態裝換過程
public void click() {
if (state == CLOSED) {
setState(OPENING);
}
else if (state == OPENING || state == STAYOPEN) {
setState(CLOSING);
}
else if (state == OPEN) {
setState(STAYOPEN);
}
else if (state == CLOSING) {
setState(OPENING);
}
}
Door
類的setState()方法向觀察者通知傳送門狀態改變事件
代碼如下
private void setState(int state) {
this
state = state;
setChanged();
notifyObservers();
}
用State模式改造
Door
類的代碼比較復雜
整個類到處都用到了狀態變量
如果要比較圖
的狀態機和Door
類的各個狀態變換方法
將是非常困難的
click()方法尤其如此
那麼
怎樣在這個例子中應用State模式呢?首先要把傳送門的各種狀態分別定義成類
如圖
所示
圖
能夠更好地與圖
的狀態機對應
更改後的類設計中
Door
包含了狀態機的上下文信息
所謂上下文信息
就是描述環境和一系列其它對象相關的信息
就本例而言
State利用一個上下文對象記錄了傳送門的當前狀態是DoorState類的哪一個實例
圖
傳送門各個狀態
DoorState類的構造函數要求提供一個Door
對象
DoorState的子類利用該對象傳達狀態變更信息
在這種設計方案中
DoorState的子類通過一個Door
類型的屬性綁定到特定的傳送門(Door
)對象
因而要求一個DoorState對象只被一個Door
對象引用
同時Door類要把它的狀態信息定義成局部變量
代碼如下
public class Door
extends Observable {
public final DoorState CLOSED = new DoorClosed(this);
// 按照類似方式定義DoorState類型的
// OPENING
OPEN
CLOSING
STAYOPEN對象(略)
private DoorState state = CLOSED;
//
}
DoorState類是一個抽象類
由子類實現其click()方法
在狀態機中
每一個狀態均有相應的
按下按鈕
操作
修改後的設計中每一個描述狀態的類也有一個click()方法
兩者是一致的
DoorState類處理了其它可能的變換
所以DoorState的子類可以忽略無關的事件
代碼如下
public abstract class DoorState {
protected Door
door;
public DoorState(Door
door) {
this
door = door;
}
public abstract void click();
public String status() {
String s = getClass()
getName();
return s
substring(s
lastIndexOf(
) +
);
}
public void complete() { }
public void timeout() { }
}
由上可以看到
現在的status()方法要比修改設計方案之前的status()方法簡單多了
新status()方法返回的結果與修改前版本的結果略有不同
它的狀態信息從類的名稱獲得
如果要返回修改設計方案之前的信息
只需把這些狀態信息分別記錄到DoorState的各個子類中
然後在這個status()方法中直接提取即可
新的設計方案中
傳送門對象(Door
)從傳送帶接收狀態改變信息的這一角色仍未改變
但現在Door
對象只需把這些狀態改變信息直接傳遞給當前的狀態對象就可以了
代碼如下
public class Door
extends Observable {
//
聲明變量
protected void setState(DoorState state) {
this
state = state;
setChanged();
notifyObservers();
}
public void click() {
state
click();
}
// complete()
status()
timeout()都直接
// 調用state的相應方法即可(略)
}
這裡的click()
complete()
status()和timeout()方法體現了Java類多態性的應用
所有這些方法都起著判斷和選擇動作的作用
即是雖然每一個方法的代碼是不含if判斷邏輯的
但實際運行時被調用的狀態對象卻不斷變化
在調用click()時會發生哪些事情呢?按照多態性規則
答案依賴於當時傳送門的狀態
修改後的代碼有效地擔負起了根據狀態執行不同動作的任務
但由於利用了多態性
它變得更加簡單了
Door
類中的setState()方法現在由DoorState的子類調用
這些DoorState的子類與圖
狀態機中的相應實體很相似
例如
狀態機中Open狀態包含Timeout和Click
DoorOpen類則包含兩個對應的方法timeout()和click()
DoorOpen類的代碼如下
public class DoorOpen extends DoorState {
public DoorOpen(Door
door) {
super(door);
}
public void click() {
door
setState(door
STAYOPEN);
}
public void timeout() {
door
setState(door
CLOSING);
}
}
從上面可以看到
利用State設計模式之後
代碼變得更簡單了
不過
細心的讀者或許已經注意到
Door
類用到的
常量
實際上是變量
這給人一種不規范的感覺
假設現在要把這些狀態常量移到_DoorConstant接口
這就需要從DoorState類消除Door
實例變量
修改辦法是
重新定義DoorState類中的click()
complete()和timeout()變換方法
把一個Door
對象以參數的形式傳遞給它們
按照這種設計方法
Door
對象調用狀態變換方法
例如click()時
將采用state
click(this)的形式
From:http://tw.wingwit.com/Article/program/Java/gj/201311/27535.html