状态模式

简介

状态模式 有点类似策略模式,同样是变化,只是应对变化的方式不一样而已。策略模式面对变化,是采取将变化的部分与不变的部分分离开来,采取封装算法族的方式进行抽象,具体实现只需对不同接口进行组合。而状态模式,变化的部分取决于对象的内部状态,通过当前的状态来决定每个方法的具体实现。

思路

《Head First设计模式》给了一个糖果机的案例,它的运行机制是这样的:首先糖果机有四种状态,分别是没有25分钱有25分钱售出糖果糖果售罄。同时,导致糖果机状态变化也对应着四个动作,分别是:投入25分钱退回25分钱转动曲柄发放糖果。糖果机的状态转化关系如下:

如图,糖果机的实现应该包含四个方法,而且每个方法的具体行为跟糖果机当前的状态有关系。比如说,当没有25分钱时是不能退回25分钱和售出糖果的。当转动曲柄的时候,如果没有糖果应该提醒顾客已售空。

实现

在使用状态模式之前,我们的 代码实现 可能是这样的

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
42
public class GumballMachine {

final static int SOLD_OUT = 0; // 售罄状态
final static int NO_QUARTE = 1; // 没有25分钱
final static int HAS_QUARTER = 2; // 有25分钱
final static int SOLD = 3; // 售出糖果

int state = SOLD_OUT; // 默认为售罄状态,因为没有糖果
int count = 0;

public GumballMachine(int count) {
this.count = count;
if (count > 0) { // 初始化时如果大于0,为没有25分钱
state = NO_QUARTE;
}
}

// 投入硬币
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You cna't insert another quarter");
} else if (state == NO_QUARTE) {
state = HAS_QUARTER;
System.out.println("You inserted a quarter");
} else if (state == SOLD_OUT) {
System.out.println("You can't insert a quarter,the machine is sold out");
} else if (state == SOLD) {
System.out.println("please wait,we're already giving you a guaball");
}
}
// 退回硬币
public void ejectQuater() {}

// 转动曲柄
public void turnCrank() {}

// 发放糖果
public void dispense() {}

// 其他方法

}

这样子实现也没什么问题,但是随着业务的变更,比如需要有10%的可能会掉下两个糖果,发现上面的代码并没有遵循开闭原则、状态转化埋藏在条件语句中,并不好理解。更重要的是没有将会变化的部分封装,牵一发而动全身,未来很有可能导致隐藏的bug。

重构

在重构之前,我们先梳理一下逻辑:

  1. 首先,我们定义一个State接口,在这个接口内,糖果机的每个动作都有一个实现的方法。
  2. 然后,为机器的每个状态实现状态类,这些状态类将负责在对应的状态下进行的行为。
  3. 最后,我们要摆脱旧的条件代码,取而代之的是,将动作委托到状态类。

类图设计如下:

状态接口定义为

1
2
3
4
5
6
7
8
9
10
public interface State {

void insertQuater(); // 投入硬币

void ejectQuater(); // 退回硬币

void turnCrank(); // 转动曲柄

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class StateGumballMachine {

State noQuaterState;
State hasQuaterState;
State soldState;
State soldOutState;


State state = soldState;
int count = 0;

public StateGumballMachine(int count) {
noQuaterState = new NoQuaterState(this);
hasQuaterState = new HasQuaterState(this);
soldState = new SoldState(this);
soldOutState = new SoldOutState(this);
this.count = count;
if (count > 0) {
state = noQuaterState;
}
}

// 投入硬币
public void insertQuarter() {
state.insertQuater();
}

// 退回硬币
public void ejectQuater() {
state.ejectQuater();
}

// 转动曲柄
public void turnCrank() {
state.turnCrank();
state.dispense();
}

// 发放糖果
public void releaseBall() {
System.out.println("A gumball comes rolling out the slot...");
if (count != 0) {
count = count - 1;
}
}
}

对应没有25分钱的状态类实现为:

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
public class NoQuaterState implements State {

StateGumballMachine stateGumballMachine;

public NoQuaterState(StateGumballMachine stateGumballMachine) {
this.stateGumballMachine = stateGumballMachine;
}

@Override
public void insertQuater() {
System.out.println("You inserted a quarter");
stateGumballMachine.setState(stateGumballMachine.getHasQuaterState());
}

@Override
public void ejectQuater() {
System.out.println("You haven't inserted a quater");
}

@Override
public void turnCrank() {
System.out.println("You turned,but there are no gumballs");
}

@Override
public void dispense() {
System.out.println("You need to pay first");
}
}

售出糖果的状态实现

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
public class SoldState implements State {

StateGumballMachine stateGumballMachine;

public SoldState(StateGumballMachine stateGumballMachine) {
this.stateGumballMachine = stateGumballMachine;
}

@Override
public void insertQuater() {
System.out.println("please wait,we're already giving you a guaball");
}

@Override
public void ejectQuater() {
System.out.println("Sorry,you already turned the crank");
}

@Override
public void turnCrank() {
System.out.println("Turing twice doesn't get you another gumball!");

}

@Override
public void dispense() {
stateGumballMachine.releaseBall();
if (stateGumballMachine.getCount() > 0) {
stateGumballMachine.setState(stateGumballMachine.getNoQuaterState());
} else {
stateGumballMachine.setState(stateGumballMachine.getSoldOutState());
}
}
}

从上面 代码实现 很容易看出,每个状态是如何进行转化的,而且如果需要再添加一种状态的话,也是很容易进行扩展的。总而言之言之,状态模式提升了代码的可读性可扩展性

那么,现在回到刚才那个问题:

需要添加可能会一下出两颗糖果的可能,代码应该怎么实现呢?

定义

书上定义为:

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。

类关系图为: