状态模式

状态模式

一、什么是状态模式?

状态模式(State Design Pattern):对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

通常情况下,状态只作为数据,而不是对象,它只在对象之间进行流转,是对象之间数据交流的一种方式。

状态模式,则是将状态也当成是一个对象,赋予它数据和行为动作,它的数据就是状态,行为动作就是状态转换。

二、为什么要用状态模式?

  • 对象存在多种状态,在不同状态下,对象会做出不同的行为
  • 对象的状态,会被外部的事件影响,从而导致对象的状态发生变化

应对这种情况,一般有几种方法解决。

2.1 分支逻辑法

  • 传统的解决方案是:对不同的状态、事件、动作都考虑到,使用 if-else 加以判断,处理不同的情况
  • if-else 这种方式,代码逻辑复杂,判断条件臃肿,可读性差,可扩展性差,容易遗漏情况

2.2 查表法

  • 对分支逻辑法的一种优化是,把 if-else 的判断逻辑迁移到一张表中,比如二维表
  • 通过 key-value 的形式,将状态转换和状态动作都映射成一张二维表,根据状态和事件能直接查到下一步的转换和动作
  • 这种方式,比起 if-else 要优雅一些,代码结构清晰,可读性和可扩展性较好

2.3 状态模式

  • 分支逻辑和查表,都是一种正向的逻辑判断,即所有的状态转换和动作执行,都是由外部事件触发和外部处理的
  • 状态模式,则是一种反向思维,事件触发依旧是外部的,但是改成了由状态自己本身进行状态转换和动作执行
  • 状态模式,就是把之前由上下文处理的状态转换和动作,都迁移到了状态对象里面,隔离了不同状态的代码逻辑
  • 优点是,符合“单一职责”,代码可读性好,结构清晰,可读性和可维护性好
  • 缺点是,会引入较多的状态类,代码可维护性变差了一些

三、怎么实现状态模式?

状态模式包括以下几个角色:

  • 上下文环境(Context):用于内部维护一个当前状态,并负责具体状态的切换
  • 抽象状态(State):定义的状态接口,用以抽象对象的状态行为
  • 具体状态(ConcreteState):抽象状态的实现类,主要实现是状态的行为以及转换

状态模式结构:

状态模式结构

示例代码结构:

状态模式示例结构

状态和上下文接口:

1
2
3
4
5
6
7
8
public 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
public 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
public class DayState implements State {

private static DayState singleton = new DayState();
private DayState() {}

public static DayState 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
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
public class NightState implements State {

private static NightState singleton = new NightState();
private NightState() {}

public static NightState 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.callSecurityCenter("晚上的通话记录");
}

@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
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
public class SafeFrame extends Frame implements ActionListener, Context {

private TextField textClock = new TextField(60);
private TextArea textScreen = new TextArea(10, 60);
private Button buttonUse = new Button("使用金库");
private Button buttonAlarm = new Button("按下警铃");
private Button buttonPhone = new Button("正常通话");
private Button buttonExit = new Button("结束");

private State state = DayState.getInstance();
private ActionEvent e;

public SafeFrame(String title) {
super(title);
setBackground(Color.lightGray);
setLayout(new BorderLayout());

add(textClock, BorderLayout.NORTH);
textClock.setEditable(false);

add(textScreen, BorderLayout.CENTER);
textScreen.setEditable(false);

Panel panel = new Panel();
panel.add(buttonUse);
panel.add(buttonAlarm);
panel.add(buttonPhone);
panel.add(buttonExit);

add(panel, BorderLayout.SOUTH);

pack();
show();

buttonUse.addActionListener(this);
buttonAlarm.addActionListener(this);
buttonPhone.addActionListener(this);
buttonExit.addActionListener(this);
}

public void actionPerformed(ActionEvent e) {
this.e = e;
System.out.println(e.toString());
if (e.getSource() == buttonUse) {
state.doUse(this);
} else if(e.getSource() == buttonAlarm) {
state.doAlarm(this);
} else if (e.getSource() == buttonPhone) {
state.doPhone(this);
} else if (e.getSource() == buttonExit) {
System.exit(0);
} else {
System.out.println("?");
}
}

@Override
public void setClock(int hour) {
String clockString = "现在时间是 ";
if (hour < 10) {
clockString += "0" + hour + ":00";
} else {
clockString += hour + ":00";
}
System.out.println(clockString);
textClock.setText(clockString);
state.doClock(this, hour);
}

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

@Override
public void callSecurityCenter(String msg) {
textScreen.append("呼叫: " + msg + "\n");
}

@Override
public void recordLog(String msg) {
textScreen.append("记录 ... " + msg + "\n");
}

}

四、状态模式有什么优缺点?

优点:

  • 结构清晰。将状态相关的转换和动作都局部化分割到状态对象中,符合“单一职责”原则
  • 可扩展性强。很容易就能扩展一种新状态,不过可能要修改其他状态类的转换代码

缺点:

  • 类数据增多。每个状态都是一个类,必然会导致状态类增多
  • 状态转换结构不清晰。状态转换写在每个单独的状态类里面,而没有一个完整的转换图,在代码可读性上不是那么好

五、与其他模式的对比

5.1 职责链模式

  • 职责链模式和状态模式,都能处理过多 if-else 的问题
  • 职责链模式,强调的是对象间状态的转移,不同对象之间并不知道对方的存在,只是由外部按照职责链顺序执行
  • 状态模式,更强调的是对象内状态的转换,对象之间互相知道对方的存在,便于进行状态转换

5.2 策略模式

  • 策略模式,对象之间是以一种可互相替换的方式存在
  • 状态模式,对象是以不同状态的形式存在,是一种互相转换的关系,但不是可替换的
作者

jiaduo

发布于

2022-01-15

更新于

2023-04-03

许可协议