欢迎你来读这篇博客,这篇博客主要是关于状态模式。
其中包括状态模式的核心思想、适用场景、优缺点、与策略模式/责任链模式/状态机/枚举状态流转的区别,以及 Java 后端开发中订单状态流转的完整案例。
序言
在后端业务系统中,“状态”几乎无处不在。
比如订单系统:
1 2 3
| 待支付 -> 已支付 -> 已发货 -> 已完成 │ └── 已取消
|
再比如工单系统:
1
| 待处理 -> 处理中 -> 待审核 -> 已关闭
|
再比如审批系统:
1 2
| 草稿 -> 审批中 -> 审批通过 └── 审批拒绝
|
这些系统都有一个共同特点:
同一个对象,在不同状态下,对同一个操作会有不同表现。
比如订单的 pay() 操作:
- 待支付订单可以支付;
- 已支付订单不能重复支付;
- 已取消订单不能支付;
- 已完成订单不能支付。
如果直接写代码,很容易变成这样:
1 2 3 4 5 6 7 8 9
| if ("CREATED".equals(order.getStatus())) { } else if ("PAID".equals(order.getStatus())) { } else if ("CANCELLED".equals(order.getStatus())) { } else if ("COMPLETED".equals(order.getStatus())) { }
|
一个 pay() 方法这样写还能忍。
如果还有:
cancel()
ship()
confirmReceive()
refund()
close()
那每个方法里都会出现一堆状态判断。
最后代码会变成状态判断迷宫。
状态模式就是为了解决这类问题:
当对象的行为取决于它的状态,并且运行时状态会改变时,可以把不同状态下的行为封装到不同状态类中。
简单说:
状态模式就是把状态相关的 if else 拆成一个个状态对象。
状态不再只是一个字段,而是一个能决定行为的对象。
正文
chapter 1:什么是状态模式
状态模式,英文是 State Pattern,属于行为型设计模式。
它的定义是:
允许一个对象在其内部状态改变时改变它的行为,对象看起来好像修改了它所属的类。
这句话听起来有点绕,可以换成大白话:
一个对象处于不同状态时,同一个方法会表现出不同的行为。
例如订单对象:
1 2 3
| order.pay(); order.cancel(); order.ship();
|
如果订单处于不同状态,这些方法的行为就不一样。
状态模式的做法是:
把每一种状态封装成一个状态类,由状态类负责处理该状态下允许或禁止的行为。
例如:
1 2 3 4 5
| CreatedOrderState PaidOrderState ShippedOrderState CompletedOrderState CancelledOrderState
|
每个状态类只处理自己状态下的行为。
这样订单对象不需要写一堆 if else 判断当前状态。
chapter 2:状态模式的核心角色
状态模式一般包含三个角色:
- Context 上下文对象:持有当前状态,并把请求委托给当前状态对象处理。
- State 抽象状态:定义各状态共同支持的行为接口。
- ConcreteState 具体状态:实现某个状态下的具体行为。
结构如下:
1 2 3 4 5 6 7 8 9 10 11 12
| Client │ ▼ Context │ currentState ▼ State ▲ │ ├── ConcreteStateA ├── ConcreteStateB └── ConcreteStateC
|
在订单案例中:
| 状态模式角色 |
订单系统示例 |
| Context |
OrderContext / Order |
| State |
OrderState |
| ConcreteState |
CreatedState、PaidState、ShippedState、CompletedState、CancelledState |
订单对象持有当前状态:
1
| private OrderState state;
|
当调用:
订单对象会委托给当前状态:
真正决定能不能支付的是当前状态对象。
chapter 3:为什么需要状态模式
假设订单状态有 5 种:
1 2 3 4 5
| CREATED PAID SHIPPED COMPLETED CANCELLED
|
订单操作有 5 个:
1 2 3 4 5
| pay cancel ship confirmReceive refund
|
如果不用状态模式,代码可能会像这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void pay() { if (status == CREATED) { status = PAID; return; }
if (status == PAID) { throw new IllegalStateException("订单已支付,不能重复支付"); }
if (status == CANCELLED) { throw new IllegalStateException("订单已取消,不能支付"); }
if (status == SHIPPED || status == COMPLETED) { throw new IllegalStateException("当前状态不能支付"); } }
|
其他方法也类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public void cancel() { if (status == CREATED) { status = CANCELLED; return; }
if (status == PAID) { }
if (status == SHIPPED) { throw new IllegalStateException("已发货订单不能直接取消"); } }
|
状态越多,操作越多,判断就越复杂。
这种写法的问题是:
- 状态判断散落在多个方法里;
- 新增状态时要修改很多方法;
- 新增操作时要修改很多状态判断;
- 状态流转规则不清晰;
- 代码难测试;
- 业务规则容易漏;
- 方法越来越长。
状态模式把复杂度拆开:
1 2 3
| CreatedOrderState 只处理 CREATED 下的行为 PaidOrderState 只处理 PAID 下的行为 ShippedOrderState 只处理 SHIPPED 下的行为
|
每个状态类只关心自己。
chapter 4:状态模式解决的核心问题
状态模式主要解决的是:
对象行为随状态变化而变化,并且状态判断逻辑过于复杂的问题。
它特别适合以下情况:
- 对象有多个状态;
- 每个状态下支持的行为不同;
- 状态之间有明确流转关系;
- 操作中存在大量
if else 或 switch;
- 状态规则还可能继续扩展。
状态模式不是为了消灭所有 if else。
它是为了消灭那种状态维度上的巨大 if else。
如果只有两个状态、三个判断,直接写判断就好。
如果状态和操作都很多,那状态模式就很香。
chapter 5:不用状态模式会怎样
先看一个反例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class BadOrder {
private String status;
public void pay() { if ("CREATED".equals(status)) { status = "PAID"; System.out.println("订单支付成功"); return; }
if ("PAID".equals(status)) { throw new IllegalStateException("订单已支付,不能重复支付"); }
if ("CANCELLED".equals(status)) { throw new IllegalStateException("订单已取消,不能支付"); }
throw new IllegalStateException("当前状态不能支付,status = " + status); }
public void cancel() { if ("CREATED".equals(status)) { status = "CANCELLED"; System.out.println("订单取消成功"); return; }
if ("PAID".equals(status)) { throw new IllegalStateException("订单已支付,不能直接取消"); }
if ("SHIPPED".equals(status)) { throw new IllegalStateException("订单已发货,不能取消"); }
throw new IllegalStateException("当前状态不能取消,status = " + status); }
public void ship() { if ("PAID".equals(status)) { status = "SHIPPED"; System.out.println("订单发货成功"); return; }
throw new IllegalStateException("当前状态不能发货,status = " + status); } }
|
这只是三个操作。
如果订单系统继续扩展:
- 退款;
- 售后;
- 关闭;
- 超时取消;
- 部分发货;
- 部分退款;
- 待审核;
- 待风控;
- 待补款。
这个类会很快膨胀。
状态流转规则会被埋在一堆判断中。
这时候状态模式就很适合。
chapter 6:案例背景:订单状态流转
下面用一个 Java 后端常见案例来讲状态模式:订单状态流转。
订单支持以下状态:
1 2 3 4 5
| CREATED 待支付 PAID 已支付 SHIPPED 已发货 COMPLETED 已完成 CANCELLED 已取消
|
订单支持以下操作:
1 2 3 4 5
| pay 支付 cancel 取消 ship 发货 confirmReceive 确认收货 refund 退款
|
规则如下:
| 当前状态 |
支付 |
取消 |
发货 |
确认收货 |
退款 |
| CREATED |
可以,变为 PAID |
可以,变为 CANCELLED |
不可以 |
不可以 |
不可以 |
| PAID |
不可以 |
不建议直接取消 |
可以,变为 SHIPPED |
不可以 |
可以,变为 CANCELLED |
| SHIPPED |
不可以 |
不可以 |
不可以 |
可以,变为 COMPLETED |
可以,进入售后,这里简化为 CANCELLED |
| COMPLETED |
不可以 |
不可以 |
不可以 |
不可以 |
可按售后规则处理,这里不支持 |
| CANCELLED |
不可以 |
不可以 |
不可以 |
不可以 |
不可以 |
这个规则如果用 if else 写,会很丑。
我们用状态模式实现。
chapter 7:定义订单状态枚举
先定义状态枚举。
1 2 3 4 5 6 7 8 9 10 11 12
| public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
COMPLETED,
CANCELLED }
|
枚举只是状态标识。
真正行为交给状态类。
chapter 8:定义订单状态接口 OrderState
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface OrderState {
OrderStatus status();
void pay(OrderContext context);
void cancel(OrderContext context);
void ship(OrderContext context);
void confirmReceive(OrderContext context);
void refund(OrderContext context); }
|
每个状态都实现这几个操作。
可能有人会问:
某些状态不支持某些操作,为什么接口还要定义?
因为这些操作是订单整体对外暴露的行为。
不同状态下可以选择支持,也可以选择抛异常。
这样客户端只需要调用:
1 2 3
| order.pay(); order.cancel(); order.ship();
|
不用关心当前状态。
chapter 9:定义抽象状态类 AbstractOrderState
为了避免每个状态都重复写“不支持操作”,可以定义一个抽象状态类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public abstract class AbstractOrderState implements OrderState {
@Override public void pay(OrderContext context) { throw unsupported("支付"); }
@Override public void cancel(OrderContext context) { throw unsupported("取消"); }
@Override public void ship(OrderContext context) { throw unsupported("发货"); }
@Override public void confirmReceive(OrderContext context) { throw unsupported("确认收货"); }
@Override public void refund(OrderContext context) { throw unsupported("退款"); }
protected IllegalStateException unsupported(String action) { return new IllegalStateException("订单状态 " + status() + " 不支持操作:" + action); } }
|
具体状态只需要重写自己支持的操作。
这能减少很多重复代码。
chapter 10:定义订单上下文 OrderContext
订单上下文持有当前状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class OrderContext {
private final Long orderId;
private OrderState state;
public OrderContext(Long orderId, OrderState initialState) { this.orderId = orderId; this.state = initialState; }
public Long getOrderId() { return orderId; }
public OrderStatus getStatus() { return state.status(); }
public void changeState(OrderState newState) { System.out.println("订单状态变更,orderId = " + orderId + ",from = " + state.status() + ",to = " + newState.status());
this.state = newState; }
public void pay() { state.pay(this); }
public void cancel() { state.cancel(this); }
public void ship() { state.ship(this); }
public void confirmReceive() { state.confirmReceive(this); }
public void refund() { state.refund(this); } }
|
这里最关键的是:
1
| private OrderState state;
|
以及:
订单上下文不自己判断当前状态,而是把操作委托给当前状态对象。
状态对象可以通过:
1
| context.changeState(...)
|
修改上下文状态。
chapter 11:定义待支付状态 CreatedOrderState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CreatedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.CREATED; }
@Override public void pay(OrderContext context) { System.out.println("订单支付成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.PAID); }
@Override public void cancel(OrderContext context) { System.out.println("订单取消成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
待支付状态下:
- 支持支付;
- 支持取消;
- 不支持发货;
- 不支持确认收货;
- 不支持退款。
不支持的操作由 AbstractOrderState 默认抛异常。
chapter 12:定义已支付状态 PaidOrderState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class PaidOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.PAID; }
@Override public void ship(OrderContext context) { System.out.println("订单发货成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.SHIPPED); }
@Override public void refund(OrderContext context) { System.out.println("已支付订单退款成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
已支付状态下:
- 支持发货;
- 支持退款;
- 不支持重复支付;
- 不支持直接确认收货。
chapter 13:定义已发货状态 ShippedOrderState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ShippedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.SHIPPED; }
@Override public void confirmReceive(OrderContext context) { System.out.println("订单确认收货成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.COMPLETED); }
@Override public void refund(OrderContext context) { System.out.println("已发货订单发起售后退款,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
这里为了简化,把已发货退款后状态改为 CANCELLED。
真实项目中通常会进入售后状态:
1 2 3
| AFTER_SALE REFUNDING REFUNDED
|
chapter 14:定义已完成状态 CompletedOrderState
1 2 3 4 5 6 7
| public class CompletedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.COMPLETED; } }
|
已完成状态下所有操作默认不支持。
如果未来支持售后,可以重写 refund()。
chapter 15:定义已取消状态 CancelledOrderState
1 2 3 4 5 6 7
| public class CancelledOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.CANCELLED; } }
|
已取消状态是终态。
所有操作默认不支持。
chapter 16:定义状态对象集合 OrderStates
状态对象通常可以复用。
因为状态对象本身一般不保存订单个体数据。
订单个体数据在 OrderContext 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public final class OrderStates {
public static final OrderState CREATED = new CreatedOrderState();
public static final OrderState PAID = new PaidOrderState();
public static final OrderState SHIPPED = new ShippedOrderState();
public static final OrderState COMPLETED = new CompletedOrderState();
public static final OrderState CANCELLED = new CancelledOrderState();
private OrderStates() { }
public static OrderState of(OrderStatus status) { return switch (status) { case CREATED -> CREATED; case PAID -> PAID; case SHIPPED -> SHIPPED; case COMPLETED -> COMPLETED; case CANCELLED -> CANCELLED; }; } }
|
这里使用静态单例状态对象。
状态类不保存订单 ID、用户 ID、金额等数据。
所以可以安全复用。
chapter 17:客户端使用状态模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class StatePatternDemo {
public static void main(String[] args) { OrderContext order = new OrderContext( 10001L, OrderStates.CREATED );
System.out.println("当前状态:" + order.getStatus());
order.pay();
System.out.println("当前状态:" + order.getStatus());
order.ship();
System.out.println("当前状态:" + order.getStatus());
order.confirmReceive();
System.out.println("当前状态:" + order.getStatus());
try { order.cancel(); } catch (Exception e) { System.out.println("取消失败:" + e.getMessage()); } } }
|
输出类似:
1 2 3 4 5 6 7 8 9 10 11
| 当前状态:CREATED 订单支付成功,orderId = 10001 订单状态变更,orderId = 10001,from = CREATED,to = PAID 当前状态:PAID 订单发货成功,orderId = 10001 订单状态变更,orderId = 10001,from = PAID,to = SHIPPED 当前状态:SHIPPED 订单确认收货成功,orderId = 10001 订单状态变更,orderId = 10001,from = SHIPPED,to = COMPLETED 当前状态:COMPLETED 取消失败:订单状态 COMPLETED 不支持操作:取消
|
这个过程里没有复杂 if else。
不同状态下的行为由不同状态类控制。
chapter 18:状态模式的关键点
这个案例里有几个关键点。
1. Context 持有当前状态
1
| private OrderState state;
|
2. Context 把操作委托给状态对象
3. 状态对象决定行为
1 2 3
| public void pay(OrderContext context) { context.changeState(OrderStates.PAID); }
|
4. 状态对象可以切换 Context 状态
1
| context.changeState(...)
|
5. 不支持的行为默认抛异常
1
| throw unsupported("支付");
|
这样每个状态类只关心自己支持的动作。
chapter 19:状态模式和数据库状态字段
真实项目中,订单状态通常会保存在数据库中。
例如:
1 2 3 4 5
| order id status amount user_id
|
状态模式并不是不要数据库状态字段。
状态模式的作用是:
把数据库里的状态值映射成状态行为对象。
例如:
1 2 3 4 5
| OrderStatus status = orderEntity.getStatus();
OrderState state = OrderStates.of(status);
OrderContext context = new OrderContext(orderId, state);
|
处理完成后:
1 2
| orderEntity.setStatus(context.getStatus()); orderRepository.save(orderEntity);
|
也就是说:
- 数据库保存状态值;
- Java 状态对象封装状态行为;
- 状态流转后再保存新状态值。
chapter 20:Spring Boot 中落地状态模式
在 Spring Boot 项目中,可以把状态对象注册成 Bean。
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface OrderState {
OrderStatus status();
void pay(OrderStateContext context);
void cancel(OrderStateContext context);
void ship(OrderStateContext context);
void confirmReceive(OrderStateContext context);
void refund(OrderStateContext context); }
|
上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class OrderStateContext {
private final Long orderId;
private OrderStatus status;
public OrderStateContext(Long orderId, OrderStatus status) { this.orderId = orderId; this.status = status; }
public Long getOrderId() { return orderId; }
public OrderStatus getStatus() { return status; }
public void changeStatus(OrderStatus status) { this.status = status; } }
|
状态路由器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import org.springframework.stereotype.Component;
import java.util.List; import java.util.Map; import java.util.stream.Collectors;
@Component public class OrderStateRouter {
private final Map<OrderStatus, OrderState> stateMap;
public OrderStateRouter(List<OrderState> states) { this.stateMap = states.stream() .collect(Collectors.toUnmodifiableMap(OrderState::status, state -> state)); }
public OrderState getState(OrderStatus status) { OrderState state = stateMap.get(status);
if (state == null) { throw new IllegalArgumentException("Unsupported order status: " + status); }
return state; } }
|
订单应用服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional;
@Service public class OrderApplicationService {
private final OrderRepository orderRepository;
private final OrderStateRouter orderStateRouter;
public OrderApplicationService(OrderRepository orderRepository, OrderStateRouter orderStateRouter) { this.orderRepository = orderRepository; this.orderStateRouter = orderStateRouter; }
@Transactional(rollbackFor = Exception.class) public void pay(Long orderId) { OrderEntity order = orderRepository.findById(orderId) .orElseThrow(() -> new IllegalArgumentException("order not found"));
OrderStateContext context = new OrderStateContext( order.getId(), order.getStatus() );
OrderState state = orderStateRouter.getState(order.getStatus());
state.pay(context);
order.setStatus(context.getStatus());
orderRepository.save(order); } }
|
这个结构适合真实项目。
状态类可以注入其他服务,例如:
- 支付服务;
- 库存服务;
- 消息服务;
- 领域事件发布器。
chapter 21:Spring 状态类示例
待支付状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import org.springframework.stereotype.Component;
@Component public class CreatedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.CREATED; }
@Override public void pay(OrderStateContext context) { System.out.println("执行支付成功逻辑,orderId = " + context.getOrderId());
context.changeStatus(OrderStatus.PAID); }
@Override public void cancel(OrderStateContext context) { System.out.println("执行取消订单逻辑,orderId = " + context.getOrderId());
context.changeStatus(OrderStatus.CANCELLED); } }
|
已支付状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import org.springframework.stereotype.Component;
@Component public class PaidOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.PAID; }
@Override public void ship(OrderStateContext context) { System.out.println("执行发货逻辑,orderId = " + context.getOrderId());
context.changeStatus(OrderStatus.SHIPPED); }
@Override public void refund(OrderStateContext context) { System.out.println("执行退款逻辑,orderId = " + context.getOrderId());
context.changeStatus(OrderStatus.CANCELLED); } }
|
这样新增状态时,只需要新增一个状态 Bean。
chapter 22:状态模式和枚举状态机
有些项目会用枚举管理状态流转。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public enum OrderStatus {
CREATED { @Override public boolean canPay() { return true; } },
PAID { @Override public boolean canShip() { return true; } };
public boolean canPay() { return false; }
public boolean canShip() { return false; } }
|
这种方式适合简单状态规则。
但如果每个状态下的行为逻辑比较复杂,比如要调用多个服务、发事件、写日志,就不适合把逻辑全部塞进枚举。
状态模式更适合复杂行为。
| 对比项 |
枚举状态 |
状态模式 |
| 适合场景 |
简单状态判断 |
复杂状态行为 |
| 类数量 |
少 |
多 |
| 可注入 Spring Bean |
不方便 |
方便 |
| 行为复杂度 |
不宜太高 |
可以更复杂 |
| 扩展性 |
中等 |
较好 |
实践建议:
简单状态流转用枚举,复杂状态行为用状态模式。
chapter 23:状态模式和有限状态机
状态模式和有限状态机,FSM,关系很近。
有限状态机关注:
- 有哪些状态;
- 有哪些事件;
- 每个事件触发什么状态转移;
- 状态转移是否合法。
例如:
1 2 3 4
| CREATED --pay--> PAID PAID --ship--> SHIPPED SHIPPED --confirm--> COMPLETED CREATED --cancel--> CANCELLED
|
状态模式关注:
不同状态下对象行为如何变化。
有限状态机更偏“状态转移规则”。
状态模式更偏“状态对应的行为封装”。
在复杂业务中,可以结合使用:
- 状态机负责定义可流转关系;
- 状态模式负责实现状态下的行为。
如果状态非常复杂,可以考虑成熟状态机框架,例如 Spring Statemachine。
但如果只是普通订单状态,手写状态模式也够用。
chapter 24:状态模式和策略模式的区别
状态模式和策略模式非常像。
它们都会把行为封装到不同类中。
但意图不同。
| 对比项 |
状态模式 |
策略模式 |
| 核心目的 |
对象行为随状态变化 |
算法或策略可替换 |
| 是否有状态流转 |
通常有 |
通常没有 |
| Context 是否改变内部状态 |
是 |
不一定 |
| 状态/策略是否知道 Context |
状态通常会改变 Context 状态 |
策略通常只执行算法 |
| 示例 |
订单 CREATED/PAID/SHIPPED |
支付宝/微信支付策略 |
一句话区分:
策略模式是“我选择一种算法”。
状态模式是“我现在处于某个状态,所以行为不同,而且可能切换到下一个状态”。
例如:
这是策略模式。
根据订单当前状态决定能不能支付,这是状态模式。
chapter 25:状态模式和责任链模式的区别
责任链模式是请求沿着多个处理器传递。
状态模式是当前状态对象处理请求。
| 对比项 |
状态模式 |
责任链模式 |
| 关注点 |
对象当前状态决定行为 |
多个处理器依次处理请求 |
| 执行者数量 |
通常当前状态一个对象处理 |
链上可能多个处理器处理 |
| 是否强调状态转移 |
是 |
不一定 |
| 典型场景 |
订单状态流转 |
下单校验链、过滤器链 |
| 结构 |
Context -> State |
Handler -> Handler |
如果是:
适合状态模式。
如果是:
适合责任链模式。
chapter 26:状态模式和命令模式的区别
命令模式封装的是请求。
状态模式封装的是状态行为。
| 对比项 |
状态模式 |
命令模式 |
| 核心对象 |
State |
Command |
| 关注点 |
当前状态下如何响应操作 |
把操作请求封装成对象 |
| 是否有状态流转 |
是 |
不一定 |
| 是否支持队列/撤销 |
不是重点 |
是重点 |
| 示例 |
PaidOrderState.ship() |
ShipOrderCommand.execute() |
它们可以结合使用。
例如:
1 2 3
| ShipOrderCommand -> OrderApplicationService.ship(orderId) -> 当前状态对象处理 ship 行为
|
命令表示“请求发货”。
状态模式决定“当前订单能不能发货,以及发货后状态怎么变”。
chapter 27:状态模式和备忘录模式的区别
备忘录模式用于保存和恢复状态。
状态模式用于根据状态改变行为。
| 对比项 |
状态模式 |
备忘录模式 |
| 核心目的 |
状态决定行为 |
保存和恢复历史状态 |
| 关注点 |
当前状态行为 |
历史快照 |
| 是否用于回滚 |
不一定 |
是 |
| 示例 |
CREATED 状态可以 pay |
保存订单修改前快照 |
订单状态流转适合状态模式。
订单状态回滚到历史快照适合备忘录模式。
chapter 28:状态模式的优点
1. 消除大量状态判断
把状态相关的 if else 拆到不同状态类中。
2. 单一职责更清晰
每个状态类只负责当前状态下的行为。
3. 状态流转规则更清晰
状态之间如何切换,可以在状态类中体现。
4. 扩展新状态更方便
新增状态时新增状态类即可。
5. 更符合开闭原则
修改某个状态行为时,尽量只改对应状态类。
6. 方便单元测试
每个状态类可以单独测试。
chapter 29:状态模式的缺点
1. 类数量增加
每个状态通常对应一个类。
状态多时类会很多。
2. 状态流转分散
状态切换逻辑分布在各个状态类中。
如果没有文档或状态图,整体流转可能不直观。
3. 不适合简单状态
如果只有两个状态,直接 if else 更简单。
4. 状态对象设计不当会有线程安全问题
状态对象如果被复用,就不要保存订单个体数据。
5. 跨状态复杂规则仍然需要治理
比如“某些用户在某些时间不能支付”,这可能不是状态本身的问题,还需要规则、策略或责任链配合。
chapter 30:适用场景
状态模式适合以下场景。
1. 对象有多个状态
例如订单、工单、审批单、任务、支付单。
2. 不同状态下行为不同
同一个操作在不同状态下表现不同。
3. 状态之间存在明确流转
例如:
1
| CREATED -> PAID -> SHIPPED -> COMPLETED
|
4. 代码中存在大量状态判断
尤其是多个方法中都判断同一个状态字段。
5. 状态规则可能扩展
未来可能新增状态或调整某个状态下的行为。
chapter 31:不适合使用的场景
以下场景不建议使用状态模式。
1. 状态很少,逻辑很简单
两个状态、几个判断,不要过度设计。
2. 状态只是展示字段
如果状态只用于展示,不影响行为,不需要状态模式。
3. 状态流转全部由数据库配置
如果状态流转完全动态配置,可能更适合状态机引擎。
4. 状态行为没有明显差异
如果每个状态下行为差不多,状态模式价值不大。
5. 团队不熟悉这种建模方式
状态模式类较多,团队需要理解状态对象和上下文的关系。
chapter 32:真实项目中的实践建议
1. 状态对象不要保存业务实体个体数据
推荐:
1 2
| public class PaidOrderState implements OrderState { }
|
不推荐:
1 2 3 4 5
| public class PaidOrderState {
private Long orderId; private BigDecimal amount; }
|
订单数据应该在 Context 或 Entity 中。
状态对象最好无状态,可复用。
2. 状态切换要有日志
状态变化是重要业务行为。
建议记录:
1 2 3 4 5 6
| orderId fromStatus toStatus operator reason time
|
3. 数据库更新要做状态条件
真实项目中要避免并发状态覆盖。
例如:
1 2 3
| UPDATE orders SET status = 'PAID' WHERE id = ? AND status = 'CREATED'
|
不要只按 ID 更新状态。
否则并发下可能出现状态错乱。
4. 状态模式不替代事务
状态对象可以决定状态流转,但数据库事务、并发控制、幂等仍然要处理。
5. 复杂流转建议画状态图
状态多时,一定要画状态图。
不要让状态流转只存在代码里。
6. 状态和操作要分清
状态是名词:
操作是动词:
状态模式的接口通常就是这些操作。
7. 状态非法操作要明确报错
不要静默忽略。
例如:
1
| throw new IllegalStateException("订单状态 PAID 不支持重复支付");
|
比什么都不做更安全。
8. 可以和领域事件结合
状态切换成功后,可以发布事件:
1 2 3
| OrderPaidEvent OrderShippedEvent OrderCompletedEvent
|
状态模式负责流转,观察者模式负责后续通知。
9. 不要把所有业务规则都塞进状态类
状态类只处理状态相关行为。
风控、库存、优惠、权限等规则可以交给其他服务或责任链。
chapter 33:完整案例代码汇总
订单状态枚举
1 2 3 4 5 6 7 8 9 10 11 12
| public enum OrderStatus {
CREATED,
PAID,
SHIPPED,
COMPLETED,
CANCELLED }
|
状态接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface OrderState {
OrderStatus status();
void pay(OrderContext context);
void cancel(OrderContext context);
void ship(OrderContext context);
void confirmReceive(OrderContext context);
void refund(OrderContext context); }
|
抽象状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public abstract class AbstractOrderState implements OrderState {
@Override public void pay(OrderContext context) { throw unsupported("支付"); }
@Override public void cancel(OrderContext context) { throw unsupported("取消"); }
@Override public void ship(OrderContext context) { throw unsupported("发货"); }
@Override public void confirmReceive(OrderContext context) { throw unsupported("确认收货"); }
@Override public void refund(OrderContext context) { throw unsupported("退款"); }
protected IllegalStateException unsupported(String action) { return new IllegalStateException("订单状态 " + status() + " 不支持操作:" + action); } }
|
订单上下文
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| public class OrderContext {
private final Long orderId;
private OrderState state;
public OrderContext(Long orderId, OrderState initialState) { this.orderId = orderId; this.state = initialState; }
public Long getOrderId() { return orderId; }
public OrderStatus getStatus() { return state.status(); }
public void changeState(OrderState newState) { System.out.println("订单状态变更,orderId = " + orderId + ",from = " + state.status() + ",to = " + newState.status());
this.state = newState; }
public void pay() { state.pay(this); }
public void cancel() { state.cancel(this); }
public void ship() { state.ship(this); }
public void confirmReceive() { state.confirmReceive(this); }
public void refund() { state.refund(this); } }
|
待支付状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class CreatedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.CREATED; }
@Override public void pay(OrderContext context) { System.out.println("订单支付成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.PAID); }
@Override public void cancel(OrderContext context) { System.out.println("订单取消成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
已支付状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class PaidOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.PAID; }
@Override public void ship(OrderContext context) { System.out.println("订单发货成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.SHIPPED); }
@Override public void refund(OrderContext context) { System.out.println("已支付订单退款成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
已发货状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class ShippedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.SHIPPED; }
@Override public void confirmReceive(OrderContext context) { System.out.println("订单确认收货成功,orderId = " + context.getOrderId());
context.changeState(OrderStates.COMPLETED); }
@Override public void refund(OrderContext context) { System.out.println("已发货订单发起售后退款,orderId = " + context.getOrderId());
context.changeState(OrderStates.CANCELLED); } }
|
已完成状态
1 2 3 4 5 6 7
| public class CompletedOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.COMPLETED; } }
|
已取消状态
1 2 3 4 5 6 7
| public class CancelledOrderState extends AbstractOrderState {
@Override public OrderStatus status() { return OrderStatus.CANCELLED; } }
|
状态集合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| public final class OrderStates {
public static final OrderState CREATED = new CreatedOrderState();
public static final OrderState PAID = new PaidOrderState();
public static final OrderState SHIPPED = new ShippedOrderState();
public static final OrderState COMPLETED = new CompletedOrderState();
public static final OrderState CANCELLED = new CancelledOrderState();
private OrderStates() { }
public static OrderState of(OrderStatus status) { return switch (status) { case CREATED -> CREATED; case PAID -> PAID; case SHIPPED -> SHIPPED; case COMPLETED -> COMPLETED; case CANCELLED -> CANCELLED; }; } }
|
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class StatePatternDemo {
public static void main(String[] args) { OrderContext order = new OrderContext( 10001L, OrderStates.CREATED );
System.out.println("当前状态:" + order.getStatus());
order.pay();
System.out.println("当前状态:" + order.getStatus());
order.ship();
System.out.println("当前状态:" + order.getStatus());
order.confirmReceive();
System.out.println("当前状态:" + order.getStatus());
try { order.cancel(); } catch (Exception e) { System.out.println("取消失败:" + e.getMessage()); } } }
|
chapter 34:一句话总结
状态模式的本质是:
把对象在不同状态下的行为封装成不同状态类,让对象行为随着内部状态变化而变化。
它特别适合:
- 订单状态流转;
- 工单状态流转;
- 审批状态流转;
- 支付单状态;
- 任务生命周期;
- 工作流节点;
- 游戏角色状态;
- TCP 连接状态。
状态模式最重要的价值不是“多写几个状态类”,而是:
把复杂状态判断从大方法里拆出来,让每个状态自己负责自己的行为。
好的状态模式像一张清晰的状态图:当前在哪里,能做什么,下一步去哪,都很明确。
坏的状态模式像一堆散落的门禁卡:类很多,但谁能开哪扇门,没人说得清。
所以使用状态模式时,永远记住一句话:
状态模式适合状态复杂的对象,不适合为了消灭两个 if 而硬上设计模式。
参考资料
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software.
- Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices.
- Eric Evans. Domain-Driven Design: Tackling Complexity in the Heart of Software.
- Martin Fowler. Patterns of Enterprise Application Architecture.
- Spring Statemachine Documentation.
- Refactoring Guru: State Pattern.
- SourceMaking: State Design Pattern.
启示录
富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。