📌 PDF:AI人工智能 — AI Agent智能体实战项目
大白话说Java设计模式-32-状态模式(业务实战篇):大白商城订单状态机的"行为随状态变"
📌 一句话本质:状态模式就是"行为随状态变",把每个状态封装成类,状态之间自动转换。
🏷️ 标签:状态模式 / Java 设计模式 / 状态机 / 订单状态 / 大白商城 🎯 适合:初中级后端 / 经常被"状态逻辑"困扰的工程师
目录
- 一、业务场景引入:为什么需要"状态机"?
- 二、反面教材:if-else 堆状态的"灾难现场"
- 三、模式原理:状态的"状态对象 + 转换"
- 四、实战代码:大白商城订单状态机完整实现
- 五、状态 vs 策略 / 状态机
- 六、Spring StateMachine 怎么用?
- 七、状态 + 观察者怎么组合?
- 八、工程决策 Checklist
- 九、与其他模式协作
- 十、本篇小结 + 下篇预告
一、业务场景引入:为什么需要"状态机"?
大白商城 2024 年订单中心遇到一个老大难:
“订单有 6 种状态(待支付/已支付/已发货/已完成/已取消/退款中),每种状态能做的操作不一样,if-else 写到 1500 行!”
我打开代码一看:
/**
* ❌ 大白商城老的订单代码
*/
public class OrderServiceV0 {
public void pay(String orderId) {
Order order = orderDao.findById(orderId);
if (order.getStatus().equals("PENDING_PAY")) {
// 1️⃣ 校验
// 2️⃣ 改状态
order.setStatus("PAID");
// 3️⃣ 发事件
// 4️⃣ 通知
} else if (order.getStatus().equals("PAID")) {
throw new RuntimeException("已支付,请勿重复支付");
} else if (order.getStatus().equals("SHIPPED")) {
throw new RuntimeException("已发货,不能支付");
}
// ... 500 行 else if
}
public void ship(String orderId) {
Order order = orderDao.findById(orderId);
if (order.getStatus().equals("PAID")) {
// 发货
} else if (order.getStatus().equals("PENDING_PAY")) {
throw new RuntimeException("未支付,不能发货");
}
// ... 500 行
}
public void cancel(String orderId) {
Order order = orderDao.findById(orderId);
if (order.getStatus().equals("PENDING_PAY")) {
// 取消
} else if (order.getStatus().equals("PAID")) {
// 退款 + 取消
} else if (order.getStatus().equals("SHIPPED")) {
// 拒绝取消
}
// ... 500 行
}
}
这种代码的痛点:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 1500 行 if-else | 难维护 |
| ② | 加新状态要改 N 个方法 | 改一处忘一处 |
| ③ | 状态转换容易漏 | Bug 频出 |
| ④ | 业务状态被淹没 | 难以理解 |
老板要的"加新状态只加 1 个类",怎么搞?
答案就是——状态模式。
1.1 大白话讲透状态
继续打比方:
场景:订单有 6 种状态,每种状态下能做的操作不一样。
- 错误做法:每个方法(pay/ship/cancel)都写 if-else 判断状态
- 正确做法:
- 把每个状态封装成类:
PendingPayState/PaidState/ShippedState/ …- 每个状态类只关心自己的行为
- 状态之间自动转换
- 加新状态?只加 1 个 State 类
状态模式 = 对象行为随状态变化,把每个状态封装成类。
1.2 状态模式的 3 个真实场景
大白商城里,状态模式用在:
| 场景 | “状态” |
|---|---|
| 订单状态 | 待支付/已支付/已发货/已完成/已取消/退款中 |
| 支付状态 | 待支付/支付中/已支付/已退款/支付失败 |
| 物流状态 | 待发货/已发货/运输中/已签收/已退货 |
| 账号状态 | 未激活/已激活/已冻结/已注销 |
| 优惠券状态 | 未使用/已使用/已过期/已作废 |
任何"行为随状态变"的场景,都用状态模式。
二、反面教材:if-else 堆状态的"灾难现场"
我们看 4 个反面教材,看它们是怎么一步步崩的。
2.1 反面教材 v1:if-else 堆叠
/**
* ❌ 反面教材 v1:if-else 堆叠
*/
public void pay() {
if (status.equals("PENDING_PAY")) { ... }
else if (status.equals("PAID")) { ... }
// 100 个 else if
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 1500 行 if-else | 难维护 |
| ② | 加状态要改所有方法 | 改 N 处 |
2.2 反面教材 v2:switch-case
/**
* ❌ 反面教材 v2:switch-case
*/
public void pay() {
switch (status) {
case "PENDING_PAY": ...; break;
case "PAID": ...; break;
// 100 个 case
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 业务逻辑分散 | 难追踪 |
| ② | case 字符串易错 | 编译期不查 |
2.3 反面教材 v3:状态判断分散
/**
* ❌ 反面教材 v3:状态判断分散
*/
public void pay() { /* 状态判断 */ }
public void ship() { /* 状态判断 */ }
public void cancel() { /* 状态判断 */ }
// 100 个方法都做状态判断
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 状态判断重复 | 改状态要改 N 个方法 |
| ② | 无法复用 | 业务逻辑重复 |
2.4 反面教材 v4:完全没抽象
/**
* ❌ 反面教材 v4:完全没抽象
*/
public class OrderServiceV4 {
public void process() {
// 1000 行所有逻辑混在一起
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 无法复用 | 改一处牵动全身 |
| ② | 无法测试 | 单元测试跑不动 |
2.5 4 个反面教材的共同病根
| 痛点 | 反模式方案能不能解决? |
|---|---|
| 加状态不动老代码 | ❌ 全部要改 |
| 状态封装 | ❌ 全部分散 |
| 状态转换清晰 | ❌ 全部混乱 |
| 行为随状态变 | ❌ 全部要写 if |
必须上状态模式。
三、模式原理:状态的"状态对象 + 转换"
3.1 状态的 2 个核心概念
状态(State):
- 封装对象在某个状态下的行为
- 不同状态的行为不同
- 状态之间可以转换
上下文(Context):
- 持有当前状态
- 委托状态处理请求
- 状态转换时通知
大白商城例子:
| 概念 | 例子 |
|---|---|
| 状态 | PendingPayState / PaidState / ShippedState |
| 上下文 | Order(持有当前状态) |
| 行为 | pay() / ship() / cancel() / complete() |
| 转换 | PaidState → ShippedState → CompletedState |
3.2 状态的 4 个核心角色
| 角色 | 职责 | 例子 |
|---|---|---|
| 状态接口(State) | 定义行为接口 | OrderState |
| 具体状态 A(Concrete State A) | 实现某状态行为 | PendingPayState |
| 具体状态 B(Concrete State B) | 实现某状态行为 | PaidState |
| 上下文(Context) | 持有当前状态,委托处理 | Order |
3.3 一张图看懂状态
Order(上下文)
│
│ 持有 currentState
│
├── PendingPayState(待支付)
│ ├── pay() → 转为 PaidState
│ └── cancel() → 转为 CancelledState
│
├── PaidState(已支付)
│ ├── ship() → 转为 ShippedState
│ └── refund() → 转为 RefundingState
│
└── ShippedState(已发货)
└── confirm() → 转为 CompletedState
3.4 状态的"灵魂三问"
Q1:状态 vs 策略,区别是什么?
答:
- 状态:自动转换(状态机驱动)
- 策略:手动选择(客户端选)
- 状态"行为随状态变",自动切换
- 策略"行为按场景选",手动选择
Q2:状态 vs 状态机,区别是什么?
答:
- 状态:设计模式(封装状态行为)
- 状态机:算法模型(定义状态转换规则)
- 状态模式实现状态机
- 状态机用状态模式
Q3:状态模式怎么和状态机结合?
答:
- 状态机定义规则:什么事件 → 什么状态
- 状态模式实现行为:每个状态做什么
- 结合:状态机负责"转换",状态模式负责"行为"
3.5 状态的 3 种实现方式
| 方式 | 特点 | 适用 |
|---|---|---|
| 状态接口 | 每个状态一个类 | 标准 |
| 状态机库 | Spring StateMachine | 复杂状态机 |
| 枚举 + switch | 简单 | 简单状态 |
大白商城主推状态接口方式。
四、实战代码:大白商城订单状态机完整实现
下面是大白商城生产环境在用的状态实现,全套代码可直接复制到 IDEA 跑。
4.1 项目环境与依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.dabai.mall</groupId>
<artifactId>mall-design-pattern-32</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>mall-design-pattern-32</name>
<description>大白商城 - 设计模式 32 状态模式</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 状态接口:OrderState
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
/**
* ✅ 状态接口:订单状态
*
* @author 大白商城技术团队
*/
public interface OrderState {
/**
* ✅ 支付
*/
void pay(Order order);
/**
* ✅ 发货
*/
void ship(Order order);
/**
* ✅ 取消
*/
void cancel(Order order);
/**
* ✅ 完成
*/
void complete(Order order);
/**
* ✅ 退款
*/
void refund(Order order);
/**
* ✅ 获取状态名
*/
String getStateName();
}
4.3 6 个具体状态
PendingPayState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:待支付
*/
@Slf4j
@Component
public class PendingPayState implements OrderState {
@Override
public void pay(Order order) {
log.info("【订单】待支付 → 支付, orderId={}", order.getOrderId());
// 1️⃣ 业务逻辑
// 2️⃣ 状态转换
order.setState(new PaidState());
}
@Override
public void ship(Order order) {
throw new IllegalStateException("待支付订单不能发货");
}
@Override
public void cancel(Order order) {
log.info("【订单】待支付 → 取消, orderId={}", order.getOrderId());
order.setState(new CancelledState());
}
@Override
public void complete(Order order) {
throw new IllegalStateException("待支付订单不能完成");
}
@Override
public void refund(Order order) {
throw new IllegalStateException("待支付订单不能退款");
}
@Override
public String getStateName() {
return "PENDING_PAY";
}
}
PaidState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:已支付
*/
@Slf4j
@Component
public class PaidState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("订单已支付,请勿重复支付");
}
@Override
public void ship(Order order) {
log.info("【订单】已支付 → 发货, orderId={}", order.getOrderId());
order.setState(new ShippedState());
}
@Override
public void cancel(Order order) {
log.info("【订单】已支付 → 取消(需退款), orderId={}", order.getOrderId());
order.setState(new RefundingState());
}
@Override
public void complete(Order order) {
throw new IllegalStateException("已支付订单不能直接完成");
}
@Override
public void refund(Order order) {
log.info("【订单】已支付 → 退款, orderId={}", order.getOrderId());
order.setState(new RefundingState());
}
@Override
public String getStateName() {
return "PAID";
}
}
ShippedState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:已发货
*/
@Slf4j
@Component
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 cancel(Order order) {
throw new IllegalStateException("已发货订单不能直接取消,需走退货流程");
}
@Override
public void complete(Order order) {
log.info("【订单】已发货 → 已完成, orderId={}", order.getOrderId());
order.setState(new CompletedState());
}
@Override
public void refund(Order order) {
log.info("【订单】已发货 → 退货退款, orderId={}", order.getOrderId());
order.setState(new RefundingState());
}
@Override
public String getStateName() {
return "SHIPPED";
}
}
CompletedState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:已完成
*/
@Slf4j
@Component
public class CompletedState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("已完成订单已支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("已完成订单已发货");
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("已完成订单不能取消");
}
@Override
public void complete(Order order) {
throw new IllegalStateException("订单已完成");
}
@Override
public void refund(Order order) {
log.info("【订单】已完成 → 售后退款, orderId={}", order.getOrderId());
order.setState(new RefundingState());
}
@Override
public String getStateName() {
return "COMPLETED";
}
}
CancelledState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:已取消
*/
@Slf4j
@Component
public class CancelledState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("已取消订单不能支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("已取消订单不能发货");
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("订单已取消");
}
@Override
public void complete(Order order) {
throw new IllegalStateException("已取消订单不能完成");
}
@Override
public void refund(Order order) {
throw new IllegalStateException("已取消订单无需退款");
}
@Override
public String getStateName() {
return "CANCELLED";
}
}
RefundingState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:退款中
*/
@Slf4j
@Component
public class RefundingState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("退款中订单不能支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("退款中订单不能发货");
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("退款中订单不能取消");
}
@Override
public void complete(Order order) {
throw new IllegalStateException("退款中订单不能完成");
}
@Override
public void refund(Order order) {
log.info("【订单】退款中 → 退款完成, orderId={}", order.getOrderId());
order.setState(new RefundedState());
}
@Override
public String getStateName() {
return "REFUNDING";
}
}
RefundedState:
package com.dabai.mall.order.state;
import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* ✅ 具体状态:已退款
*/
@Slf4j
@Component
public class RefundedState implements OrderState {
@Override
public void pay(Order order) {
throw new IllegalStateException("已退款订单不能支付");
}
@Override
public void ship(Order order) {
throw new IllegalStateException("已退款订单不能发货");
}
@Override
public void cancel(Order order) {
throw new IllegalStateException("已退款订单已取消");
}
@Override
public void complete(Order order) {
throw new IllegalStateException("已退款订单已结束");
}
@Override
public void refund(Order order) {
throw new IllegalStateException("订单已退款");
}
@Override
public String getStateName() {
return "REFUNDED";
}
}
4.4 上下文:Order
package com.dabai.mall.order;
import com.dabai.mall.order.state.OrderState;
import com.dabai.mall.order.state.PendingPayState;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
/**
* ✅ 上下文:订单
* <p>
* 持有当前状态,委托状态处理请求
*
* @author 大白商城技术团队
*/
@Slf4j
@Data
public class Order {
private String orderId;
private String userId;
private BigDecimal amount;
private String status;
/**
* ✅ 当前状态(持有状态对象)
*/
private OrderState state;
public Order() {
// 默认待支付
this.state = new PendingPayState();
}
/**
* ✅ 业务入口:委托给当前状态
*/
public void pay() {
log.info("【订单】当前状态: {}, 执行: pay", state.getStateName());
state.pay(this);
}
public void ship() {
log.info("【订单】当前状态: {}, 执行: ship", state.getStateName());
state.ship(this);
}
public void cancel() {
log.info("【订单】当前状态: {}, 执行: cancel", state.getStateName());
state.cancel(this);
}
public void complete() {
log.info("【订单】当前状态: {}, 执行: complete", state.getStateName());
state.complete(this);
}
public void refund() {
log.info("【订单】当前状态: {}, 执行: refund", state.getStateName());
state.refund(this);
}
}
4.5 业务调用方
package com.dabai.mall.order.service;
import com.dabai.mall.order.Order;
import org.springframework.stereotype.Service;
/**
* ✅ 订单服务:状态模式的调用方
* <p>
* 业务方只调 Order.pay(),不关心状态细节
*
* @author 大白商城技术团队
*/
@Service
public class OrderService {
public void pay(String orderId) {
Order order = loadOrder(orderId);
order.pay(); // ✅ 委托给当前状态
}
public void ship(String orderId) {
Order order = loadOrder(orderId);
order.ship();
}
public void cancel(String orderId) {
Order order = loadOrder(orderId);
order.cancel();
}
public void complete(String orderId) {
Order order = loadOrder(orderId);
order.complete();
}
public void refund(String orderId) {
Order order = loadOrder(orderId);
order.refund();
}
private Order loadOrder(String orderId) {
// 实际从 DB 加载
Order order = new Order();
order.setOrderId(orderId);
return order;
}
}
关键点:
| 关键点 | 解释 |
|---|---|
业务方只调 order.pay() | 不知道 6 种状态细节 |
| 加新状态? | 只加 1 个 State 类 |
| 改行为? | 只改 State 类 |
4.6 单元测试
package com.dabai.mall.order;
import com.dabai.mall.order.state.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* 状态模式完整单元测试
*/
class OrderStateTest {
@Test
void testPendingPayToPaid() {
Order order = new Order();
assertEquals("PENDING_PAY", order.getState().getStateName());
order.pay();
assertEquals("PAID", order.getState().getStateName());
}
@Test
void testPaidToShipped() {
Order order = new Order();
order.pay();
order.ship();
assertEquals("SHIPPED", order.getState().getStateName());
}
@Test
void testShippedToCompleted() {
Order order = new Order();
order.pay();
order.ship();
order.complete();
assertEquals("COMPLETED", order.getState().getStateName());
}
@Test
void testPendingPayToCancelled() {
Order order = new Order();
order.cancel();
assertEquals("CANCELLED", order.getState().getStateName());
}
@Test
void testPaidToRefunding() {
Order order = new Order();
order.pay();
order.refund();
assertEquals("REFUNDING", order.getState().getStateName());
}
@Test
void testInvalidTransition() {
// ✅ 待支付状态不能发货
Order order = new Order();
assertThrows(IllegalStateException.class, order::ship);
}
@Test
void testDuplicateAction() {
// ✅ 不能重复支付
Order order = new Order();
order.pay();
assertThrows(IllegalStateException.class, order::pay);
}
@Test
void testFullStateMachine() {
// ✅ 完整流程
Order order = new Order();
order.pay();
order.ship();
order.complete();
assertEquals("COMPLETED", order.getState().getStateName());
}
}
五、状态 vs 策略 / 状态机
5.1 一张表看清区别
| 模式 | 控制方 | 行为切换 | 适用 |
|---|---|---|---|
| 状态 | 状态机自动 | 自动 | 行为随状态变 |
| 策略 | 客户端手动 | 手动 | 多种算法可切换 |
| 状态机 | 算法模型 | 规则定义 | 状态转换规则 |
5.2 一个具体例子区分 3 个模式
大白商城:
| 模式 | 例子 |
|---|---|
| 状态 | 订单 6 种状态(行为随状态变) |
| 策略 | 支付 4 种方式(手动选择) |
| 状态机 | 订单状态转换规则 |
5.3 决策树
行为随状态变?
├── 是
│ ├── 状态之间有自动转换?
│ │ ├── 是 → ✅ 状态模式
│ │ └── 否 → 不用状态
└── 否
├── 多种算法可切换?
│ ├── 是 → ✅ 策略
│ └── 否 → 普通类
5.4 状态 vs 策略:易混淆
| 维度 | 状态 | 策略 |
|---|---|---|
| 控制方 | 状态机 | 客户端 |
| 行为切换 | 自动 | 手动 |
| 状态关系 | 互相转换 | 互相独立 |
| 改动点 | 状态类 | 策略类 |
大白商城选型:
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 订单状态 | 状态 | 状态自动转换 |
| 支付方式 | 策略 | 手动选择 |
| 退款流程 | 状态 | 状态机驱动 |
| 优惠规则 | 策略 | 互相独立 |
六、Spring StateMachine 怎么用?
6.1 Spring StateMachine 简介
Spring StateMachine 是 Spring 官方提供的状态机库:
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-core</artifactId>
</dependency>
6.2 状态机配置
/**
* ✅ Spring 状态机配置
*/
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig
extends StateMachineConfigurerAdapter<String, String> {
@Override
public void configure(StateMachineStateConfigurer<String, String> states)
throws Exception {
states.withStates()
.initial("PENDING_PAY")
.states(EnumSet.allOf(OrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
throws Exception {
transitions
.withExternal()
.source("PENDING_PAY").target("PAID").event("PAY")
.and()
.withExternal()
.source("PAID").target("SHIPPED").event("SHIP")
.and()
.withExternal()
.source("SHIPPED").target("COMPLETED").event("COMPLETE");
}
}
6.3 使用状态机
/**
* ✅ 使用 Spring 状态机
*/
@Service
public class OrderStateMachineService {
@Autowired
private StateMachine<String, String> stateMachine;
public void pay(String orderId) {
// 发事件,状态机自动转换
stateMachine.sendEvent(MessageBuilder.withPayload("PAY").build());
}
}
6.4 大白商城的"抄作业"
/**
* 借鉴 Spring StateMachine:自定义状态机
*/
public class SimpleStateMachine {
private final Map<String, Map<String, String>> transitions = new HashMap<>();
private String currentState;
public SimpleStateMachine(String initial) {
this.currentState = initial;
}
public SimpleStateMachine from(String from).to(String to).on(String event) {
transitions.computeIfAbsent(from, k -> new HashMap<>()).put(event, to);
return this;
}
public boolean fire(String event) {
Map<String, String> map = transitions.get(currentState);
if (map == null) return false;
String nextState = map.get(event);
if (nextState == null) return false;
currentState = nextState;
return true;
}
}
七、状态 + 观察者怎么组合?
7.1 组合 1:状态转换发事件
/**
* ✅ 状态 + 观察者:状态转换发事件
*/
@Component
public class PaidState implements OrderState {
@Autowired
private ApplicationEventPublisher eventPublisher;
@Override
public void ship(Order order) {
// ✅ 状态转换
order.setState(new ShippedState());
// ✅ 发事件
eventPublisher.publishEvent(
new OrderShippedEvent(this, order.getOrderId(), ...));
}
}
7.2 组合 2:状态内调观察者
/**
* ✅ 状态 + 观察者:状态变化时通知
*/
@Component
public abstract class AbstractOrderState implements OrderState {
@Autowired
protected List<OrderStateListener> listeners;
protected void notifyStateChange(Order order, String oldState, String newState) {
listeners.forEach(l -> l.onStateChange(order, oldState, newState));
}
}
7.3 组合 3:状态机驱动事件流
/**
* ✅ 状态机 + 观察者:完整流程
*/
@Component
public class OrderStateMachine {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void changeState(Order order, OrderEvent event) {
// 1️⃣ 状态转换
OrderState oldState = order.getState();
event.applyTo(order);
OrderState newState = order.getState();
// 2️⃣ 发事件
eventPublisher.publishEvent(new OrderStateChangedEvent(
this, order.getOrderId(),
oldState.getStateName(),
newState.getStateName()));
}
}
八、工程决策 Checklist
8.1 ✅ 这 5 种情况,强烈建议用状态模式
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 行为随状态变 | 订单/支付/物流 |
| ② | 加新状态不动老代码 | 符合开闭原则 |
| ③ | 状态之间自动转换 | 状态机驱动 |
| ④ | 消除 if-else | 业务清晰 |
| ⑤ | 状态可独立测试 | 单元测试友好 |
8.2 ❌ 这 5 种情况,绝对不要用状态模式
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 只有 1-2 个状态 | 过度设计 |
| ② | 状态之间无转换 | 用枚举 |
| ③ | 业务简单 | switch 够用 |
| ④ | 状态极少变 | 没必要 |
| ⑤ | 追求简单 | 类多 |
8.3 ⚠️ 状态模式的 6 大常见坑
| 序号 | 坑 | 表现 | 解决方案 |
|---|---|---|---|
| ① | 状态类过多 | 100 个 State | 抽象公共 |
| ② | 状态转换混乱 | 不该转的转了 | 状态机规则 |
| ③ | 状态可变性 | 状态对象被修改 | 不可变 |
| ④ | 状态未持久化 | 重启后丢失 | 状态序列化 |
| ⑤ | 并发问题 | 同一订单并发 | 锁 |
| ⑥ | 状态对象共享 | 多个订单共享 | 每个订单独立 |
8.4 面试官视角:状态高频追问
Q1:状态 vs 策略,区别是什么?
答:状态自动转换(状态机驱动),策略手动选择(客户端选)。状态"行为随状态变",策略"行为按场景选"。
Q2:状态模式和状态机什么关系?
答:状态机定义规则(什么事件转什么状态),状态模式实现行为(每个状态做什么)。状态机是模型,状态模式是实现。
Q3:Spring StateMachine 怎么用?
答:用
@EnableStateMachine+StateMachineConfigurerAdapter配置状态和转换,sendEvent()发事件触发转换。
九、与其他模式协作
9.1 状态 + 观察者 = 状态变化通知
9.2 状态 + 策略 = 状态内用策略
@Component
public class PaidState implements OrderState {
@Autowired
private PaymentContext paymentContext; // 策略
@Override
public void refund(Order order) {
// ✅ 用策略选择退款渠道
paymentContext.refund(order.getChannel(), ...);
order.setState(new RefundingState());
}
}
9.3 状态 + 工厂方法 = 状态工厂
/**
* ✅ 状态工厂:按状态名创建状态
*/
public class OrderStateFactory {
private static final Map<String, OrderState> STATES = new HashMap<>();
static {
STATES.put("PENDING_PAY", new PendingPayState());
STATES.put("PAID", new PaidState());
STATES.put("SHIPPED", new ShippedState());
}
public static OrderState getState(String name) {
return STATES.get(name);
}
}
9.4 大白商城模式协作全景图
┌──────────────┐
│ 状态 │ ← 本篇
└──────┬───────┘
│
┌───────────┬───────┼───────┬───────────┐
│ │ │ │ │
┌───▼───┐ ┌────▼───┐ ┌▼────┐ ┌▼─────┐ ┌───▼────┐
│策略 │ │观察者 │ │工厂 │ │单例 │ │ 模板 │
│(行为) │ │(通知) │ │(创建)│ │(状态)│ │ 方法 │
└───────┘ └────────┘ └──────┘ └──────┘ └───────┘
26 篇 30 篇 04 篇 02 篇 28 篇
十、本篇小结 + 下篇预告
10.1 本篇小结(5 个核心要点)
- 本质:状态 = 行为随状态变,加新状态只加 1 个 State 类。
- 场景:订单/支付/物流/账号/优惠券,行为随状态变。
- 对比:状态 vs 策略(自动 vs 手动)/ 状态 vs 状态机(实现 vs 模型)。
- Spring StateMachine:
@EnableStateMachine+ sendEvent() 触发。 - 避坑:状态类过多 / 转换混乱 / 状态可变 / 并发问题。
10.2 一句话总结
状态不是"换种方式写 if-else",是"用状态对象封装行为"。大白商城从 1500 行 if-else → 7 个 State 类——加新状态只加 1 个类,业务代码零改动。
10.3 知识脑图
状态模式
├── 2 个核心概念
│ ├── 状态(State)
│ └── 上下文(Context)
├── 4 大角色
│ ├── 状态接口(OrderState)
│ ├── 具体状态 A(PendingPayState)
│ ├── 具体状态 B(PaidState)
│ └── 上下文(Order)
├── 7 个具体状态
│ ├── PendingPayState(待支付)
│ ├── PaidState(已支付)
│ ├── ShippedState(已发货)
│ ├── CompletedState(已完成)
│ ├── CancelledState(已取消)
│ ├── RefundingState(退款中)
│ └── RefundedState(已退款)
├── 模式对比
│ ├── vs 策略(自动 vs 手动)
│ ├── vs 状态机(实现 vs 模型)
│ └── vs 枚举(行为 vs 常量)
├── Spring StateMachine
│ ├── @EnableStateMachine
│ ├── StateMachineConfigurerAdapter
│ └── sendEvent()
└── 模式协作
├── + 观察者(状态变化通知)
├── + 策略(状态内用策略)
└── + 工厂方法(状态工厂)
10.4 下篇预告
第 33 篇【状态模式 - 源码剖析篇】:JDK / Spring 中的状态实现
下一篇我们会深入源码,回答三个问题:
- JDK 的
Thread.State怎么用状态机? - Spring StateMachine 怎么实现状态转换?
- TCP 连接 / 业务流程怎么用状态机?
并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。
觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/horizon90/article/details/163994813




