《Head First Design Patterns》:第10章,状态(State)模式

状态模式的定义

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

上述定义中前半句话的含义:该模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态而改变。下面糖果机的例子中,当糖果机在NoQuarterStateHasQuarterState两种不同的状态时,你投入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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public class GumballMachine {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;

int state = SOLD_OUT;
int count = 0;

public GumballMachine(int count) {
this.count = count;
if (count > 0) {
state = NO_QUARTER;
}
}

public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("You can't insert another quarter");
} else if (state == NO_QUARTER) {
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 gumball");
}
}

public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Quarter returned");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("You haven't inserted a quarter");
} else if (state == SOLD) {
System.out.println("Sorry, you already turned the crank");
} else if (state == SOLD_OUT) {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}
}

public void turnCrank() {
if (state == SOLD) {
System.out.println("Turning twice doesn't get you another gumball!");
} else if (state == NO_QUARTER) {
System.out.println("You turned but there's no quarter");
} else if (state == SOLD_OUT) {
System.out.println("You turned, but there are no gumballs");
} else if (state == HAS_QUARTER) {
System.out.println("You turned...");
state = SOLD;
dispense();
}
}

private void dispense() {
if (state == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count = count - 1;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("You need to pay first");
} else if (state == SOLD_OUT) {
System.out.println("No gumball dispensed");
} else if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
}
}

// 其他方法
}

方案

将每个状态的行为局部化到它自己的类中,这样一来,如果我们针对某个状态做了改变,就不会把其他的代码给搞乱了:

  1. 定义状态接口State,所有的状态都需要实现这个接口,接口中定义了糖果机上所有可能发生动作所代表的方法。每个具体的状态类继承自接口State,它们只需要关注各自所关心的方法,例如,NoQuarterState只需要关注“投币”动作,并当“投币”动作发生后,将状态转换到HasQuarterState
  2. 糖果机GumballMachine类在实例化时,初始化全部状态;state属性为糖果机当前的状态,每当糖果机接收到外部动作,它会委托给state指向的状态对象的相应方法,这样就实现了糖果机的状态转换;
1
2
3
4
5
6
7
8
public interface State {
public void insertQuarter();
public void ejectQuarter();
public void turnCrank();
public void dispense();

public void refill();
}
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
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 void refill() { }

public String toString() {
return "waiting for quarter";
}
}

实现更多的状态… …

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class GumballMachine {
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;

State state;
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;
} else {
state = soldOutState;
}
}

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

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

public void turnCrank() {
state.turnCrank();
state.dispense();
}

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;
System.out.println("The gumball machine was just refilled; its new count is: " + this.count);
state.refill();
}

void setState(State state) {
this.state = state;
}
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;
}
}

状态模式VS策略模式

状态模式 策略模式
我们把状态模式想成是不用在对象中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在对象内简单地改变状态对象来改变该对象的行为。 我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。