状态模式
定义
自然界很多事物都有多种状态,而且不同状态下会具有不同的行为,这些状态在特定条件下还会发生相互转换,比如水。

在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同状态下也将具有不同的行为。
状态模式(state pattern)的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
状态模式就是用来解决系统中复杂对象的状态转换以及不同状态下行为的封装问题,状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来 表示状态),使得对象状态可以灵活变化。
模式原理

- State: 抽象状态类
- ConcreteState: 具体状态类,实现抽象状态类所定义的接口
- Context: 上下文类,持有一个抽象状态类的引用
有限状态机
有限状态机,英文翻译是 Finite State Machine ,缩写为 FSM ,简称为状态机。状态机有 3 个组成部分,状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。 事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
以超级马里奥游戏为例
应用实例
模拟交通信号灯的状态转换,交通信号灯一般包括了红黄绿三种颜色状态,不同状态之间的切换逻辑为,红灯只能切换为黄灯,黄灯可以切换为绿等或者红灯、绿灯只能切换为黄灯。

不使用设计模式
public class TrafficLightController {
private String state = "RED"; // RED -> YELLOW -> GREEN -> YELLOW -> RED ...
public void switchToNext() {
if ("RED".equals(state)) {
System.out.println("红灯 -> 黄灯:请准备");
state = "YELLOW_AFTER_RED";
} else if ("YELLOW_AFTER_RED".equals(state)) {
System.out.println("黄灯 -> 绿灯:可以通行");
state = "GREEN";
} else if ("GREEN".equals(state)) {
System.out.println("绿灯 -> 黄灯:请减速");
state = "YELLOW_AFTER_GREEN";
} else if ("YELLOW_AFTER_GREEN".equals(state)) {
System.out.println("黄灯 -> 红灯:禁止通行");
state = "RED";
} else {
throw new IllegalStateException("未知状态: " + state);
}
}
public static void main(String[] args) {
TrafficLightController controller = new TrafficLightController();
for (int i = 0; i < 6; i++) {
controller.switchToNext();
}
}
}
这种 if-else 分支方式在状态较多、行为较复杂时会快速膨胀,难以维护和扩展。
使用状态模式
// 抽象状态
interface LightState {
void switchToNext(TrafficLight context);
String name();
}
// 上下文
class TrafficLight {
private LightState currentState;
public TrafficLight() {
this.currentState = new RedState();
}
public void setState(LightState newState) {
this.currentState = newState;
}
public void switchToNext() {
currentState.switchToNext(this);
}
public String getCurrentStateName() {
return currentState.name();
}
}
// 具体状态:红灯
class RedState implements LightState {
@Override
public void switchToNext(TrafficLight context) {
System.out.println("红灯 -> 黄灯:请准备");
context.setState(new YellowAfterRedState());
}
@Override
public String name() { return "RED"; }
}
// 具体状态:红灯后的黄灯
class YellowAfterRedState implements LightState {
@Override
public void switchToNext(TrafficLight context) {
System.out.println("黄灯 -> 绿灯:可以通行");
context.setState(new GreenState());
}
@Override
public String name() { return "YELLOW_AFTER_RED"; }
}
// 具体状态:绿灯
class GreenState implements LightState {
@Override
public void switchToNext(TrafficLight context) {
System.out.println("绿灯 -> 黄灯:请减速");
context.setState(new YellowAfterGreenState());
}
@Override
public String name() { return "GREEN"; }
}
// 具体状态:绿灯后的黄灯
class YellowAfterGreenState implements LightState {
@Override
public void switchToNext(TrafficLight context) {
System.out.println("黄灯 -> 红灯:禁止通行");
context.setState(new RedState());
}
@Override
public String name() { return "YELLOW_AFTER_GREEN"; }
}
// 演示
public class Main {
public static void main(String[] args) {
TrafficLight light = new TrafficLight();
for (int i = 0; i < 6; i++) {
System.out.println("当前状态: " + light.getCurrentStateName());
light.switchToNext();
}
System.out.println("当前状态: " + light.getCurrentStateName());
}
}
相较于分支法,状态模式将每个状态的转换逻辑与行为内聚在各自的状态类中:
- 易扩展:新增状态只需新增类并在相关转换处接入。
- 高内聚低耦合:消除了臃肿的条件分支。
- 可复用:相同状态可在多个上下文中复用。
总结
状态模式是状态机的一种实现方式。状态机又叫做有限状态机,它由 3 个部分组成:状态、事件、动作。其中,事件也称为转移条件。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何操作。
针对状态机,有 3 种实现方式。
- 分支逻辑法。利用 if-else 或者 switch-case 分支逻辑,参照状态转移图,将每一个 状态转移原模原样地直译成代码。对于简单的状态机来说,这种实现方式最简单、最直接、是首选。
- 查表法。对于状态很多、状态转移比较复杂的状态机来说,查表法比较合适。通过二维数组来表示状态转移图,能极大地提高代码的可读性和可维护性。
- 状态模式。对于状态并不多、状态转移也比较简单,但事件触发执行的动作包含的业务逻辑可能比较复杂的状态机来说,首选这种实现方式。
优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象的状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合为一体,而不是一个巨大的语句块。
缺点
- 状态模式的使用必然会增加系统类和对象的个数。
- 状态模式的结构与实现都较为复杂,如果使用不当则导致程序结构和代码的混乱。
- 状态模式对 "开闭原则"的支持并不太好(添加新的状态类需要修改那些负责状态转换的源代码)。