状态模式-State Pattern
序言
红绿灯的状态决定车辆的行为:红灯停、绿灯行、黄灯减速等待。同一个路口,因为"状态"不同,交通规则(行为)完全不同。软件世界里也是如此——很多对象的行为取决于它当前所处的状态。
以电商订单为例:订单在"待支付"状态可以取消或支付;在"已支付"状态可以发货;在"已发货"状态可以确认收货……同一个订单对象,在不同状态下能做的操作完全不同。
如果不用状态模式,最直接的写法就是一大堆 if-else:根据当前状态字段判断该执行什么操作、下一步允许转到哪个状态。状态越多,if-else 越深,代码越难以维护。状态模式(State Pattern)正是为这种情况而生:把每个状态封装成一个独立的类,让对象在状态切换时"看起来像换了一个类"。
定义
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。
两个关键点:
- 状态决定行为:对象的每个方法由当前状态对象负责实现;
- 看起来像换类:客户端调用同样的方法,因为状态不同而得到不同行为——仿佛对象被换成了另一个类的实例。
结构
让我们从一个问题开始
假设我们要实现订单的状态流转:待支付 → 已支付 → 已发货 → 已完成,每个状态下允许执行的操作不同。
不用状态模式,代码会长这样:
public class Order {
private String state = "PENDING_PAYMENT";
public void pay() {
if ("PENDING_PAYMENT".equals(state)) {
state = "PAID";
System.out.println("支付成功");
} else {
throw new IllegalStateException("当前状态不允许支付");
}
}
public void ship() {
if ("PAID".equals(state)) {
state = "SHIPPED";
System.out.println("已发货");
} else {
throw new IllegalStateException("当前状态不允许发货");
}
}
// confirm()、cancel()…… 每个方法都要重复判断状态
}
问题显而易见:
- 每个方法都要判断状态,状态一多,
if-else到处重复; - 新增一个状态(比如"退款中"),需要修改所有相关方法;
- 状态流转规则散落在各个方法中,无法直观看到"状态机"全貌。
状态模式的结构
把每个状态封装成独立类,订单(Context)只维护一个"当前状态"引用:
各个角色的职责如下:
- Context(上下文):持有当前状态对象,把请求委托给状态对象处理,对应图中的
Order。 - State(抽象状态):定义状态相关行为的接口,对应图中的
OrderState。 - ConcreteState(具体状态):实现特定状态下的行为,并负责状态流转(把自己的后继状态设置回 Context),对应图中的
PendingPaymentState、PaidState、ShippedState。
关键设计:状态流转的逻辑从 Context 中移出,放进了各状态类。每个状态类只关心"自己在当前状态下该做什么、下一步能去哪个状态"。
代码实现
我们来实现订单状态机。
定义抽象状态接口
public interface OrderState {
void pay(Order order);
void ship(Order order);
void confirm(Order order);
void cancel(Order order);
}
定义上下文(订单)
public class Order {
private OrderState state;
public Order() {
// 初始状态:待支付
this.state = new PendingPaymentState();
}
public void setState(OrderState state) {
this.state = state;
}
public String getStateName() {
return state.getClass().getSimpleName();
}
// 以下方法全部委托给当前状态对象
public void pay() {
state.pay(this);
}
public void ship() {
state.ship(this);
}
public void confirm() {
state.confirm(this);
}
public void cancel() {
state.cancel(this);
}
}
定义具体状态
每个状态类实现自己允许的行为,并执行状态流转:
public class PendingPaymentState implements OrderState {
@Override
public void pay(Order order) {
System.out.println("待支付状态:支付成功");
// 状态流转:待支付 -> 已支付
order.setState(new PaidState());
}
@Override
public void ship(Order order) {
throw new IllegalStateException("待支付状态不允许发货");
}
@Override
public void confirm(Order order) {
throw new IllegalStateException("待支付状态不允许确认收货");
}
@Override
public void cancel(Order order) {
System.out.println("待支付状态:订单取消");
}
}
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("已支付状态不允许重复支付");
}
@Override
public void ship(Order order) {
System.out.println("已支付状态:发货成功");
// 状态流转:已支付 -> 已发货
order.setState(new ShippedState());
}
@Override
public void confirm(Order order) {
throw new IllegalStateException("已支付状态不允许确认收货");
}
@Override
public void cancel(Order order) {
System.out.println("已支付状态:订单取消,发起退款");
// 简单起见这里只打印,实际会进入"退款中"状态
}
}
public class ShippedState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("已发货状态不允许支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("已发货状态不允许重复发货");
}
@Override
public void confirm(Order order) {
System.out.println("已发货状态:确认收货,订单完成");
// 状态流转:已发货 -> 已完成(这里用 null 简化表示终态)
order.setState(new FinishedState());
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("已发货状态不允许取消");
}
}
public class FinishedState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("订单已完成,不能支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("订单已完成,不能发货");
}
@Override
public void confirm(Order order) {
throw new IllegalStateException("订单已完成,不能确认收货");
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("订单已完成,不能取消");
}
}
客户端使用
public class Main {
public static void main(String[] args) {
Order order = new Order();
System.out.println("当前状态:" + order.getStateName());
order.pay();
System.out.println("当前状态:" + order.getStateName());
order.ship();
System.out.println("当前状态:" + order.getStateName());
order.confirm();
System.out.println("当前状态:" + order.getStateName());
System.out.println("========================");
// 再创建一个订单,尝试非法操作
Order order2 = new Order();
try {
order2.ship(); // 待支付状态不允许发货
} catch (IllegalStateException e) {
System.out.println("非法操作被拦截:" + e.getMessage());
}
}
}
输出结果:
当前状态:PendingPaymentState
待支付状态:支付成功
当前状态:PaidState
已支付状态:发货成功
当前状态:ShippedState
已发货状态:确认收货,订单完成
当前状态:FinishedState
========================
非法操作被拦截:待支付状态不允许发货
核心收益:
- 订单类(Context)不再有任何
if-else,所有方法只是简单的委托; - 新增状态只需要新增一个状态类,不需要修改订单类;
- 每个状态的流转规则集中在自己类中,状态机一目了然。
在开源框架中的应用
Spring StateMachine:从源码看状态机的核心抽象
Spring StateMachine(org.springframework.statemachine)是状态模式在企业级状态机中的完整实现。从源码层面看,它的核心接口与状态模式的结构直接对应:
- State(抽象状态) =
org.springframework.statemachine.state.State<S,E>接口——每个状态对象封装了"进入该状态时的行为(entry actions)"和"离开时的行为(exit actions)"; - Context(上下文) =
StateMachine接口——持有当前状态,接收事件驱动流转; - 状态流转 =
Transition——描述"从哪个状态、在什么事件/守卫条件下、转到哪个状态"。
状态接口:State
// org.springframework.statemachine.state.State(源码骨架)
public interface State<S, E> extends Region<S, E> {
// 状态 id(如 "PENDING_PAYMENT")
S getId();
// 进入该状态时执行的动作(entry actions)
Collection<Action<S, E>> getEntryActions();
// 离开该状态时执行的动作(exit actions)
Collection<Action<S, E>> getExitActions();
// 是否为初始状态 / 终态
boolean isInitial();
boolean isEnd();
// 状态是否需要检查事件……
...
}
对照状态模式:状态对象(State)把"与状态相关的行为"(entry/exit actions)封装在自身内部,而不是由 Context 用 if-else 判断——这正是状态模式"状态决定行为"的核心。
上下文接口:StateMachine
// org.springframework.statemachine.StateMachine(源码骨架)
public interface StateMachine<S, E> extends Region<S, E> {
// 获取当前状态(Context 持有的"当前状态"引用)
State<S, E> getState();
// 发送事件,驱动状态流转
boolean sendEvent(E event);
// 启动/停止状态机
void start();
void stop();
...
}
流转描述:Transition
// org.springframework.statemachine.transition.Transition(源码骨架)
public interface Transition<S, E> {
// 源状态 / 目标状态
State<S, E> getSource();
State<S, E> getTarget();
// 守卫条件:事件是否允许触发本次流转
Guard<S, E> getGuard();
// 流转时执行的动作
Collection<Action<S, E>> getActions();
...
}
流转核心:AbstractStateMachine 的事件处理
AbstractStateMachine 收到事件后的核心逻辑是:查找满足条件(事件类型 + 守卫)的 Transition,执行"退出旧状态 → 执行流转动作 → 进入新状态",并更新 Context 持有的当前状态:
// org.springframework.statemachine.support.AbstractStateMachine(源码骨架)
protected boolean handleEventInternal(E event) {
...
// 1. 从当前状态的出向转移中找到"匹配该事件"的 Transition
Transition<S, E> transition = findTransition(event, ...);
// 2. 执行"离开当前状态"的 exit actions
for (Action<S, E> action : currentState.getExitActions()) {
action.execute(stateContext);
}
// 3. 执行流转本身的 actions
for (Action<S, E> action : transition.getActions()) {
action.execute(stateContext);
}
// 4. 更新上下文持有的"当前状态"
currentState = transition.getTarget();
// 5. 执行"进入新状态"的 entry actions
for (Action<S, E> action : currentState.getEntryActions()) {
action.execute(stateContext);
}
...
}
核心思想:状态机不再用 if (state == PAID) ... 判断行为,而是把行为(entry/exit actions)挂在每个 State 对象上,流转时由框架统一调度——这与我们的订单实现完全同构,只是 Spring StateMachine 把状态流转规则集中到了配置层,并额外提供 了 Guard(守卫条件)、持久化、分布式状态机等工程能力。
JDK 中的应用:Thread 状态
java.lang.Thread.State 定义了线程的六个状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED。JDK 内部通过状态机约束线程生命周期:只有 NEW 状态才能 start(),TERMINATED 是终态等——这是状态思想在 JVM 内部的体现(JDK 的实现方式不完全等价于 GoF 状态模式,但状态决定行为的本质一致)。
适用场景
- 对象的行为依赖其状态,且状态数量较多、流转规则复杂时,状态模式可以把状态逻辑从大方法中解放出来。
- 当状态流转规则(哪个状态在什么事件下转到哪个状态)需要集中、清晰、可维护地表达时,状态模式/状态机是标准答案。
- 当同一对象在不同状态下需要执行不同行为,且希望运行时动态切换行为时,状态模式让对象"看起来换了一个类"。
- 注意:如果状态很少且流转简单(一两个
if-else就够),引入状态模式反而增加类数量;此时直接条件判断更合适。
与其他设计模式的关系
- 状态模式与策略模式(Strategy)结构几乎一样(Context + 接口 + 多个实现),但意图不同:策略模式的多个实现是相互独立、可替换的算法,由客户端选择;状态模式的多个实现是相互关联、自动流转的状态,由状态对象自己决定后继。
- 状态对象可以通过**单例模式(Singleton)**共享(无状态的状态类可以复用),减少对象创建开销。
- 状态变化时需要通知外部,可以结合观察者模式(Observer);需要记录状态流转历史,可以结合备忘录模式(Memento)。
总结
状态模式把"与状态相关的行为"从 Context 中剥离,封装到独立的状态类中:
- Context 的方法退化为对状态对象的简单委托,消除大量
if-else; - 每个状态类自己管理行为与流转,新增状态无需修改 Context;
- 客户端视角下,对象的行为随状态变化而变化,"看起来换了一个类"。
状态模式的缺点是:状态类数量随状态数线性增长,且状态类之间通过 Context 间接流转,可能产生一定理解成本。但对于订单、工作流、审批、协议状态等复杂状态机场景,状态模式(及其工程化形态 Spring StateMachine)带来的可维护性收益是决定性的。