状态模式

状态模式

有时一个对象的行为取决于一或多个动态变化的属性(状态),这样的对象称为有状态的(stateful)对象,其对象状态是从事先定义好的一系列值中取出。当这样的对象与外部事件产生互动时,内部状态就会改变,对象行为也随之变化。

在UML中可以使用状态图来描述对象状态的变化。在状态模式中,创建表示各种状态的对象和一个行为随着状态对象改变而改变的 context 对象。

架构

  • Context:环境类
  • State:抽象状态类
  • ConcreteState:具体状态类

意义

解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。 状态模式的关键是引入了一个抽象类来专门表示对象的状态 - 抽象状态类。而对象的每种具体状态类都继承该类,并在不同具体状态类中实现不同状态的行为,包括各种状态之间的转换。

使用状态模式定义糖果机

在现实世界中,有一台糖果机,可以糖果机上执行如下动作:

  • 投入一枚25分钱的硬币(机器上有按钮)
  • 退出一枚25分钱的硬币(机器上有按钮)
  • 转动曲柄以获取糖果(机器上有曲柄)
  • 糖果机释放糖果(糖果机在转动曲柄后,自动释放)
  • 重新为糖果机装填糖果

糖果机的状态如下

image-20231228232823387

定义糖果机的状态和接口

image-20231228232735116

实现糖果机 GumballMachine 的代码:

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
package headfirst.designpatterns.state.gumballstate;

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;
}

public String toString() {
StringBuffer result = new StringBuffer();
result.append("\nMighty Gumball, Inc.");
result.append("\nJava-enabled Standing Gumball Model #2004");
result.append("\nInventory: " + count + " gumball");
if (count != 1) {
result.append("s");
}
result.append("\n");
result.append("Machine is " + state + "\n");
return result.toString();
}
}

定义 State 接口:

1
2
3
4
5
6
7
8
9
10
11
package headfirst.designpatterns.state.gumballstate;

public interface State {

void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
void refill();
}

定义**”没有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
package headfirst.designpatterns.state.gumballstate;

public class NoQuarterState implements State {
// 在当前状态执行相应的操作后,涉及对糖果机状态的改变,因此,依赖GumballMachine。
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";
}
}

定义**”有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
package headfirst.designpatterns.state.gumballstate;

public class HasQuarterState implements State {
GumballMachine gumballMachine;

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


public void insertQuarter() {
System.out.println("You can't insert another quarter");
}

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

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

public void dispense() {
System.out.println("No gumball dispensed");
}

public void refill() { }

public String toString() {
return "waiting for turn of crank";
}
}

定义**”售出糖果”**的状态:

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
package headfirst.designpatterns.state.gumballstate;

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("Turning twice doesn't get you another gumball!");
}

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());
}
}

public void refill() { }

public String toString() {
return "dispensing a gumball";
}
}

定义**”售罄糖果”**的状态:

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
package headfirst.designpatterns.state.gumballstate;

public class SoldOutState implements State {
GumballMachine gumballMachine;

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

public void insertQuarter() {
System.out.println("You can't insert a quarter, the machine is sold out");
}

public void ejectQuarter() {
System.out.println("You can't eject, you haven't inserted a quarter yet");
}

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

public void dispense() {
System.out.println("No gumball dispensed");
}

public void refill() {
gumballMachine.setState(gumballMachine.getNoQuarterState());
}

public String toString() {
return "sold out";
}
}

测试:

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
package headfirst.designpatterns.state.gumballstate;

public class GumballMachineTestDrive {

public static void main(String[] args) {
GumballMachine gumballMachine = new GumballMachine(2);

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);

gumballMachine.insertQuarter();
gumballMachine.turnCrank();
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

gumballMachine.refill(5);
gumballMachine.insertQuarter();
gumballMachine.turnCrank();

System.out.println(gumballMachine);
}
}

测试结果:

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
Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 2 gumballs
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 1 gumball
Machine is waiting for quarter

You inserted a quarter
You turned...
A gumball comes rolling out the slot...
Oops, out of gumballs!
You can't insert a quarter, the machine is sold out
You turned, but there are no gumballs
No gumball dispensed
The gumball machine was just refilled; its new count is: 5
You inserted a quarter
You turned...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is waiting for quarter

优点

封装了转换规则

枚举可能的状态,在枚举状态之前需要确定状态种类

将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。

允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。

可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

缺点

必然增加系统类和对象的个数

结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱

对”开闭原则”支持不太好,对可切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码

为所有状态创建单独的类会使系统变得复杂。对于实体状态类来说,有些行为来自于自身,有些行为继承自抽象基类,这一方面在子类和父类之间形成紧耦合,另一方面使代码的可读性变差。但是,使用枚举则非常简单,与通过状态模式来创建标准类型相比,枚举可能是更好的方法。

这里我们同时得到了两种方法的好处: 获得了一个非常简单的标准类型,又能有效地表示当前的状态

策略模式与状态模式

策略模式是定义一个算法家族,把他们封装起来,使得他们之间可以相互替换,

状态模式是将一个个状态封装成一个个类,当内部状态发生改变时,改变他们的行为。

策略模式和状态模式的类图几乎一样,策略模式中,客户端知道具体的策略有哪些,客户端能够通过setStrategy方法来动态的设置具体使用哪个策略,状态模式中,客户端不知道内部状态是怎么变化的,状态模式通过状态转移来组合State对象,最后把行为呈现出来

适用场景

代码中包含大量与对象状态有关的条件语句:

对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为

代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态


状态模式
http://example.com/2023/10/15/计算机基础/设计模式/20-状态模式/
作者
PALE13
发布于
2023年10月15日
许可协议