State Pattern

將狀態封裝成獨立的類別,並把動作 delegate 到目前的狀態物件,讓物件行為隨著內在狀態改變而改變。

使用情境

系統中有些狀態以及操作,而操作會依據狀態不同而不同,通常這時候能畫出 state machine。

依據 state machine,我們可能就會寫出這樣的 code:

1
2
3
4
5
6
7
8
9
10
// pseudo code
function operation() {
if (state == STATE_A) {
// do sth.
}
else if (state == STATE_B) {
// do other thing
}
// ...
}

operation() 通常是 state machine 裡的動作。

不只 operation() 裡有很多 if,還可能有很多像 operation() 的 function。如果其中有 state 需求變動,或者要加入新的 state,改這段 code 頭就痛了……

State Pattern 是將 state 封裝成一個個 class(封裝會變動的部份)。將在不同 state 要做的操作,分別放到每個 state class 中,並透過將操作 delegate 給 state object 做,來消除原本 code 裡一堆的 if。

來點例子

用《深入淺出設計模式》的例子,糖果機與狀態 class 們的 code 擷取如下:

1
2
3
4
5
6
public interface State {
public void insertQuater();
public void ejectQuater();
public void turnCrank();
public void dispense();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class NoQuarterState implements State {
GumballMachine gumballMachine;

public NoQuarterState(GumballMachine machine) {
this.gumballMachine = machine; // 擁有糖果機的參考
}

public void insertQuater() {
// do sth.
// 改變糖果機的 state
gumballMachine.setState(gumballMachine.getHasQuarterState());
}

public void ejectQuater() {
// do sth.
}

public void turnCrank() {
// do sth.
}

public void dispense() {
// do sth.
}
}

其他的 state class 就不一一列舉啦~依此推類。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

// 不需要用表示 state 的 const 來紀錄狀態,直接用 state object 就可以了
State state = soldOutState;
int count = 0;

public GumballMachine(int numberGumballs) {
// 先把所有 state object 生出來
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);

// 初始化 member
this.count = numberGumballs;
if (numberGumballs > 0) {
state = noQuarterState;
}
}

public void insertQuarter() {
// 將操作 delegate 給 state object
state.insertQuarter();
}

public void ejectQuarter() {
state.ejectQuarter();
}

// other operations

public State getHasQuarterState() {
return this.hasQuarterState;
}

// other getters for State
}

糖果機將操作 delegate 給 state object 做,state object 會做事並且改變糖果機的狀態。

UML

Context::request() call state.operation1(),將事情 delegate 給 state object 做。在糖果機的例子裡,GumballMachine 就是 Context

由誰處理狀態轉換?

狀態轉換可以在 state object 做,也可以在 context 做,糖果機的例子是由 state object 做狀態轉換。

一般原則是狀態轉換是固定的時候,適合在 context 做,而轉換會在 runtime 因為條件不同而有不同時適合在 state object 做。

在 state object 轉換狀態的缺點是會讓 state object 們互相依賴,解決這問題的方式之一是讓 state object 可以透過 context 取得其他 state object,也就是糖果機的 state getters。

與 Strategy Pattern 比較

State pattern 跟 Strategy pattern 的 UML 根本長得一樣。兩個 pattern 的差異在於「意圖」,也可以說是出發點、想達到的目的。

State pattern 將行為封裝在一堆 state object 中,context 隨著狀態不同將動作 delegate 給其中一個 state object。Strategy pattern 則是有很多 algorithm 可以選,通常由使用 algorithm 的使用者(使用 strategy 的 code)決定用哪一個。Strategy 提供了 algotithm 選擇上的彈性但由使用者主導。State 則以狀態為主,狀態改變會導致行為改變。