状态模式
一、什么是状态模式?
状态模式(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 策略模式
- 策略模式,对象之间是以一种可互相替换的方式存在
- 状态模式,对象是以不同状态的形式存在,是一种互相转换的关系,但不是可替换的