状态模式:状态转换、有限状态机
状态模式,说白了就是让对象的行为随着内部状态的改变而改变。你想想看,一个对象在不同状态下,对同一个请求的反应可能完全不同。比如一个订单,在“待支付”状态下,你调用“取消”方法,它会取消订单;但在“已发货”状态下,同样的“取消”方法,可能就变成申请退货了。
我刚开始接触设计模式时,觉得状态模式跟策略模式长得挺像。后来在项目中踩过坑才明白,这俩本质上是两码事。策略模式是让你在多个算法之间自由切换,而状态模式是让对象的状态驱动行为变化,状态之间是有流转关系的。
什么是状态模式
状态模式的核心思想其实很简单:把每个状态封装成一个独立的类,让每个状态类负责处理自己状态下的行为。这样当对象的状态发生变化时,只需要切换当前的状态对象就行了。
我记得有一次重构一个工作流引擎,里面有个审批节点,状态有“待审批”、“审批中”、“已通过”、“已驳回”四种。一开始代码里全是 if-else 判断,看得我头皮发麻。后来用状态模式一重构,整个世界都清净了。
状态模式的定义:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
状态模式的结构
状态模式通常包含三个角色:
- Context(上下文):持有当前状态对象的引用,负责状态的切换
- State(抽象状态):定义状态相关的行为接口
- ConcreteState(具体状态):实现具体状态下的行为逻辑
来看一个我实际项目中简化过的例子——电梯控制系统。电梯有开门、关门、运行、停止四种状态。不同状态下,按同一个按钮效果完全不同。
// 抽象状态
public interface ElevatorState {
void openDoor();
void closeDoor();
void run();
void stop();
}
// 具体状态:开门状态
public class OpenState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("门已经是开着的");
}
@Override
public void closeDoor() {
System.out.println("门正在关闭...");
// 切换到关门状态
}
@Override
public void run() {
System.out.println("门开着不能运行");
}
@Override
public void stop() {
System.out.println("已经停着");
}
}
// 具体状态:运行状态
public class RunState implements ElevatorState {
@Override
public void openDoor() {
System.out.println("运行中不能开门");
}
@Override
public void closeDoor() {
System.out.println("门已经是关着的");
}
@Override
public void run() {
System.out.println("已经在运行");
}
@Override
public void stop() {
System.out.println("正在停止...");
// 切换到停止状态
}
}
// 上下文
public class Elevator {
private ElevatorState currentState;
public Elevator() {
this.currentState = new StopState(); // 初始状态:停止
}
public void setState(ElevatorState state) {
this.currentState = state;
}
public void openDoor() {
currentState.openDoor();
}
public void closeDoor() {
currentState.closeDoor();
}
public void run() {
currentState.run();
}
public void stop() {
currentState.stop();
}
}
你看,每个状态类只关心自己状态下的行为逻辑。如果某个操作在当前状态下不允许,直接拒绝就行。这样代码的可读性和可维护性都上去了。
有限状态机(FSM)
说到状态模式,就不得不提有限状态机。有限状态机是一种数学模型,它由有限个状态、有限个输入事件、状态转移规则和初始状态组成。状态模式其实就是有限状态机在面向对象编程中的一种实现方式。
我做过一个游戏开发项目,里面的人物角色有“站立”、“行走”、“奔跑”、“跳跃”、“攻击”等状态。这些状态之间的转换是有严格规则的——你不能从“站立”直接跳到“攻击”,必须先切换到“攻击准备”状态。这种场景用有限状态机来管理再合适不过了。
我的经验:在实现有限状态机时,我习惯把状态转移表单独抽出来管理。这样状态和转移逻辑解耦,后期加新状态或者改转移规则,都不需要动状态类本身。
状态转移图
下面我用 SVG 画一个简单的电梯状态转移图,帮你直观理解状态之间的流转关系:
状态模式 vs 策略模式
这个问题我面试时经常被问到。很多人分不清状态模式和策略模式,其实它们有本质区别:
| 对比维度 | 状态模式 | 策略模式 |
|---|---|---|
| 核心目的 | 管理对象的状态流转 | 封装可互换的算法 |
| 状态关系 | 状态之间有转移规则 | 策略之间相互独立 |
| 行为选择 | 由当前状态决定 | 由客户端选择 |
| 状态数量 | 通常固定且有限 | 可以动态增加 |
| 典型场景 | 工作流、游戏角色 | 排序算法、支付方式 |
说白了,策略模式是你主动选算法,状态模式是状态替你做了选择。你想想看,在策略模式里,客户端需要知道有哪些策略,然后自己选一个。但在状态模式里,客户端根本不需要关心当前是什么状态,它只管调用方法,状态对象自己会处理。
实际项目中的应用
我在做电商系统时,订单状态管理就是一个典型的状态模式应用。订单有“待支付”、“已支付”、“已发货”、“已收货”、“已完成”、“已取消”等状态。每个状态下,用户能做的操作完全不同。
避坑指南:我曾经在一个项目中,把状态转移逻辑写在了上下文类里,结果状态类越来越多,上下文类也越来越臃肿。后来我改成把状态转移规则放在状态类自身,每个状态类都知道自己可以转移到哪些状态,这样上下文类就轻量多了。
还有一个常见的坑是状态爆炸。如果状态太多,状态类也会很多。我的建议是:状态数量控制在 5-7 个以内,超过这个数就要考虑用状态表或者状态机框架来管理了。
状态模式的优缺点
优点很明显:
- 把状态相关的行为集中到同一个类中,符合单一职责原则
- 新增状态很方便,只需要新增一个状态类
- 消除了大量的 if-else 或 switch-case 判断
缺点也不容忽视:
- 状态类会增多,如果状态太多会显得臃肿
- 状态转移逻辑分散在各个状态类中,不够集中
- 如果状态转移规则经常变化,维护成本会上升
我的建议:如果你发现状态转移规则特别复杂,可以考虑引入状态机框架,比如 Spring StateMachine 或者 Squirrel。这些框架帮你管理状态转移表,你只需要关注业务逻辑就行。
嗯,状态模式就讲到这里。记住一句话:状态模式的核心不是状态本身,而是状态之间的流转规则。搞清楚了这一点,你就能用好这个模式了。