状态设计模式(State Pattern)

描述: 在状态设计模式中类的行为是基于状态改变的,这种类型的模式属于行为设计模式。在状态上设计模式中我们创建表示各种状态和一个行为随这状态对象改变而改变

核心思想: 允许对象在内部状态发生改变时改变它的行为,对象看起来好像改变了它的类

使用场景:

  • 行为随状态的改变而改变的场景
  • 条件,分支语句的代替者

状态设计模式的优点:

  • 封装了转换规则。
  • 枚举可能的状态,在枚举状态之前需要确定状态种类。
  • 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
  • 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
  • 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。

状态设计模式的缺点:

  • 状态模式的使用必然会增加系统类和对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
  • 状态模式对”开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码。

示例:

需求:

我们考虑设计一个金库警报系统,这个系统会根据白天晚上做出不同的响应。

有一个金库
金库与警报中心相连
金库里有警铃和电话
金库里有时钟

金库只能在白天使用
白天使用金库,会在警报中心留下记录
晚上使用金库,会向警报中心发送紧急事态通知

警铃白天晚上都能用
使用警铃,会向警报中心发送紧急事态通知

电话都可以使用
白天使用电话,会呼叫警报中心
晚上使用电话,会呼叫警报中心的留言电话

基本就是以上的需求逻辑。

分析:

状态模式会发现,这些不同的行为,主要依赖于两个状态,就是白天和晚上。所以状态模式会抽象出这两种状态,每个状态就会有自己的行为实现,比如白天这个状态会实现自己的使用金库的方法,通话的方法,晚上的类也会实现自己的行为逻辑,最后我们只要取得状态对象的委托调用他们的方法就行了,不管他们具体是怎么实现的。

  • 抽象状态接口
1
2
3
4
5
6
7
8
9
10
interface State {
void doClock(Context context, int hour); // 设置时间

void doUse(Context context); // 使用金库

void doAlarm(Context context); // 按下警铃

void doPhone(Context context); // 正常通话
}

  • 实现状态接口
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
class NightState implements State {
private static NightState singleton = new NightState();

private NightState() { // 构造函数的可见性是private
}

public static State getInstance() { // 获取唯一实例
return singleton;
}

@Override
public void doClock(Context context, int hour) { // 设置时间
if (9 <= hour && hour < 17) {
context.changeState(DayState.getInstance());
}
}

@Override
public void doUse(Context context) { // 使用金库
context.callSecurityCenter("紧急:晚上使用金库!");
}

@Override
public void doAlarm(Context context) { // 按下警铃
context.callSecurityCenter("按下警铃(晚上)");
}

@Override
public void doPhone(Context context) { // 正常通话
context.recordLog("晚上的通话录音");
}

@Override
public String toString() { // 显示表示类的文字
return "[晚上]";
}
}
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
class DayState implements State {
private static DayState singleton = new DayState();

private DayState() { // 构造函数的可见性是private
}

public static State getInstance() { // 获取唯一实例
return singleton;
}

@Override
public void doClock(Context context, int hour) { // 设置时间
if (hour < 9 || 17 <= hour) {
context.changeState(NightState.getInstance());
}
}

@Override
public void doUse(Context context) { // 使用金库
context.recordLog("使用金库(白天)");
}

@Override
public void doAlarm(Context context) { // 按下警铃
context.callSecurityCenter("按下警铃(白天)");
}

@Override
public void doPhone(Context context) { // 正常通话
context.callSecurityCenter("正常通话(白天)");
}

@Override
public String toString() { // 显示表示类的文字
return "[白天]";
}
}

  • 抽象 出上下文 和 白天晚上要干的事情
1
2
3
4
5
6
7
8
9
interface Context {
void setClock(int hour); // 设置时间

void changeState(State state); // 改变状态

void callSecurityCenter(String msg); // 联系警报中心

void recordLog(String msg); // 在警报中心留下记录
}
  • 实现抽象
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
class SafeFrame implements Context {

private State state = DayState.getInstance();

// 设置时间
@Override
public void setClock(int hour) {

state.doClock(this, hour);
}

public void actionPerformed(Integer e) {
if (e == 1) { // 金库使用按钮
state.doUse(this);
} else if (e == 2) { // 按下警铃按钮
state.doAlarm(this);
} else if (e == 3) { // 正常通话按钮
state.doPhone(this);
} else if (e == 4) { // 结束按钮
System.exit(0);
} else {
System.out.println("?");
}
}

// 改变状态
@Override
public void changeState(State state) {
this.state = state;
System.out.println("从" + this.state + "状態变为了" + state + "状态。");

}

// 联系警报中心
@Override
public void callSecurityCenter(String msg) {
System.out.println("call! " + msg + "\n");
}

// 在警报中心留下记录
@Override
public void recordLog(String msg) {
System.out.println("record ... " + msg + "\n");
}
}
  • 客户端调用
1
2
3
4
5
6
7
public static void main(String[] args) {
SafeFrame frame = new SafeFrame();
frame.setClock(18);
frame.actionPerformed(1);
frame.actionPerformed(2);
frame.actionPerformed(3);
}