0%

九、状态模式

以糖果机为例

我们需要使用java实现一个糖果机,糖果机状态如下所示:

43259283

状态机101

使用状态机的方式实现,这里使用一个通用技巧:
如何对对象的内的状态建模–通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同状态。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class GumballMachine101 {
/**
* 糖果售罄
*/
final static int SOLD_OUT = 0;
/**
* 没有硬币
*/
final static int NO_QUARTER = 1;
/**
* 有硬币
*/
final static int HAS_QUARTER = 2;
/**
* 售出糖果
*/
final static int SOLD = 3;
/**
* 当前状态
*/
private int state = SOLD_OUT;
/**
* 售出糖果数
*/
private int count = 0;
public GumballMachine101(int count){
this.count = count;
if (count > 0){
state = NO_QUARTER;
}
}
/**
* 投入硬币
*/
public void insertQuarter(){
if (state == HAS_QUARTER){
System.out.println("你不能重复投入硬币");
} else if (state == NO_QUARTER){
state = HAS_QUARTER;
System.out.println("投入硬币");
} else if (state == SOLD_OUT){
System.out.println("无法投入硬币,糖果已售罄");
} else if (state == SOLD){
System.out.println("请等待,正在为您准备糖果");
}
}
/**
* 退回硬币
*/
public void ejectQuarter(){
if (state == HAS_QUARTER){
System.out.println("硬币已退回");
state = NO_QUARTER;
} else if (state == NO_QUARTER){
System.out.println("无法退回,您还未投入硬币");
} else if (state == SOLD){
System.out.println("无法退回,您已经转动了曲柄");
} else if (state == SOLD_OUT){
System.out.println("无法退回,您还未投入硬币");
}
}
/**
* 转动曲柄
*/
public void trunCrank(){
if (state == SOLD) {
System.out.println("不能转动2次曲柄");
} else if (state == NO_QUARTER){
System.out.println("还没投入硬币");
} else if (state == SOLD_OUT) {
System.out.println("糖果已售罄");
} else if (state == HAS_QUARTER) {
System.out.println("转动曲柄。。。");
state = SOLD;
dispense();
}
}
/**
* 发放糖果
*/
private void dispense() {
if (state == SOLD) {
System.out.println("售出糖果");
count--;
if (count == 0){
System.out.println("糖果卖完了");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("需要先投入硬币");
} else if (state == SOLD_OUT) {
System.out.println("没有糖果可发放");
} else if (state == HAS_QUARTER) {
System.out.println("没有糖果可发放");
}
}
}

变更请求

我们需要在糖果机上变点花样,当曲柄转动时,有10%的概率掉下来的是两颗糖果。
使用一种考虑周详的方法写糖果机的代码,并不意味着这份代码容易扩展。首先,你必须加上一个新的状态,“赢家”状态。然后,你必须在每个方法中加入一个新的条件判断来处理“赢家”状态,这可就有忙的了。
trunCrack()尤其会变得一团乱,因为你必须加入代码来检查目前的顾客是否是赢家,然后再决定切换到赢家状态还是售出糖果转态。

新的设计

不要维护现有代码,我们重写它以便于将状态对象封装在各自的类中,然后在动作发生时委托给当前状态。

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

47618146

糖果机:

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
@Data
public class GumballMachine {
/**
* 糖果售罄
*/
private State soldOutState;
/**
* 没有硬币
*/
private State noQuarterState;
/**
* 有硬币
*/
private State hasQuarterState;
/**
* 售出糖果
*/
private State soldState;
/**
* 当前状态
*/
private State state = soldOutState;
/**
* 售出糖果数
*/
private 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();
}
}

状态对象NoQuarterState:

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
public class NoQuarterState implements State {
private GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
/**
* 投入硬币
*/
@Override
public void insertQuarter() {
System.out.println("投入硬币");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
/**
* 退回硬币
*/
@Override
public void ejectQuarter() {
System.out.println("无法退回,您还未投入硬币");
}
/**
* 转动曲柄
*/
@Override
public void turnCrank() {
System.out.println("请先投入硬币");
}
/**
* 发放糖果
*/
@Override
public void dispense() {
System.out.println("请先投入硬币");
}
}

到目前为止,我们做了哪些事情?

  • 将每个状态的行为局部化到它自己的类中。
  • 将容易产生的问题的if语句删除,以方便日后维护。
  • 让每一个状态“对修改关闭”,让糖果机“对扩展开放”。

76555373

定义状态模式

状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
因为这个类将状态封装成独立的类,并将动作委托到代表当前状态的对象,我们知道行为会随着内部状态的改变而改变。
从客户的视角来看,如果说你使用的对象能够完全改变它的行为,那么你会觉得,这个对象实际上是从别的类实例化而来的。
77253919

从类图上看,策略模式和状态模式的类图是一样的,但是这两个模式的差别在于它们的“意图”。
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个。当前状态在状态对象集合中游走改变,以反映出context内部状态,因此context的行为也会跟着改变。
以策略模式而言,客户通常主动指定context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最合适的策略对象。
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代。
把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context中简单的改变状态对象来改变context的行为。

源码:https://github.com/chentianming11/design-pattern
state包!