设计模式:状态模式

欢迎你来读这篇博客,这篇博客主要是关于状态模式
其中包括状态模式的核心思想、适用场景、优缺点、与策略模式/责任链模式/状态机/枚举状态流转的区别,以及 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:状态模式的核心角色

状态模式一般包含三个角色:

  1. Context 上下文对象:持有当前状态,并把请求委托给当前状态对象处理。
  2. State 抽象状态:定义各状态共同支持的行为接口。
  3. 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;

当调用:

1
order.pay();

订单对象会委托给当前状态:

1
state.pay(this);

真正决定能不能支付的是当前状态对象。

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:状态模式解决的核心问题

状态模式主要解决的是:

对象行为随状态变化而变化,并且状态判断逻辑过于复杂的问题。

它特别适合以下情况:

  1. 对象有多个状态;
  2. 每个状态下支持的行为不同;
  3. 状态之间有明确流转关系;
  4. 操作中存在大量 if elseswitch;
  5. 状态规则还可能继续扩展。

状态模式不是为了消灭所有 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
state.pay(this);

订单上下文不自己判断当前状态,而是把操作委托给当前状态对象。

状态对象可以通过:

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 把操作委托给状态对象

1
state.pay(this);

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 支付宝/微信支付策略

一句话区分:

策略模式是“我选择一种算法”。

状态模式是“我现在处于某个状态,所以行为不同,而且可能切换到下一个状态”。

例如:

1
paymentStrategy.pay();

这是策略模式。

1
order.pay();

根据订单当前状态决定能不能支付,这是状态模式。

chapter 25:状态模式和责任链模式的区别

责任链模式是请求沿着多个处理器传递。

状态模式是当前状态对象处理请求。

对比项 状态模式 责任链模式
关注点 对象当前状态决定行为 多个处理器依次处理请求
执行者数量 通常当前状态一个对象处理 链上可能多个处理器处理
是否强调状态转移 不一定
典型场景 订单状态流转 下单校验链、过滤器链
结构 Context -> State Handler -> Handler

如果是:

1
订单已支付才能发货

适合状态模式。

如果是:

1
参数校验 -> 库存校验 -> 风控校验

适合责任链模式。

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. 状态和操作要分清

状态是名词:

1
2
3
CREATED
PAID
SHIPPED

操作是动词:

1
2
3
pay
ship
cancel

状态模式的接口通常就是这些操作。

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.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:状态模式
https://allendericdalexander.github.io/2026/04/01/java/design/21state-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议