Java 23 种设计模式:从踩坑到精通 | 番外:状态模式 —— 订单状态流转实战
Java 23 种设计模式:从踩坑到精通 | 番外:状态模式 —— 订单状态流转实战
摘要:状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。它将每个状态封装为独立的类,将与该状态相关的所有行为集中管理,并由状态类自己决定何时切换到下一个状态。本文结合电商订单状态流转的场景,完整展示如何用状态模式消除
if-else状态判断,并与策略模式深度对比,帮你掌握“行为随状态变化”的设计精髓。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么订单状态判断的 if-else 一定会炸?
- 状态模式核心角色:抽象状态、具体状态、上下文
- 手写订单流转:待支付 → 已支付 → 已发货 → 已完成,自动切换
- 状态 vs 策略:谁来触发切换?
- 面试必问:“状态模式和策略模式有什么区别?JDK 线程状态用了哪个?”
📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:State 状态模式 —— if-else 满天飞?让状态自己决定行为 | 当前:番外 · 状态模式 × 订单状态流转
🔗 返回系列总目录
1. 订单状态流转的痛点
电商订单有多个状态:待支付、已支付、已发货、已完成。每个状态下用户能做的操作不同,状态之间的流转规则也各不相同。如果直接写:
void handle(Order order, String action) {
if ("待支付".equals(order.getStatus())) {
if ("支付".equals(action)) {
order.setStatus("已支付");
}
} else if ("已支付".equals(order.getStatus())) {
if ("发货".equals(action)) {
order.setStatus("已发货");
}
} else if ("已发货".equals(order.getStatus())) {
if ("签收".equals(action)) {
order.setStatus("已完成");
}
}
// ...
}
随着状态增多,if-else 会无限膨胀,新增一个状态(如“退款中”)需要修改所有相关判断。更麻烦的是,状态间的合法流转规则隐藏在分散的判断条件中,难以看清全貌,稍有不慎就会出现“待支付直接发货”这类非法流转。
状态模式的解决思路:将每个状态封装为独立的类,状态流转规则集中在状态类内部。上下文只需要把请求委派给当前状态对象,由状态对象自己决定下一步切换到哪个状态。
1.1 你的场景该不该用状态模式?
| 判断标准 | 是 → 用状态模式 | 否 → 用其他方式 |
|---|---|---|
| 对象的行为完全由内部状态决定 | ✅ | ❌ |
| 状态数量较多(≥3个),且可能继续增加 | ✅ | ❌ |
| 状态之间的转换规则复杂,需要显式管理 | ✅ | ❌ |
| 状态极少且固定(如只有开/关两种) | ❌ | 简单的 if-else 或枚举即可 |
2. 状态模式 UML(订单状态流转场景)

3. 完整源码实现
3.1 抽象状态接口 (State)
/**
* 抽象状态:定义订单状态行为的公共接口
*/
public interface State {
/**
* 处理当前状态的逻辑,并可能触发状态切换
*/
void handle(Context context);
}
💬 白话:所有状态都必须能“处理”——拿到上下文,执行当前状态该做的事,并在需要时切换到下一个状态。
3.2 具体状态A:待支付 (ConcreteStateA)
/**
* 具体状态A:待支付状态
*/
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("📦 订单状态:【待支付】");
System.out.println(" 💡 用户已完成付款,正在更新订单状态...");
// 处理完毕后,自动切换到"已支付"状态
context.setState(new ConcreteStateB());
}
}
💬 白话:待支付状态只做一件事——用户付完款,就把订单切换到“已发货”状态。状态自己决定下一步去哪,这是状态模式的核心。
3.3 具体状态B:已发货 (ConcreteStateB)
/**
* 具体状态B:已发货状态
*/
public class ConcreteStateB implements State {
@Override
public void handle(Context context) {
System.out.println("📦 订单状态:【已发货】");
System.out.println(" 🚚 物流已签收,订单即将完成...");
// 处理完毕后,自动切换到"已完成"状态
context.setState(new ConcreteStateC());
}
}
3.4 具体状态C:已完成 (ConcreteStateC)
/**
* 具体状态C:已完成状态(终态)
*/
public class ConcreteStateC implements State {
@Override
public void handle(Context context) {
System.out.println("📦 订单状态:【已完成】");
System.out.println(" ✅ 感谢购买,欢迎再次光临!");
// 终态,不再切换
}
}
💬 白话:完成状态是“终态”——处理完就不再切换到别的状态。多次调用
handle()都只会重复打印相同结果。
3.5 上下文:订单 (Context)
/**
* 上下文:订单类,维护当前状态实例
*/
public class Context {
private State state;
private String orderId;
public Context(String orderId, State initialState) {
this.orderId = orderId;
this.state = initialState;
}
/**
* 对外统一请求入口,委托给当前状态处理
*/
public void request() {
System.out.println("=== 处理订单:" + orderId + " ===");
state.handle(this);
System.out.println();
}
/**
* 由具体状态类调用,切换当前状态
*/
public void setState(State state) {
this.state = state;
}
public State getState() { return state; }
}
💬 白话:上下文就是一个“委托者”——它只知道当前状态是谁,把请求丢给它。切换状态也是由具体状态类调用
context.setState(),上下文自己从不主动判断状态。
3.6 客户端测试
public class Client {
public static void main(String[] args) {
// 创建订单,初始状态为"待支付"
Context order = new Context("20240723001", new ConcreteStateA());
order.request(); // 待支付 → 已发货
order.request(); // 已发货 → 已完成
order.request(); // 已完成(终态)
order.request(); // 已完成(终态,不再变化)
}
}
4. 运行结果
=== 处理订单:20240723001 ===
📦 订单状态:【待支付】
💡 用户已完成付款,正在更新订单状态...
=== 处理订单:20240723001 ===
📦 订单状态:【已发货】
🚚 物流已签收,订单即将完成...
=== 处理订单:20240723001 ===
📦 订单状态:【已完成】
✅ 感谢购买,欢迎再次光临!
=== 处理订单:20240723001 ===
📦 订单状态:【已完成】
✅ 感谢购买,欢迎再次光临!
5. 核心角色回顾
| 角色 | 职责 | 对应代码 |
|---|---|---|
| State | 定义状态行为的公共接口 | State |
| ConcreteState | 封装每种状态的行为,并决定状态切换 | ConcreteStateA/B/C |
| Context | 维护当前状态,委托请求,提供 setState() |
Context |
6. 状态模式 vs 策略模式
这是面试中极容易混淆的一对:
| 对比项 | 状态模式 | 策略模式 |
|---|---|---|
| 目的 | 行为随内部状态自动改变 | 算法可手动替换 |
| 谁触发切换 | 状态对象自己调用 context.setState() |
客户端显式调用 context.setStrategy() |
| 上下文感知 | 状态类持有并操作上下文引用 | 策略类通常不知道上下文的存在 |
| 典型应用 | 订单状态机、JDK 线程状态、TCP 连接状态 | Comparator、支付方式、促销策略 |
💡 一句话记忆:状态模式是“被动流转”——状态自己决定下一步;策略模式是“主动替换”——客户端选择用哪个算法。本例中每次
request()后状态自动流转到下一步,客户端只调用同一个方法,完全不知道内部状态在变。
7. 状态模式的优缺点
| 优点 | 缺点 |
|---|---|
消除庞大的状态判断 if-else |
每个状态一个类,类数量增加 |
| 状态流转规则集中管理,清晰可见 | 状态类之间需知道彼此,有一定耦合 |
| 新增状态只需加新类,符合开闭原则 | 简单状态机(2-3 个状态)用枚举更简洁 |
| 客户端无需知道状态切换细节 | 上下文与状态双向依赖 |
8. 六大设计原则体现
| 原则 | 体现 |
|---|---|
| 单一职责 | 每种状态只负责自身行为逻辑 |
| 开闭原则 | 新增状态(如“退款中”)无需修改上下文和已有状态 |
| 里氏替换 | 所有状态可替换抽象 State 接口 |
| 依赖倒置 | 上下文依赖抽象 State,不依赖具体状态 |
| 接口隔离 | State 只有 handle() 一个方法 |
| 迪米特法则 | 上下文只知道当前状态,不知状态内部如何切换 |
附 状态模式 UML源码(订单状态流转场景)
@startuml
title Java 23 种设计模式:从踩坑到精通
footer 折哥 | 智能物流与Java实战
' 1. 全局样式配置
skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam classBorderColor #333333
skinparam classFontColor #1A1A1A
skinparam classFontSize 14
skinparam noteFontSize 12
skinparam noteFontColor #555555
skinparam arrowColor #555555
skinparam classBackgroundColor #F9F9F9
skinparam interface {
BackgroundColor #E8F5E9
BorderColor #2E7D32
}
' 2. 抽象状态
interface State {
+ handle(Context)
}
note right of State
<b>抽象状态</b>
--
定义状态行为的公共接口
每个状态封装对应的行为逻辑
end note
' 3. 具体状态A
class ConcreteStateA implements State {
+ handle(Context)
}
note right of ConcreteStateA
<b>具体状态A</b>
--
实现状态A的行为
例如:待支付状态
处理后可切换到其他状态
end note
' 4. 具体状态B
class ConcreteStateB implements State {
+ handle(Context)
}
note right of ConcreteStateB
<b>具体状态B</b>
--
实现状态B的行为
例如:已发货状态
处理后可切换到其他状态
end note
' 5. 上下文
class Context {
- state : State
+ request()
+ setState(State)
}
note right of Context
<b>上下文</b>
--
维护当前状态实例
对外提供统一请求入口
状态切换由具体状态类决定
end note
' 6. 关系连线
Context o-down-> State : 持有
ConcreteStateA .up.> State : 实现
ConcreteStateB .up.> State : 实现
@enduml
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 正篇:State 状态模式 —— if-else 满天飞?让状态自己决定行为
- 当前:番外 · 状态模式 × 订单状态流转(你在这里)
- 创建型模式汇总
- 结构型模式汇总
- 行为型模式汇总
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》、《电商多平台电子面单对接实战》。技术相通,思路可鉴。
更多推荐





所有评论(0)