STATE(狀態) ———— 對象行為型模式
意圖
允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它的類。
狀態和行為
所謂對象的狀態,通常指的就是對象實例的屬性的值;而行為指的就是對象的功能,再具體點說,行為多半可以對應到方法上。
狀態模式的功能就是分離狀態的行為,通過維護狀態的變化,來調用不同的狀態對應的不同的功能。
也就是說,狀態和行為是相關聯的,它們的關系可以描述為:狀態決定行為。
由于狀態是在運行期被改變的,因此行為也會在運行期,根據狀態的改變而改變,看起來,同一個對象,在不同的運行時刻,行為是不一樣的,就像是類被修改了一樣。
行為的平行性
注意是平行性而不是平等性。所謂平行性指的是各個狀態的行為所處的層次是一樣的,是根據不同的狀態來決定到底走平行線的哪一條,行為是不同的,當然對應的實現也是不同的,相互之間是不可替換的。如圖所示:
而平等性強調的是可替換性,大家是同一行為的不同描述或實現,因此在同一個行為發生的時候,可以根據條件來挑選任意一個實現來進行相應的處理。如圖所示:
大家可能會發現狀態模式的結構和策略模式的結構完全一樣,但是,它們的目的、實現、本質都是完全不一樣的。這個行為之間的特性也是狀態模式和策略模式一個很重要的區別,狀態模式的行為是平行性的,不可相互替換的;而策略模式的行為是平等性的,是可以相互替換的。
適用性
在下面的兩種情況下均可使用State模式:
① 一個對象的行為取決于它的狀態,并且它必須在運行時刻根據狀態改變它的行為。
② 一個操作中含有龐大的多分支的條件語句,且這些分支依賴于該對象的狀態。這個狀態通常用一個或多個枚舉常量表示。通常,有多個操作包含這一相同的條件結構。State模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態作為一個對象,這一對象可以不依賴于其他對象而獨立變化。
結構
-
Context (上下文環境)
定義客戶感興趣的接口
維護一個ConcreteState子類的實例,這個實例定義當前狀態。 -
State (狀態)
定義一個接口以封裝與Context的一個特定狀態相關的行為。 -
ConcreteState subclasses (具體狀態子類)
每一子類實現一個與Context的一個狀態相關的行為。
協作
- Context將與狀態相關的請求委托給當前的ConcreteState對象處理。
- Context可將自身作為一個參數傳遞給處理該請求的狀態對象。這使得狀態對象在必要時可訪問Context。
- Context是客戶使用的主要接口。客戶可用狀態對象來配置一個Context,一旦一個Context配置完畢,它的客戶不再需要直接與狀態對象打交道。
- Context或ConcreteState子類都可以決定哪個狀態是另外哪一個的后續者,以及是在何種條件下進行狀態轉換。
狀態維護和轉換控制
所謂狀態的維護,指的就是維護狀態的數據,就是給狀態設置不同的狀態值;而狀態的轉換,指的就是根據狀態的變化來選擇不同的狀態處理對象。在狀態模式中,通常有兩個地方可以進行狀態的維護和轉換控制。
一個就是在上下文(Context)當中,因為狀態本身通常被實現為上下文對象的狀態,因此可以在上下文里面進行狀態維護,當然也就可以控制狀態的轉換了。
另外一個地方就是在狀態的處理類(ConcreteState)里面,當每個狀態處理對象處理完自身狀態所對應的功能后,可以根據需要指定后繼的狀態,以便讓應用能正確處理后續的請求。
狀態模式的優缺點
優點
① 簡化應用邏輯控制
狀態模式使用單獨的類來封裝一個狀態的處理。如果把一個大的程序控制分成很多小塊,每塊定義一個狀態來代表,那么就可以把這些邏輯控制的代碼分散到很多單獨的狀態類當中去,這樣就把著眼點從執行狀態提高到整個對象的狀態,使得代碼結構化和意圖更清晰,從而簡化應用的邏輯控制。
對于依賴于狀態的if-else,理論上來講,也可以改變成應用狀態模式來實現,把每個if或else塊定義一個狀態來代表,那么就可以把塊內的功能代碼移動到狀態處理類去了,從而減少if-else,避免出現巨大的條件語句。
② 更好的分離狀態和行為
狀態模式通過設置所有狀態類的公共接口,把狀態和狀態對應的行為分離開來,把所有與一個特定的狀態相關的行為都放入一個對象中,使得應用程序在控制的時候,只需要關心狀態的切換,而不用關心這個狀態對應的真正處理。
③ 更好的擴展性
引入了狀態處理的公共接口后,使得擴展新的狀態變得非常容易,只需要新增加一個實現狀態處理的公共接口的實現類,然后在進行狀態維護的地方,設置狀態變化到這個新的狀態即可。
④ 顯式化進行狀態轉換
狀態模式為不同的狀態引入獨立的對象,使得狀態的轉換變得更加明確。而且狀態對象可以保證上下文不會發生內部狀態不一致的情況,因為上下文中只有一個變量來記錄狀態對象,只要為這一個變量賦值就可以了。
缺點
① 引入太多的狀態類
狀態模式也有一個很明顯的缺點,一個狀態對應一個狀態處理類,會使得程序引入太多的狀態類,使程序變得雜亂。
示例
Context類:
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;
public GumballMachine(int numberGumballs) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
int getCount() {
return count;
}
void refill(int count) {
this.count = count;
state = noQuarterState;
}
public State getState() {
return state;
}
public State getSoldOutState() {
return soldOutState;
}
public State getNoQuarterState() {
return noQuarterState;
}
public State getHasQuarterState() {
return hasQuarterState;
}
public State getSoldState() {
return soldState;
}
public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}
State接口:
public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();
}
ConcreteState 子類:
public class HasQuarterState implements State {
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert another quarter");
}
public void ejectQuarter() {
System.out.println("Quarter returned");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
public void turnCrank() {
System.out.println("You turned...");
gumballMachine.setState(gumballMachine.getSoldState());
}
public void dispense() {
System.out.println("No gumball dispensed");
}
public String toString() {
return "waiting for turn of crank";
}
}
public class NoQuarterState implements State {
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You inserted a quarter");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
public void ejectQuarter() {
System.out.println("You haven't inserted a quarter");
}
public void turnCrank() {
System.out.println("You turned, but there's no quarter");
}
public void dispense() {
System.out.println("You need to pay first");
}
public String toString() {
return "waiting for quarter";
}
}
public class SoldOutState implements State {
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}
public void ejectQuarter() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
public void turnCrank() {
System.out.println("You turned, but there are no gumballs");
}
public void dispense() {
System.out.println("No gumball dispensed");
}
public String toString() {
return "sold out";
}
}
public class SoldState implements State {
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
public void insertQuarter() {
System.out.println("Please wait, we're already giving you a gumball");
}
public void ejectQuarter() {
System.out.println("Sorry, you already turned the crank");
}
public void turnCrank() {
System.out.println("Turning twice doesn't get you another gumball!");
}
public void dispense() {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
public String toString() {
return "dispensing a gumball";
}
}
客戶測試:
public class GumballMachineTestDrive {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
System.out.println(gumballMachine);
}
}
參考
《Head First 設計模式》
《設計模式:可復用面向對象軟件的基礎》
《研磨設計模式》