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 种设计模式:从踩坑到精通》快速导航

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》《电商多平台电子面单对接实战》。技术相通,思路可鉴。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐