本文是【GoF设计模式】系列第20篇,更多内容欢迎关注公众号:咖啡八杯
前言
为什么需要状态模式?
假设正在实现电商订单系统,订单有五种状态:待支付、已支付、已发货、已完成、已取消。每种状态下可执行的操作都不一样——待支付可以支付、可以取消;已发货只能确认收货;已完成不能再动。没有状态模式的时候,代码大概长这样:
class Order {
private String status = "PENDING";
public void pay() {
if ("PENDING".equals(status)) {
System.out.println("支付成功");
status = "PAID";
} else if ("PAID".equals(status)) {
throw new RuntimeException("已支付,不能重复支付");
} else if ("SHIPPED".equals(status)) {
throw new RuntimeException("已发货,不能支付");
} else if ("CANCELLED".equals(status)) {
throw new RuntimeException("已取消,不能支付");
}
// ...
}
public void ship() {
// 同样的 if-else 判断五种状态 ...
}
public void confirm() {
// 同样的 if-else 判断五种状态 ...
}
public void cancel() {
// 同样的 if-else 判断五种状态 ...
}
}
问题一目了然:状态判断和业务逻辑混在一起。5 种状态 × 4 个操作 = 20 个条件分支散落各处。新增一个"退货中"状态?四个方法都要改一遍。想搞清楚"已支付"状态能干什么?得翻遍所有方法找带 "PAID" 的分支。
换一种思路: "当前是什么状态"应该由一个专门的对象来承担,而不是由字符串常量来标记。状态模式解决的就是这个问题——把每种状态封装成一个类,让状态自己决定"我这个状态下能做什么、做完之后变成什么",Order 只负责持有当前状态并转发调用。
概念
状态模式(State Pattern)是一种行为型设计模式,核心思想是允许一个对象在内部状态改变时改变它的行为,使对象看起来好像修改了它的类型。
从理论上看,状态模式是有限状态机(Finite State Machine, FSM) 的面向对象实现:FSM 描述"一组状态、若干事件、以及状态之间的转换规则",状态模式则把每个状态变成一个对象、把转换规则封装进状态类内部。建立这个对应关系后,遇到"状态机"一词便能直接映射到状态模式,不会当作陌生概念。
可以把它想象成红绿灯:红灯状态下车辆要停下,绿灯状态下允许通行,黄灯状态下要减速——完全不同的行为,但对外都是"同一个红绿灯"。路口本身不需要有一大堆 if (color == red) ... 的逻辑,只要"当前是哪个灯就执行那个灯的规则",切换灯色时行为自然跟着变。
状态模式涉及三个角色:
- Context(上下文) :持有一个当前状态的引用,把行为委托给当前状态对象,并提供状态切换的入口
- State(状态接口) :定义 Context 在某一状态下可能执行的所有行为接口
- ConcreteState(具体状态) :实现状态接口,负责该状态下的具体行为,并决定操作完成后转向哪个状态
classDiagram
direction BT
class Context {
-state: State
+request()
}
class State {
<<interface>>
+handle() State
}
class ConcreteStateA {
+handle() State
}
class ConcreteStateB {
+handle() State
}
ConcreteStateA ..|> State : 实现
ConcreteStateB ..|> State : 实现
Context o--> State : 持有当前状态
ConcreteStateA ..> ConcreteStateB : 转换
ConcreteStateB ..> ConcreteStateA : 转换
图中各类之间的关系:Context 持有一个 State 引用,request 方法把调用委托给当前状态。ConcreteStateA 和 ConcreteStateB 各自实现 handle 方法,处理当前状态下的行为并返回下一个状态。状态之间通过"返回新状态对象"完成转换——ConcreteStateA 的 handle 可能返回 ConcreteStateB 的实例,Context 用返回值更新自身的当前状态。
实现
状态模式的落地有两种常见变体:一种是 GoF 原书的"状态类持有 Context 引用,主动调用 Context 的 setState 切换";另一种更现代的做法是"状态类的方法直接返回下一个状态对象,由 Context 根据返回值更新"。第二种变体更简洁——状态类不需要持有 Context 引用,耦合更彻底地降到零。本文主推这种变体。
基础实现
定义 State 接口,声明 handle 方法返回 State——这个返回值就是操作完成后要转向的下一个状态。ConcreteStateA 和 ConcreteStateB 各自实现 handle,处理完自身逻辑后返回对方的实例。Context 持有当前状态,在 request 方法中调用状态的 handle 并用返回值更新自身状态。
// State:状态接口,方法返回下一个状态
interface State {
State handle();
}
// ConcreteStateA:具体状态 A
class ConcreteStateA implements State {
public State handle() {
System.out.println("状态 A 的处理逻辑");
return new ConcreteStateB(); // 返回下一个状态
}
}
// ConcreteStateB:具体状态 B
class ConcreteStateB implements State {
public State handle() {
System.out.println("状态 B 的处理逻辑");
return new ConcreteStateA();
}
}
// Context:上下文
class Context {
private State state;
public Context(State initialState) {
this.state = initialState;
}
public void request() {
this.state = state.handle(); // 用返回值更新当前状态
}
}
// 客户端
class Client {
public static void main(String[] args) {
Context context = new Context(new ConcreteStateA());
context.request(); // 状态 A 的处理逻辑 → 切换到 B
context.request(); // 状态 B 的处理逻辑 → 切换回 A
}
}
角色对照:
- Context(上下文) :
Context,持有当前状态并转发调用 - State(状态接口) :
State,声明handle返回下一个状态 - ConcreteState(具体状态) :
ConcreteStateA、ConcreteStateB
关键点:状态之间的转换关系完全封装在状态类内部——ConcreteStateA.handle 明确知道自己下一个要变成 ConcreteStateB,Context 完全不关心具体的转换规则。这带来一个天然特性:新增状态或修改转换规则时,只需要改相关的状态类,Context 和其他状态类一行不动。缺点是状态类之间存在依赖关系——ConcreteStateA 引用了 ConcreteStateB,如果状态很多,这种引用关系会形成一张"状态图",需要有意识地管理。
引入一个具体场景:电商订单系统,五种状态之间按业务规则流转。每种状态类实现四个操作方法(pay、ship、confirm、cancel),能执行的操作返回新状态,不能执行的操作返回 this(保持当前状态)。
// State:订单状态接口
interface OrderState {
OrderState pay(OrderContext ctx);
OrderState ship(OrderContext ctx);
OrderState confirm(OrderContext ctx);
OrderState cancel(OrderContext ctx);
}
// ConcreteState:待支付状态
class PendingPayment implements OrderState {
public OrderState pay(OrderContext ctx) {
System.out.println("支付成功,订单号:" + ctx.getOrderId());
return new Paid();
}
public OrderState ship(OrderContext ctx) {
System.out.println("错误:待支付订单不能发货");
return this; // 保持当前状态
}
public OrderState confirm(OrderContext ctx) {
System.out.println("错误:待支付订单不能确认收货");
return this;
}
public OrderState cancel(OrderContext ctx) {
System.out.println("订单已取消:" + ctx.getOrderId());
return new Cancelled();
}
}
// ConcreteState:已支付状态
class Paid implements OrderState {
public OrderState pay(OrderContext ctx) {
System.out.println("错误:已支付,不能重复支付");
return this;
}
public OrderState ship(OrderContext ctx) {
System.out.println("订单已发货:" + ctx.getOrderId());
return new Shipped();
}
public OrderState confirm(OrderContext ctx) {
System.out.println("错误:已支付订单尚未发货");
return this;
}
public OrderState cancel(OrderContext ctx) {
System.out.println("订单已取消,金额原路退回");
return new Cancelled();
}
}
// ConcreteState:已发货状态
class Shipped implements OrderState {
public OrderState pay(OrderContext ctx) { return this; }
public OrderState ship(OrderContext ctx) { return this; }
public OrderState confirm(OrderContext ctx) {
System.out.println("确认收货成功:" + ctx.getOrderId());
return new Completed();
}
public OrderState cancel(OrderContext ctx) {
System.out.println("错误:已发货订单请申请退货");
return this;
}
}
// ConcreteState:已完成状态(终态)
class Completed implements OrderState {
public OrderState pay(OrderContext ctx) { return this; }
public OrderState ship(OrderContext ctx) { return this; }
public OrderState confirm(OrderContext ctx) { return this; }
public OrderState cancel(OrderContext ctx) { return this; }
}
// ConcreteState:已取消状态(终态)
class Cancelled implements OrderState {
public OrderState pay(OrderContext ctx) { return this; }
public OrderState ship(OrderContext ctx) { return this; }
public OrderState confirm(OrderContext ctx) { return this; }
public OrderState cancel(OrderContext ctx) { return this; }
}
// Context:订单
class OrderContext {
private String orderId;
private OrderState state;
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new PendingPayment(); // 初始状态
}
public String getOrderId() { return orderId; }
// 每个业务方法把调用委托给当前状态,用返回值更新状态
public void pay() { state = state.pay(this); }
public void ship() { state = state.ship(this); }
public void confirm() { state = state.confirm(this); }
public void cancel() { state = state.cancel(this); }
}
角色对照:
- Context(上下文) :
OrderContext,持有订单数据和当前状态 - State(状态接口) :
OrderState,声明四个业务方法 - ConcreteState(具体状态) :
PendingPayment、Paid、Shipped、Completed、Cancelled
关键点:每个状态类只关心"我这个状态下每个操作该怎么响应、之后变成什么"——PendingPayment 只需要知道"支付后变成 Paid、取消后变成 Cancelled",其他状态怎么转换它一概不管。业务扩展友好:新增"退货中"状态只要写一个 Refunding 类,同时修改 Completed.pay 之类的相关方法,其他状态类一行不动。终态(Completed 和 Cancelled)的四个方法都返回 this,代码简洁明确——"到我这里就是终点,任何操作都不再改变状态"。
上面的状态都是普通类,每次切换状态都 new 一个新对象。Java 还有一种更省事的写法——用 enum 实现状态接口:枚举常量本身就是单例,切换状态时直接返回另一个常量,连 new 都省了。沿用前文的 State 接口(State handle()),A、B 两个状态切换的枚举版本如下。
// 枚举实现状态模式:状态常量天然单例,无需 new
enum StateEnum implements State {
STATE_A {
public State handle() {
System.out.println("状态 A 的处理逻辑");
return STATE_B; // 直接返回另一个常量
}
},
STATE_B {
public State handle() {
System.out.println("状态 B 的处理逻辑");
return STATE_A;
}
};
}
枚举实现的代价是状态集合在编译期固定,无法运行时新增状态;转换关系也写在每个常量内部,状态多了会显得拥挤。状态数量少、转换稳定时用枚举最清爽;状态会动态扩展或转换规则复杂时,用类更灵活。
总结
本质:把不同状态下的行为拆分成独立的类,让对象在状态变化时切换实现类,从而"看起来像换了个类"。
什么时候用:
- 对象的行为完全由内部状态决定,且状态之间有明确转换规则(订单、审批、TCP、游戏角色)
- 代码里出现大量基于状态的
if-else/switch判断,且每个分支的行为逻辑各不相同 - 状态数量会增长——新增状态时不希望改动所有已有代码
- 需要显式建模状态机——业务方通过状态类名就能理解流转规则
什么时候不用:
- 只有 2-3 个状态且逻辑简单——一个枚举 + 一个 switch 更直观
- 状态之间没有转换规则,本质上是"数据分类"而不是"行为分类"
- 状态频繁变化且每次都新建对象——性能敏感场景考虑用状态单例复用
- 对象没有"行为随状态变化"的需求——单纯的数据状态字段不需要模式化
简单记忆:状态自己变身法,转换规则藏于内;if-else 都下岗,对象仿佛换新装。
相似模式区分
总览
| 模式 | 核心意图 | 典型场景 |
|---|---|---|
| 状态 | 根据内部状态改变对象行为,对象自己驱动状态转换 | 订单流转、审批、TCP 状态机 |
| 策略 | 封装可互换的算法族,由外部选择使用哪个 | 排序算法、支付方式、折扣策略 |
| 责任链 | 请求沿链条依次传递,由多个处理者协同处理 | 拦截器、审批链、日志过滤 |
简单记忆:状态"自己管自己变身",策略"别人来选我一个",责任链"链上一个接一个"。
状态 vs 策略
两者的类图几乎一模一样——Context + 接口 + 多个实现类。刚学的时候最容易搞混,但它们回答的是完全不同的问题:
| 维度 | 状态模式 | 策略模式 |
|---|---|---|
| 核心意图 | 根据内部状态改变对象行为 | 封装可互换的算法族,运行时切换 |
| 谁决定用哪个实现 | 对象自身(通过状态转换) | 外部客户端(手动注入) |
| 实现类是否知道彼此 | 是——每个状态类知道自己转向哪个状态 | 否——策略之间完全独立 |
| 切换时机 | 事件触发,自动切换 | 客户端调用 setStrategy 手动切换 |
| 典型场景 | 订单流转、审批流、游戏角色 | 折扣算法、支付方式、排序策略 |
逐步区分法:
- 如果行为由对象自身的内部状态决定,且状态之间自动转换 → 选状态
- 如果行为由外部客户端选择,不同实现之间互相独立 → 选策略
- 如果不确定:问自己"谁来切换"——对象自己切换用状态,外部切换用策略
- 如果实现类会引用其他实现类(
return new StateB())→ 一定是状态
简单记忆口诀:状态"自己管自己变身",策略"别人来选我一个"。
推荐:两者都能用时优先选策略——它更简单、耦合更低。只有在需要显式建模状态机、状态之间存在明确转换规则的场景才用状态模式。策略是"我需要一个算法,你告诉我用哪个",状态是"我的行为由我此刻的样子决定,别人别操心"。
状态 vs 责任链
两者在"根据情况分派处理"这个场景上有重叠——审批流用状态模式可以做,用责任链也可以做——但内部结构完全不同:
| 维度 | 状态模式 | 责任链模式 |
|---|---|---|
| 核心意图 | 同一对象在不同状态下表现出不同行为 | 请求沿处理者链条依次传递,直到被处理 |
| 对象数量 | 一个 Context + 多个状态类,Context 唯一 | 多个 Handler 组成链,请求经过所有 Handler |
| 控制流 | Context 委托给当前状态,只调用一个状态类 | 请求从头传到尾,多个 Handler 依次尝试 |
| 典型场景 | 订单流转、TCP 状态机、游戏角色 | Servlet 过滤器、拦截器、审批链 |
逐步区分法:
- 如果核心是"同一个对象在不同时刻表现不同" → 选状态
- 如果核心是"同一个请求经过多个处理者" → 选责任链
- 审批场景:如果关心"文档当前处于哪个审批阶段" → 状态;如果关心"审批人一个接一个盖章" → 责任链
- 状态转换是"我变成谁",责任链传递是"我把请求交给下一个"
简单记忆口诀:状态是"一个对象换角色",责任链是"一个请求走链条"。
推荐:审批系统这类场景两者都能用,选择的关键在于——如果流程节点数量固定、每个节点有明确的业务含义(草稿/主管审批/经理审批),选状态;如果处理者会动态增减、链条本身可能重排,选责任链。前者建模的是"文档的生命周期",后者建模的是"事件的处理流水线"。
练习题目
智能台灯
题目描述:小明家有一盏智能台灯,有三种状态:关闭(Off)、开启(On)和闪烁(Blinking)。台灯初始状态为关闭。
台灯可通过两种操作控制:
按下按钮(button):
- 关闭 → 开启,输出
Light is ON - 开启 → 闪烁,输出
Light is Blinking - 闪烁 → 关闭,输出
Light is OFF
感应触发(sensor):
- 关闭 → 闪烁,输出
Light is Blinking - 开启 → 关闭,输出
Light is OFF - 闪烁 → 开启,输出
Light is ON
输入描述:第一行是一个整数 n(1 ≤ n ≤ 1000),表示操作次数。接下来的 n 行,每行包含一个字符串,表示操作类型(button 或 sensor)。
输出描述:对于每个操作,输出一行,表示执行该操作后灯的状态。
输入示例:
5
button
button
button
sensor
sensor
输出示例:
Light is ON
Light is Blinking
Light is OFF
Light is Blinking
Light is ON
状态转换表:
| 当前状态 | button | sensor |
|---|---|---|
| Off | → On | → Blinking |
| On | → Blinking | → Off |
| Blinking | → Off | → On |
解题思路:三种状态 × 两种操作 = 6 种转换。用返回新状态的变体:每个状态类实现 handleButton 和 handleSensor 两个方法,直接返回转换后的下一个状态对象;Light 作为 Context 持有当前状态,接收操作后用返回值更新自身状态。
角色对应:
- Context(上下文) :
Light,持有当前状态并转发操作 - State(状态接口) :
State,声明handleButton和handleSensor都返回State - ConcreteState(具体状态) :
StateOff、StateOn、StateBlinking
核心逻辑:
- 每个状态类实现两个方法,直接输出信息并返回下一个状态
Light收到button或sensor操作后调用当前状态对应的方法,用返回值更新自身- 状态转换关系全部封装在状态类内部,
Light只是转发调用
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
Light light = new Light();
while (n-- > 0) {
String type = sc.next();
if ("button".equals(type)) {
light.button();
} else {
light.sensor();
}
}
}
}
// State:状态接口
interface State {
State handleButton();
State handleSensor();
}
// ConcreteState:关闭状态
class StateOff implements State {
public State handleButton() {
System.out.println("Light is ON");
return new StateOn();
}
public State handleSensor() {
System.out.println("Light is Blinking");
return new StateBlinking();
}
}
// ConcreteState:开启状态
class StateOn implements State {
public State handleButton() {
System.out.println("Light is Blinking");
return new StateBlinking();
}
public State handleSensor() {
System.out.println("Light is OFF");
return new StateOff();
}
}
// ConcreteState:闪烁状态
class StateBlinking implements State {
public State handleButton() {
System.out.println("Light is OFF");
return new StateOff();
}
public State handleSensor() {
System.out.println("Light is ON");
return new StateOn();
}
}
// Context:智能台灯
class Light {
private State state;
public Light() {
this.state = new StateOff(); // 初始状态为关闭
}
public void button() {
this.state = state.handleButton(); // 用返回值更新状态
}
public void sensor() {
this.state = state.handleSensor();
}
}
扩展:实际项目中的状态模式
电商订单流转
订单系统是状态模式最经典的应用——待支付、已支付、已发货、已完成、已取消这几种状态之间的流转规则清晰,每种状态下的可用操作各不相同。上文实现章节的业务代码就是这个场景的完整版本,几乎所有电商中台的订单服务都能看到类似结构。这种建模方式的最大好处是新增状态不改老代码——比如后来加入"退货中"状态,只需要新增一个 Refunding 类,其他四个状态类几乎不动,符合开闭原则。
// 客户端调用极简,看不到任何 if-else
OrderContext order = new OrderContext("ORD_001");
order.pay(); // 支付成功 → Paid
order.ship(); // 订单已发货 → Shipped
order.confirm(); // 确认收货成功 → Completed
order.cancel(); // 错误:订单已完成,不能取消(自动被 Completed 状态挡回)
订单状态变更后往往还要联动其他系统--支付成功要通知库存扣减、已发货要通知物流跟踪。这时状态模式常和观察者模式组合使用:状态切换时发布一个事件,库存、物流等组件订阅该事件各自处理。状态模式管"怎么变",观察者模式管"变了之后通知谁",两者分工天然互补。
状态持久化与恢复
实际项目里状态不能只活在内存--订单状态要写进数据库,服务重启后还得能恢复。但状态对象是内存里的类实例,没法直接落库。常见做法是Context 只存一个状态标识字段(枚举或字符串),持久化时只存这个标识;从数据库加载时,根据标识重建对应的状态对象。这样既保留了状态模式的行为封装,又解决了存储问题。
class OrderContext {
private String orderId;
private OrderState state;
private String stateCode; // 落库字段:只存状态标识
// 从数据库恢复:根据标识重建状态对象
public void restore() {
this.state = switch (stateCode) {
case "PendingPayment" -> new PendingPayment();
case "Paid" -> new Paid();
case "Shipped" -> new Shipped();
case "Completed" -> new Completed();
case "Cancelled" -> new Cancelled();
default -> throw new IllegalStateException("未知状态:" + stateCode);
};
}
// 状态切换后同步更新标识,供下次落库
public void pay() {
state = state.pay(this);
stateCode = state.getClass().getSimpleName(); // 类名即状态标识
}
}
状态标识和状态类的映射关系集中在一处(如上面的 switch 或一个 Map),新增状态时只需补一条映射,业务逻辑不受影响。
游戏角色状态机
游戏角色通常有多种状态:待机、移动、攻击、受击、死亡,每种状态下的输入响应和动画播放完全不同。Unity3D 的 Animator 控制器、虚幻引擎的 State Machine 本质上就是可视化的状态模式实现。除了行为切换外,游戏状态机还有一个特色——帧驱动的自动转换:update 方法每帧被调用,攻击动画播完(比如 30 帧)后自动回到待机,这不是外部触发的,是状态自身根据内部计数器决定的。
class AttackingState implements CharacterState {
private int frameCount = 0;
public CharacterState update(CharacterContext ctx) {
frameCount++;
if (frameCount >= 30) {
return new IdleState(); // 动画播完,自动回待机
}
return this;
}
public CharacterState hit(CharacterContext ctx, int damage) {
ctx.reduceHealth(damage);
// 状态转换可能依赖上下文数据:血量决定去受击还是死亡
return ctx.getHealth() > 0 ? new HitState() : new DeadState();
}
// ... move、attack 略 ...
}
TCP 连接状态
TCP 协议的状态转换是状态模式的教科书级案例,也是 GoF 原书举的例子之一。连接有多个状态(LISTEN、SYN_RECEIVED、ESTABLISHED、FIN_WAIT、CLOSED),每次收到网络事件(SYN、ACK、FIN)时的响应和转换目标完全由当前状态决定。协议栈实现(如 Linux 内核的 TCP 模块)虽然是 C 语言写的,但结构上正是状态模式——每个状态一张"事件 → 响应 + 下一状态"的表。
class EstablishedState implements TcpState {
public TcpState close(TcpContext ctx) {
System.out.println("发送 FIN,进入 FIN_WAIT_1");
return new FinWaitState();
}
public TcpState acknowledge(TcpContext ctx) {
System.out.println("正常数据传输中的 ACK");
return this; // 保持 ESTABLISHED
}
// 不认识的事件返回 this ...
}
工作流审批系统
审批流程中,文档在不同审批阶段有不同的审批权限:草稿 → 主管审批 → 经理审批 → 总监审批 → 通过。用状态模式建模的好处是审批阶段本身就是状态类——SupervisorApproval、ManagerApproval 这些类名直接对应业务概念,产品经理看代码不用翻译。相比之下,用"字段 stage 加一堆 if (stage == 2)"的写法则完全丢失了业务语义。
class SupervisorApproval implements ApprovalState {
public ApprovalState approve(ApprovalContext ctx, String approver) {
System.out.println("主管 [" + approver + "] 审批通过");
return new ManagerApproval(); // 进入下一阶段
}
public ApprovalState reject(ApprovalContext ctx, String approver) {
System.out.println("主管 [" + approver + "] 驳回");
return new DraftState(); // 退回草稿
}
}
文档发布状态
内容管理系统里文档的生命周期天然是状态机:草稿 → 审核中 → 已审批 → 已发布。每个状态只暴露允许的操作——已发布的文档不能直接编辑(需要创建新版本)、审核中的文档不能被作者修改(防止审核过程中内容变化)。这些约束用状态模式来表达最自然,不必到处写"如果当前是审核中且用户是作者就抛异常"这样的判断。写博客发文章的后台、Confluence 之类的知识库、企业公告系统,都能套上这个结构。
class UnderReview implements DocState {
public DocState edit(DocContext ctx) {
System.out.println("错误:审核中不能编辑");
return this;
}
public DocState approve(DocContext ctx) {
System.out.println("文档审核通过:" + ctx.getTitle());
return new Approved();
}
public DocState reject(DocContext ctx) {
System.out.println("文档审核未通过,退回草稿");
return new Draft();
}
// ... 其他不允许的操作都返回 this ...
}
Spring StateMachine 框架
手写状态类在状态不多时够用,可一旦状态超过十几个、转换规则错综复杂(审批流叠加会签、驳回、加签),自己维护状态类会越来越吃力。Spring 生态的 Spring StateMachine 正是为这种场景而生——它是 Java 领域最主流的状态机框架,把状态、事件、转换关系用配置式 API 统一描述,还内置了持久化、嵌套状态、状态机事件监听等企业级能力。下面是一个极简配置示例,States 和 Events 分别是状态与事件的枚举。
@Configuration
@EnableStateMachine
class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<States, Events> {
@Override
public void configure(StateMachineStateConfigurer<States, Events> states) throws Exception {
states
.withStates()
.initial(States.PENDING) // 初始状态
.states(EnumSet.allOf(States.class)) // 全部状态
.end(States.COMPLETED); // 终态
}
@Override
public void configure(StateMachineTransitionConfigurer<States, Events> transitions) throws Exception {
transitions
.withExternal()
.source(States.PENDING).target(States.PAID).event(Events.PAY) // 待支付 -> 已支付
.and()
.withExternal()
.source(States.PAID).target(States.SHIPPED).event(Events.SHIP); // 已支付 -> 已发货
}
}
可以看到,框架里状态和转换关系是声明式配置出来的,不再需要为每个状态写一个类——source -> target + event 一行就描述一条转换。当业务状态机复杂到普通状态类难以维护时,引入 Spring StateMachine 能省下大量样板代码,这也是它和手写状态模式各擅胜场的分界线。
技术交流 & 更多原创内容,关注公众号:咖啡八杯