跳到主要内容

状态模式-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.

允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

两个关键点:

  1. 状态决定行为:对象的每个方法由当前状态对象负责实现;
  2. 看起来像换类:客户端调用同样的方法,因为状态不同而得到不同行为——仿佛对象被换成了另一个类的实例。

结构​

让我们从一个问题开始​

假设我们要实现订单的状态流转:待支付 → 已支付 → 已发货 → 已完成,每个状态下允许执行的操作不同。

不用状态模式,代码会长这样:

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()…… 每个方法都要重复判断状态
}

问题显而易见:

  1. 每个方法都要判断状态,状态一多,if-else 到处重复;
  2. 新增一个状态(比如"退款中"),需要修改所有相关方法;
  3. 状态流转规则散落在各个方法中,无法直观看到"状态机"全貌。

状态模式的结构​

把每个状态封装成独立类,订单(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
========================
非法操作被拦截:待支付状态不允许发货

核心收益:

  1. 订单类(Context)不再有任何 if-else,所有方法只是简单的委托;
  2. 新增状态只需要新增一个状态类,不需要修改订单类;
  3. 每个状态的流转规则集中在自己类中,状态机一目了然。

在开源框架中的应用​

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)带来的可维护性收益是决定性的。