前言
在软件开发中,我们经常遇到这样的场景:一个对象的行为取决于其当前的状态,并且随着状态的改变,对象的行为也会发生相应的变化。比如订单从"待支付"到"已支付"再到"已发货",每种状态下能够执行的操作都不相同。
如果使用传统的if-else或switch-case来实现这种状态相关的逻辑,代码会变得冗长、难以维护,而且每当需要新增状态或修改状态转换规则时,都要修改大量代码。**状态模式(State Pattern)**正是为了解决这类问题而诞生的。
一、什么是状态模式
1.1 定义
状态模式是一种行为型设计模式,它允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
1.2 解决的核心问题
状态模式主要解决的是当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,如何将复杂的条件判断逻辑转化为清晰的状态管理。
1.3 实际应用场景
- 订单管理系统:订单状态的流转(待支付→已支付→已发货→已完成)
- 工作流引擎:文档审批流程(草稿→待审核→已审核→已发布)
- 游戏开发:角色状态(站立→行走→奔跑→跳跃)
- 线程管理:线程的生命周期(新建→就绪→运行→阻塞→终止)
- 设备控制:设备的开关机状态、工作模式等
二、状态模式的核心结构
状态模式包含四个核心角色,每个角色都有明确的职责:
2.1 角色划分
| 角色 | 职责描述 |
|---|---|
| 环境类(Context) | 定义客户感兴趣的接口,维护一个具体状态类的实例,将与状态相关的操作委托给当前状态对象处理 |
| 抽象状态类(State) | 定义所有具体状态的共同接口,声明状态处理方法 |
| 具体状态类(ConcreteState) | 实现抽象状态类定义的接口,处理环境类在该状态下的具体行为 |
| 客户端(Client) | 使用环境类,通过环境类来操作状态 |
2.2 类图结构
┌─────────────────┐
│ Context │
├─────────────────┤
│ - state: State │
├─────────────────┤
│ + setState() │
│ + request() │
└────────┬────────┘
│
│ depends on
│
┌────────┴────────┐
│ <<interface>> │
│ State │
├─────────────────┤
│ + handle() │
└────────┬────────┘
│
│ extends
│
┌────┴────┬──────────┐
▼ ▼ ▼
┌──────┐ ┌──────┐ ┌──────┐
│StateA│ │StateB│ │StateC│
└──────┘ └──────┘ └──────┘
三、实现步骤与代码示例
下面以订单状态流转为例,实现一个完整的状态模式示例。
3.1 场景说明
电商订单有以下状态流转:
- 待支付(PENDING):可以取消订单、支付
- 已支付(PAID):可以发货、退款
- 已发货(SHIPPED):可以确认收货、申请退货
- 已完成(COMPLETED):订单结束,可评价
- 已取消(CANCELLED):订单结束,无后续操作
3.2 代码实现
3.2.1 抽象状态类
java
/**
* 订单状态抽象类
* 定义所有状态共有的接口
*/
public abstract class OrderState {
protected OrderContext orderContext;
public OrderState(OrderContext orderContext) {
this.orderContext = orderContext;
}
// 支付操作
public abstract void pay();
// 发货操作
public abstract void ship();
// 确认收货操作
public abstract void confirmReceipt();
// 取消订单操作
public abstract void cancel();
// 获取当前状态名称
public abstract String getStateName();
}
3.2.2 环境类(Context)
java
/**
* 订单环境类
* 维护订单状态,对外提供操作接口
*/
public class OrderContext {
private String orderId;
private OrderState currentState;
private double amount;
public OrderContext(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
// 初始状态为待支付
this.currentState = new PendingState(this);
}
// 设置状态
public void setState(OrderState state) {
this.currentState = state;
System.out.println("订单 [" + orderId + "] 状态变更为: " + state.getStateName());
}
// 委托给具体状态类处理
public void pay() {
currentState.pay();
}
public void ship() {
currentState.ship();
}
public void confirmReceipt() {
currentState.confirmReceipt();
}
public void cancel() {
currentState.cancel();
}
// Getter方法
public String getOrderId() {
return orderId;
}
public double getAmount() {
return amount;
}
}
3.2.3 具体状态类
待支付状态:
java
/**
* 待支付状态
* 可以支付或取消订单
*/
public class PendingState extends OrderState {
public PendingState(OrderContext orderContext) {
super(orderContext);
}
@Override
public void pay() {
System.out.println("正在支付订单,金额: " + orderContext.getAmount() + "元");
// 状态流转:待支付 → 已支付
orderContext.setState(new PaidState(orderContext));
System.out.println("支付成功!");
}
@Override
public void ship() {
System.out.println("错误:待支付状态下不能发货!");
}
@Override
public void confirmReceipt() {
System.out.println("错误:待支付状态下不能确认收货!");
}
@Override
public void cancel() {
System.out.println("取消订单成功");
// 状态流转:待支付 → 已取消
orderContext.setState(new CancelledState(orderContext));
}
@Override
public String getStateName() {
return "待支付";
}
}
已支付状态:
java
/**
* 已支付状态
* 可以发货
*/
public class PaidState extends OrderState {
public PaidState(OrderContext orderContext) {
super(orderContext);
}
@Override
public void pay() {
System.out.println("错误:已支付状态下不能重复支付!");
}
@Override
public void ship() {
System.out.println("正在为您发货...");
// 状态流转:已支付 → 已发货
orderContext.setState(new ShippedState(orderContext));
System.out.println("发货成功!");
}
@Override
public void confirmReceipt() {
System.out.println("错误:已支付状态下不能确认收货!");
}
@Override
public void cancel() {
System.out.println("错误:已支付状态下不能直接取消,请先申请退款!");
}
@Override
public String getStateName() {
return "已支付";
}
}
已发货状态:
java
/**
* 已发货状态
* 可以确认收货
*/
public class ShippedState extends OrderState {
public ShippedState(OrderContext orderContext) {
super(orderContext);
}
@Override
public void pay() {
System.out.println("错误:已发货状态下不能支付!");
}
@Override
public void ship() {
System.out.println("错误:已发货状态下不能重复发货!");
}
@Override
public void confirmReceipt() {
System.out.println("感谢您确认收货!");
// 状态流转:已发货 → 已完成
orderContext.setState(new CompletedState(orderContext));
System.out.println("订单已完成");
}
@Override
public void cancel() {
System.out.println("错误:已发货状态下不能取消,请先申请退货!");
}
@Override
public String getStateName() {
return "已发货";
}
}
已完成状态:
java
/**
* 已完成状态
* 订单结束状态
*/
public class CompletedState extends OrderState {
public CompletedState(OrderContext orderContext) {
super(orderContext);
}
@Override
public void pay() {
System.out.println("错误:订单已完成,无法支付!");
}
@Override
public void ship() {
System.out.println("错误:订单已完成,无法发货!");
}
@Override
public void confirmReceipt() {
System.out.println("错误:订单已完成,不能重复确认收货!");
}
@Override
public void cancel() {
System.out.println("错误:订单已完成,无法取消!");
}
@Override
public String getStateName() {
return "已完成";
}
}
已取消状态:
java
/**
* 已取消状态
* 订单结束状态
*/
public class CancelledState extends OrderState {
public CancelledState(OrderContext orderContext) {
super(orderContext);
}
@Override
public void pay() {
System.out.println("错误:订单已取消,无法支付!");
}
@Override
public void ship() {
System.out.println("错误:订单已取消,无法发货!");
}
@Override
public void confirmReceipt() {
System.out.println("错误:订单已取消,无法确认收货!");
}
@Override
public void cancel() {
System.out.println("错误:订单已取消,不能重复取消!");
}
@Override
public String getStateName() {
return "已取消";
}
}
3.2.4 客户端测试
java
/**
* 状态模式测试客户端
*/
public class StatePatternDemo {
public static void main(String[] args) {
System.out.println("========== 订单状态流转演示 ==========\n");
// 创建订单
OrderContext order = new OrderContext("ORD20240112001", 299.00);
System.out.println();
// 测试1:正常流程
System.out.println("--- 测试正常流程 ---");
order.pay(); // 待支付 → 已支付
order.ship(); // 已支付 → 已发货
order.confirmReceipt(); // 已发货 → 已完成
System.out.println("\n--- 创建新订单测试异常操作 ---");
// 测试2:取消流程
OrderContext order2 = new OrderContext("ORD20240112002", 158.00);
order2.cancel(); // 待支付 → 已取消
order2.pay(); // 已取消状态下尝试支付(应该失败)
System.out.println("\n--- 测试状态非法操作 ---");
// 测试3:已支付状态下尝试取消
OrderContext order3 = new OrderContext("ORD20240112003", 599.00);
order3.pay();
order3.cancel(); // 已支付状态下取消(应该失败)
}
}
输出结果:
========== 订单状态流转演示 ==========
订单 [ORD20240112001] 状态变更为: 待支付
--- 测试正常流程 ---
正在支付订单,金额: 299.0元
订单 [ORD20240112001] 状态变更为: 已支付
支付成功!
正在为您发货...
订单 [ORD20240112001] 状态变更为: 已发货
发货成功!
感谢您确认收货!
订单 [ORD20240112001] 状态变更为: 已完成
订单已完成
--- 创建新订单测试异常操作 ---
订单 [ORD20240112002] 状态变更为: 待支付
取消订单成功
订单 [ORD20240112002] 状态变更为: 已取消
错误:订单已取消,无法支付!
--- 测试状态非法操作 ---
订单 [ORD20240112003] 状态变更为: 待支付
正在支付订单,金额: 599.0元
订单 [ORD20240112003] 状态变更为: 已支付
支付成功!
错误:已支付状态下不能直接取消,请先申请退款!
四、状态模式的优缺点分析
4.1 优势
| 优势 | 说明 |
|---|---|
| 逻辑分离 | 将不同状态的行为分散到不同的类中,消除了大量的条件判断语句,代码结构清晰 |
| 扩展性强 | 新增状态只需增加新的状态类,无需修改现有代码,符合开闭原则 |
| 状态转换集中 | 状态转换逻辑由状态类自己管理,避免了状态转换分散在各个方法中 |
| 易于维护 | 每个状态类的职责单一,修改某个状态的行为不会影响其他状态 |
| 符合单一职责 | 每个具体状态类只负责一种状态下的行为处理 |
4.2 潜在不足
| 缺点 | 说明 |
|---|---|
| 类数量增加 | 每个状态都需要一个独立的类,当状态数量较多时,类的数量会显著增加 |
| 状态间通信 | 某些状态可能需要访问其他状态的信息,状态之间的耦合度可能增加 |
| 过度设计风险 | 对于简单的状态逻辑,使用状态模式可能显得过于复杂 |
| 学习成本 | 相比传统的if-else实现,状态模式的理解和实现成本稍高 |
五、应用场景总结
5.1 适合使用状态模式的场景
-
订单管理系统
- 电商订单状态流转、退款申请处理、售后状态管理
-
工作流审批系统
- 文档审批流程、请假审批、报销审批等多级审批场景
-
游戏角色状态管理
- 角色的各种状态(站立、行走、奔跑、跳跃、攻击、受伤等)
- 技能冷却、增益/减益效果的状态管理
-
设备控制系统
- IoT设备的工作模式切换(待机、运行、维护、故障等)
- 自动售货机的状态管理(空闲、投币、出货、缺货等)
-
交通信号灯系统
- 红绿灯的状态切换(红灯→绿灯→黄灯→红灯)
- 铁路道口的状态管理
5.2 不适合使用状态模式的场景
- 状态非常简单(只有2-3个状态),且状态转换逻辑简单
- 状态很少变化,未来不太可能新增状态
- 一次性代码或临时性项目
六、与其他设计模式的对比
6.1 状态模式 vs 策略模式
| 对比维度 | 状态模式 | 策略模式 |
|---|---|---|
| 目的 | 管理对象的状态转换 | 定义算法族, interchangeable |
| 状态转换 | 状态类自己负责状态转换 | 客户端决定使用哪个策略 |
| 行为依赖 | 行为随内部状态改变 | 算法可独立变化 |
| 调用关系 | 状态类持有Context引用 | 策略类不持有Context引用 |
| 典型应用 | 订单流转、工作流 | 排序算法、支付方式 |
核心区别 :状态模式的状态是自动流转 的,由状态类决定下一个状态;而策略模式的策略选择是手动的,由客户端决定使用哪个策略。
6.2 状态模式 vs 观察者模式
| 对比维度 | 状态模式 | 观察者模式 |
|---|---|---|
| 关注点 | 对象内部状态和行为的变化 | 多个对象对主题变化的响应 |
| 解耦方向 | 解耦状态和行为 | 解耦事件发布和订阅 |
| 使用场景 | 单对象的状态管理 | 一对多的消息通知 |
6.3 状态模式 vs 责任链模式
两者都可以处理流程式的操作,但本质不同:
- 状态模式:每个状态处理该状态下的行为,状态之间有明确的转换关系
- 责任链模式:处理者按链式顺序处理请求,直到有处理者处理成功
七、最佳实践与注意事项
7.1 最佳实践
1. 合理设计状态粒度
java
// ❌ 过于细粒度:将"已支付待发货"拆分为多个状态
public class PaidWaitShippingState extends OrderState { }
public class PaidShippingPreparingState extends OrderState { }
// ✅ 合理粒度:合并为一个"已支付"状态
public class PaidState extends OrderState {
@Override
public void ship() {
// 在内部处理发货准备工作
System.out.println("准备发货...");
orderContext.setState(new ShippedState(orderContext));
}
}
2. 使用枚举管理状态常量
java
/**
* 订单状态枚举
*/
public enum OrderStatus {
PENDING("待支付"),
PAID("已支付"),
SHIPPED("已发货"),
COMPLETED("已完成"),
CANCELLED("已取消");
private String displayName;
OrderStatus(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}
3. 状态持久化
在数据库中记录订单状态时,建议使用状态枚举的name()或ordinal()值:
java
public class Order {
private String orderId;
private String status; // 存储状态名称,如 "PAID"
// ...
}
4. 状态机可视化
对于复杂的状态转换,建议使用状态机图进行可视化设计:
支付
取消
发货
确认收货
PENDING
PAID
CANCELLED
SHIPPED
COMPLETED
7.2 避坑指南
1. 避免状态循环引用
java
// ❌ 危险:状态之间直接相互持有引用
public class StateA extends OrderState {
private StateB stateB; // 避免这样
}
// ✅ 正确:通过Context管理状态
public class StateA extends OrderState {
@Override
public void handle() {
// 通过Context切换到下一个状态
orderContext.setState(new StateB(orderContext));
}
}
2. 考虑状态转换的原子性
java
@Override
public void pay() {
// ❌ 不安全:状态切换过程中可能被中断
orderContext.setState(new PaidState(orderContext));
updateDatabase(); // 如果这里失败,状态已切换但数据库未更新
// ✅ 安全:先处理业务逻辑,再切换状态
if (updateDatabase()) {
orderContext.setState(new PaidState(orderContext));
}
}
3. 状态转换的合法性检查
java
@Override
public void pay() {
// 检查状态转换是否合法
if (!canTransitionTo(PaidState.class)) {
throw new IllegalStateException("当前状态不允许支付");
}
// 执行业务逻辑
processPayment();
// 切换状态
orderContext.setState(new PaidState(orderContext));
}
4. 状态历史的记录
在某些业务场景下,需要记录状态变化历史:
java
public class OrderContext {
private List<StateHistory> stateHistory = new ArrayList<>();
public void setState(OrderState state) {
// 记录状态历史
stateHistory.add(new StateHistory(
LocalDateTime.now(),
this.currentState.getStateName(),
state.getStateName()
));
this.currentState = state;
}
// 状态历史记录类
private static class StateHistory {
private LocalDateTime changeTime;
private String fromState;
private String toState;
// getter/setter...
}
}
5. 结合Spring框架使用
在Spring项目中,可以将状态类注册为Bean:
java
@Component
public class PaidState extends OrderState {
@Autowired
private PaymentService paymentService;
@Override
public void pay() {
paymentService.processPayment(orderContext.getOrderId());
orderContext.setState(new ShippedState(orderContext));
}
}
7.3 进阶优化
1. 使用状态机框架
对于复杂的状态管理,可以考虑使用Spring State Machine、Squirrel State Machine等专业框架:
java
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderStatus.PENDING)
.states(EnumSet.allOf(OrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatus.PENDING).target(OrderStatus.PAID).event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.SHIPPED).event(OrderEvent.SHIP);
}
}
2. 状态模式与策略模式结合
某些场景下,可以将状态模式和策略模式结合使用,在一个状态下使用不同的策略:
java
public class PaidState extends OrderState {
private ShippingStrategy shippingStrategy;
public PaidState(OrderContext orderContext, ShippingStrategy shippingStrategy) {
super(orderContext);
this.shippingStrategy = shippingStrategy;
}
@Override
public void ship() {
// 使用策略模式处理不同发货方式
shippingStrategy.ship(orderContext);
orderContext.setState(new ShippedState(orderContext));
}
}
八、总结
状态模式是一种非常实用的设计模式,特别适合处理对象行为随状态变化的复杂业务场景。通过将状态逻辑分散到独立的状态类中,状态模式让代码结构更加清晰,维护成本显著降低。
核心要点回顾:
- ✅ 消除复杂的条件判断:用状态类替换if-else
- ✅ 符合开闭原则:新增状态无需修改现有代码
- ✅ 状态转换集中管理:每个状态类负责自己的转换逻辑
- ✅ 职责单一:每个状态类只关注一种状态下的行为
使用建议:
- 当状态数量 ≥ 3,且状态转换逻辑复杂时,优先考虑状态模式
- 简单场景(2-3个状态)使用枚举+简单判断即可
- 结合状态机框架处理超复杂的状态管理
- 注意状态持久化和状态历史的记录
掌握状态模式,能让你在面对复杂的状态管理需求时游刃有余,写出更加优雅、可维护的代码!