状态模式

状态模式实际上是为了解决一类状态变化的问题,这类问题不难,但是会很复杂,如果设计不好的化会有一堆的if-else,并且非常难以扩展且及其容易出错。下面我们以一个糖果机的例子来举例说明

糖果机问题

假设我们有一个糖果机器,如下图所示:

StatePattern

最初始的状态是糖果机里面没有硬币,当我们投入硬币后,可以选择退回硬币或者转动曲柄,选择转动曲柄则可以得到一颗糖果,如果没有糖果,则退回硬币,有的话则糖果数量-1,如果售出糖果后,数目为0,则显示糖果售綮。

我们来看这个问题,糖果机一共就只有这4种状态,实际上这就是糖果机在4个状态之间的转换操作。一般我们遇到这种问题直观的来看,可以这样解决:

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
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) {
this.state = NO_QUARTER;
}
}

public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("Already has a quarter, you cannot insert another quarter!");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("Insert a quarter!");
} else if (state == SOLD_OUT) {
System.out.println("The gumball is sold out!");
} else if (state == SOLD) {
System.out.println("Please wait, we 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 had not insert a quarter!");
} else if (state == SOLD_OUT) {
System.out.println("You cannot eject because you have not insert a quarter!");
} else if (state == SOLD) {
System.out.println("Sorry we already given you a gumball, you cannot eject quarter!");
}
}

public void turnCrank() {
if (state == HAS_QUARTER) {
System.out.println("You turning....");
state = SOLD;
dispense();
} else if (state == NO_QUARTER) {
System.out.println("Please insert a quarter!");
} else if (state == SOLD_OUT) {
System.out.println("You cannot turn the crank, the gumball is already sold out");
} else if (state == SOLD) {
System.out.println("Sorry we already given you a gumball, you cannot turn crank again!");
}
}

public void dispense() {
if (state == HAS_QUARTER) {
System.out.println("No gumball dispensed");
} 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 == SOLD) {
System.out.println("A gumball comes rolling out the slot");
count--;
if (count == 0) {
System.out.println("Oops, out of gumballs!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
}
}
}

代码归档如下:
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/statePattern/ifElseDemo

说实话,上面的代码过于复杂了。先不说它的圈复杂度问题,就这个一堆的if-else之间的状态转换就会很让人发麻,一不小心就会搞错了。这在大型软件项目上就等着陷入沼泽吧。即使我们头脑清醒的按照上面的方案实现了这个业务,同时也不在意圈复杂度问题(有钱任性)。那么如果我们增加一点需求,假设在旋转按钮的时候,有一定的概率出两颗糖,那么我们该怎么办呢?(这个问题其实也可以很简单的扩展,我们只要在dispense那里概率性的吐两颗糖就行,但是我们不想这样做,因为这样实际上违反了我们的一个类或者方法,一个责任的原则。同时如果后续要求吐4颗糖或者只吐一颗呢,又要去动手改变代码。)因此我们需要新加一个状态,同时要更改上面的所有函数,状态转换变得更加复杂。。。。。

那么我们有什么方法能处理掉这一堆的if-else呢?状态模式就是解药。

状态模式要求每个状态的行为放到各自的类中,每个状态只需要实现它自己的动作即可。糖果机只需要委托给代表当前状态的状态对象即可。

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

StatePattern1

  • 首先定义state接口
1
2
3
4
5
6
public interface State {
void insertQuarter();
void ejectQuarter();
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
public class HasQuarterState implements State {
GumballMachine gumballMachine;

public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("You cannot insert another quarter!");
}

public void ejectQuarter() {
System.out.println("Quarter return!");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}

public void turnCrank() {
System.out.println("You turned crank......");
gumballMachine.setState(gumballMachine.getSoldState());
}

public void dispense() {
System.out.println("No gumball dispensed!");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 crank, but there is no quarter!");
}

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
public class SoldOutState implements State {
GumballMachine gumballMachine;

public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}

public void insertQuarter() {
System.out.println("Sorry, the gumball is sold out!");
}

public void ejectQuarter() {
System.out.println("You cannont eject eht quarter because you have not insert a quarter!");
}

public void turnCrank() {
System.out.println("Sorry, the gumball is sold out, you cannot turn crank!");
}

public void dispense() {
System.out.println("No gumball dispensed!");
}
}
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 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("Sorry, you already turned the crank twice!");
}

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());
}
}
}
  • 最后我们修改GumballMachine

我们用State类来替换掉之前代码中的int状态码。之前为什么会有那么多的if-else?那是因为每个操作都有4种状态需要考虑。而这里我们将操作放在状态中,针对每一种状态调用相应的操作,利用接口的特性,可以很容易的实现代码的简化。

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
public class GumballMachine {
State soldOutState;
State hasQuarterState;
State noQuarterState;
State soldState;

State state = soldOutState;
int count = 0;

public GumballMachine(int count) {
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);

this.count = count;
if (count > 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--;
}
}

int getCount() {
return count;
}

public State getSoldOutState() {
return soldOutState;
}

public State getNoQuarterState() {
return noQuarterState;
}

public State getSoldState() {
return soldState;
}

public State getHasQuarterState() {
return hasQuarterState;
}
}

我们的测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class GumballMachineTestDemo1 {
public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(5);

System.out.println("--------------------");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println("--------------------");
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();
}
}

现在我们增加一个扩展,有十分之一的概率可以获得两块糖,这样我们只需要新增加一个状态

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 WinnerState implements State {
GumballMachine gumballMachine;

public WinnerState(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("Sorry, you already turned the crank twice!");
}

public void dispense() {
System.out.println("You are lucky ot get two gumballs!");
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("Oops, out of gumballs!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
}

然后,我们需要在HasQuarterState里面适配修改状态,这里面主要修改的是turnCrank函数

1
2
3
4
5
6
7
8
9
public void turnCrank() {
System.out.println("You turned crank......");
int winner = randomWinner.nextInt(10);
if ((winner == 0) && (gumballMachine.getCount() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}

然后在gumballMachine类中添加WinnerState类的初始化

https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/statePattern/GumballDemo

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

显示 Gitment 评论