對象的狀態(tài)由各個屬性的當(dāng)前值構(gòu)成。當(dāng)我們調(diào)用某個對象的setXXX()方法時,通常表示修改它的XXX屬性。另外,對象在執(zhí)行方法時,也可能修改自己的狀態(tài)。在某些情形下,例如建立事務(wù)或機器模型時,對象的狀態(tài)可能是決定其行為的關(guān)鍵因素,依賴于狀態(tài)的代碼邏輯可能遍布于類的大量方法。State模式的目標就是簡化這類代碼,把依賴于狀態(tài)的邏輯集中到一組類,每一個類代表一種不同的狀態(tài),避免if語句嵌套過深或過于復(fù)雜,轉(zhuǎn)而依賴于多態(tài)性來調(diào)用不同的方法。
狀態(tài)模型
如果對象的狀態(tài)信息很關(guān)鍵,對象會擁有一些變量來指示如何根據(jù)狀態(tài)做出相應(yīng)的動作。這些變量大量地散布于復(fù)雜的多層嵌套if語句中,來描述對象如何響應(yīng)可能出現(xiàn)的事件。用這種方式建立對象模型的缺點在于if語句可能變得相當(dāng)復(fù)雜一旦要修改對象的狀態(tài)模型,往往有多個方法的許多if語句需要調(diào)整。
以傳送帶的門為例,考慮其狀態(tài)變化過程為:傳送帶的門由單個按鈕控制,并且假設(shè)初始時處于關(guān)閉狀態(tài)。按一下按鈕門開始打開,如果在門完全打開之前再次按下按鈕,門開始關(guān)閉。一旦門完全打開,它將在2秒延時之后自動開始關(guān)閉過程。要禁止門自動關(guān)閉,可以在門打開之后按一下按鈕。圖1描述了傳送門的狀態(tài)變化情況。它是一個UML狀態(tài)機(State Machine),其中click表示按下按鈕的動作。顯然,與純文字描述相比UML狀態(tài)機圖示更加直觀易懂。
按照常規(guī)的設(shè)計思路(不使用State設(shè)計模式),在模擬傳送帶工作過程的軟件中,可以使用一個Door1對象代表傳送門(如圖2所示),狀態(tài)改變事件由傳送帶軟件發(fā)送給Door1對象。
圖1 UML狀態(tài)機

圖2 狀態(tài)改變事件發(fā)送給Door1對象

Door1類從Observable派生,這樣客戶程序(例如一個GUI程序)就能夠方便地了解傳送門狀態(tài)。Door1類首先定義傳送門可能處于的狀態(tài),代碼如下:
public class Door1 extends Observable {
public static final int CLOSED = 1;
public static final int OPENING = 2;
public static final int OPEN = 3;
public static final int CLOSING = 4;
public static final int STAYOPEN = 5;
private int state = CLOSED;
//...
}
status()方法返回傳送門狀態(tài)的文字描述,如下所示:
public String status() {
switch (state) {
case OPENING :
return "正在打開";
case OPEN :
//...
default :
return "已關(guān)閉";
}
}
當(dāng)用戶點擊傳送帶的按鈕時,傳送帶程序調(diào)用Door1對象的click()方法。click()方法模擬圖1所示的狀態(tài)裝換過程:
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);
}
}
Door1類的setState()方法向觀察者通知傳送門狀態(tài)改變事件,代碼如下:
private void setState(int state) {
this.state = state;
setChanged();
notifyObservers();
}
狀態(tài)模型
如果對象的狀態(tài)信息很關(guān)鍵,對象會擁有一些變量來指示如何根據(jù)狀態(tài)做出相應(yīng)的動作。這些變量大量地散布于復(fù)雜的多層嵌套if語句中,來描述對象如何響應(yīng)可能出現(xiàn)的事件。用這種方式建立對象模型的缺點在于if語句可能變得相當(dāng)復(fù)雜一旦要修改對象的狀態(tài)模型,往往有多個方法的許多if語句需要調(diào)整。
以傳送帶的門為例,考慮其狀態(tài)變化過程為:傳送帶的門由單個按鈕控制,并且假設(shè)初始時處于關(guān)閉狀態(tài)。按一下按鈕門開始打開,如果在門完全打開之前再次按下按鈕,門開始關(guān)閉。一旦門完全打開,它將在2秒延時之后自動開始關(guān)閉過程。要禁止門自動關(guān)閉,可以在門打開之后按一下按鈕。圖1描述了傳送門的狀態(tài)變化情況。它是一個UML狀態(tài)機(State Machine),其中click表示按下按鈕的動作。顯然,與純文字描述相比UML狀態(tài)機圖示更加直觀易懂。
按照常規(guī)的設(shè)計思路(不使用State設(shè)計模式),在模擬傳送帶工作過程的軟件中,可以使用一個Door1對象代表傳送門(如圖2所示),狀態(tài)改變事件由傳送帶軟件發(fā)送給Door1對象。
圖1 UML狀態(tài)機

圖2 狀態(tài)改變事件發(fā)送給Door1對象

Door1類從Observable派生,這樣客戶程序(例如一個GUI程序)就能夠方便地了解傳送門狀態(tài)。Door1類首先定義傳送門可能處于的狀態(tài),代碼如下:
public class Door1 extends Observable {
public static final int CLOSED = 1;
public static final int OPENING = 2;
public static final int OPEN = 3;
public static final int CLOSING = 4;
public static final int STAYOPEN = 5;
private int state = CLOSED;
//...
}
status()方法返回傳送門狀態(tài)的文字描述,如下所示:
public String status() {
switch (state) {
case OPENING :
return "正在打開";
case OPEN :
//...
default :
return "已關(guān)閉";
}
}
當(dāng)用戶點擊傳送帶的按鈕時,傳送帶程序調(diào)用Door1對象的click()方法。click()方法模擬圖1所示的狀態(tài)裝換過程:
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);
}
}
Door1類的setState()方法向觀察者通知傳送門狀態(tài)改變事件,代碼如下:
private void setState(int state) {
this.state = state;
setChanged();
notifyObservers();
}