计算机小能手(AI+Java)头像
关注
大白话说Java设计模式-32-状态模式(业务实战篇)封面图

大白话说Java设计模式-32-状态模式(业务实战篇)

📌 PDF:AI人工智能 — AI Agent智能体实战项目

大白话说Java设计模式-32-状态模式(业务实战篇):大白商城订单状态机的"行为随状态变"

📌 一句话本质:状态模式就是"行为随状态变",把每个状态封装成类,状态之间自动转换。

🏷️ 标签:状态模式 / Java 设计模式 / 状态机 / 订单状态 / 大白商城   🎯 适合:初中级后端 / 经常被"状态逻辑"困扰的工程师


目录


一、业务场景引入:为什么需要"状态机"?

大白商城 2024 年订单中心遇到一个老大难:

“订单有 6 种状态(待支付/已支付/已发货/已完成/已取消/退款中),每种状态能做的操作不一样,if-else 写到 1500 行!”

我打开代码一看:

/**
 * ❌ 大白商城老的订单代码
 */
public class OrderServiceV0 {

    public void pay(String orderId) {
        Order order = orderDao.findById(orderId);
        if (order.getStatus().equals("PENDING_PAY")) {
            // 1️⃣ 校验
            // 2️⃣ 改状态
            order.setStatus("PAID");
            // 3️⃣ 发事件
            // 4️⃣ 通知
        } else if (order.getStatus().equals("PAID")) {
            throw new RuntimeException("已支付,请勿重复支付");
        } else if (order.getStatus().equals("SHIPPED")) {
            throw new RuntimeException("已发货,不能支付");
        }
        // ... 500 行 else if
    }

    public void ship(String orderId) {
        Order order = orderDao.findById(orderId);
        if (order.getStatus().equals("PAID")) {
            // 发货
        } else if (order.getStatus().equals("PENDING_PAY")) {
            throw new RuntimeException("未支付,不能发货");
        }
        // ... 500 行
    }

    public void cancel(String orderId) {
        Order order = orderDao.findById(orderId);
        if (order.getStatus().equals("PENDING_PAY")) {
            // 取消
        } else if (order.getStatus().equals("PAID")) {
            // 退款 + 取消
        } else if (order.getStatus().equals("SHIPPED")) {
            // 拒绝取消
        }
        // ... 500 行
    }
}

这种代码的痛点

序号问题后果
1500 行 if-else难维护
加新状态要改 N 个方法改一处忘一处
状态转换容易漏Bug 频出
业务状态被淹没难以理解

老板要的"加新状态只加 1 个类",怎么搞?

答案就是——状态模式

1.1 大白话讲透状态

继续打比方:

场景:订单有 6 种状态,每种状态下能做的操作不一样。

  • 错误做法:每个方法(pay/ship/cancel)都写 if-else 判断状态
  • 正确做法
    1. 把每个状态封装成类PendingPayState / PaidState / ShippedState / …
    2. 每个状态类只关心自己的行为
    3. 状态之间自动转换
    4. 加新状态?只加 1 个 State 类

状态模式 = 对象行为随状态变化,把每个状态封装成类

1.2 状态模式的 3 个真实场景

大白商城里,状态模式用在:

场景“状态”
订单状态待支付/已支付/已发货/已完成/已取消/退款中
支付状态待支付/支付中/已支付/已退款/支付失败
物流状态待发货/已发货/运输中/已签收/已退货
账号状态未激活/已激活/已冻结/已注销
优惠券状态未使用/已使用/已过期/已作废

任何"行为随状态变"的场景,都用状态模式


二、反面教材:if-else 堆状态的"灾难现场"

我们看 4 个反面教材,看它们是怎么一步步崩的。

2.1 反面教材 v1:if-else 堆叠

/**
 * ❌ 反面教材 v1:if-else 堆叠
 */
public void pay() {
    if (status.equals("PENDING_PAY")) { ... }
    else if (status.equals("PAID")) { ... }
    // 100 个 else if
}

翻车现场

序号问题后果
1500 行 if-else难维护
加状态要改所有方法改 N 处

2.2 反面教材 v2:switch-case

/**
 * ❌ 反面教材 v2:switch-case
 */
public void pay() {
    switch (status) {
        case "PENDING_PAY": ...; break;
        case "PAID": ...; break;
        // 100 个 case
    }
}

翻车现场

序号问题后果
业务逻辑分散难追踪
case 字符串易错编译期不查

2.3 反面教材 v3:状态判断分散

/**
 * ❌ 反面教材 v3:状态判断分散
 */
public void pay() { /* 状态判断 */ }
public void ship() { /* 状态判断 */ }
public void cancel() { /* 状态判断 */ }
// 100 个方法都做状态判断

翻车现场

序号问题后果
状态判断重复改状态要改 N 个方法
无法复用业务逻辑重复

2.4 反面教材 v4:完全没抽象

/**
 * ❌ 反面教材 v4:完全没抽象
 */
public class OrderServiceV4 {
    public void process() {
        // 1000 行所有逻辑混在一起
    }
}

翻车现场

序号问题后果
无法复用改一处牵动全身
无法测试单元测试跑不动

2.5 4 个反面教材的共同病根

痛点反模式方案能不能解决?
加状态不动老代码❌ 全部要改
状态封装❌ 全部分散
状态转换清晰❌ 全部混乱
行为随状态变❌ 全部要写 if

必须上状态模式


三、模式原理:状态的"状态对象 + 转换"

3.1 状态的 2 个核心概念

状态(State)

  • 封装对象在某个状态下的行为
  • 不同状态的行为不同
  • 状态之间可以转换

上下文(Context)

  • 持有当前状态
  • 委托状态处理请求
  • 状态转换时通知

大白商城例子

概念例子
状态PendingPayState / PaidState / ShippedState
上下文Order(持有当前状态)
行为pay() / ship() / cancel() / complete()
转换PaidStateShippedStateCompletedState

3.2 状态的 4 个核心角色

角色职责例子
状态接口(State)定义行为接口OrderState
具体状态 A(Concrete State A)实现某状态行为PendingPayState
具体状态 B(Concrete State B)实现某状态行为PaidState
上下文(Context)持有当前状态,委托处理Order

3.3 一张图看懂状态

Order(上下文)
  │
  │ 持有 currentState
  │
  ├── PendingPayState(待支付)
  │     ├── pay() → 转为 PaidState
  │     └── cancel() → 转为 CancelledState
  │
  ├── PaidState(已支付)
  │     ├── ship() → 转为 ShippedState
  │     └── refund() → 转为 RefundingState
  │
  └── ShippedState(已发货)
        └── confirm() → 转为 CompletedState

3.4 状态的"灵魂三问"

Q1:状态 vs 策略,区别是什么?

答:

  • 状态自动转换(状态机驱动)
  • 策略手动选择(客户端选)
  • 状态"行为随状态变",自动切换
  • 策略"行为按场景选",手动选择

Q2:状态 vs 状态机,区别是什么?

答:

  • 状态设计模式(封装状态行为)
  • 状态机算法模型(定义状态转换规则)
  • 状态模式实现状态机
  • 状态机状态模式

Q3:状态模式怎么和状态机结合?

答:

  • 状态机定义规则:什么事件 → 什么状态
  • 状态模式实现行为:每个状态做什么
  • 结合:状态机负责"转换",状态模式负责"行为"

3.5 状态的 3 种实现方式

方式特点适用
状态接口每个状态一个类标准
状态机库Spring StateMachine复杂状态机
枚举 + switch简单简单状态

大白商城主推状态接口方式


四、实战代码:大白商城订单状态机完整实现

下面是大白商城生产环境在用的状态实现,全套代码可直接复制到 IDEA 跑

4.1 项目环境与依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.dabai.mall</groupId>
    <artifactId>mall-design-pattern-32</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>mall-design-pattern-32</name>
    <description>大白商城 - 设计模式 32 状态模式</description>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.2 状态接口:OrderState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;

/**
 * ✅ 状态接口:订单状态
 *
 * @author 大白商城技术团队
 */
public interface OrderState {

    /**
     * ✅ 支付
     */
    void pay(Order order);

    /**
     * ✅ 发货
     */
    void ship(Order order);

    /**
     * ✅ 取消
     */
    void cancel(Order order);

    /**
     * ✅ 完成
     */
    void complete(Order order);

    /**
     * ✅ 退款
     */
    void refund(Order order);

    /**
     * ✅ 获取状态名
     */
    String getStateName();
}

4.3 6 个具体状态

PendingPayState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:待支付
 */
@Slf4j
@Component
public class PendingPayState implements OrderState {

    @Override
    public void pay(Order order) {
        log.info("【订单】待支付 → 支付, orderId={}", order.getOrderId());
        // 1️⃣ 业务逻辑
        // 2️⃣ 状态转换
        order.setState(new PaidState());
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("待支付订单不能发货");
    }

    @Override
    public void cancel(Order order) {
        log.info("【订单】待支付 → 取消, orderId={}", order.getOrderId());
        order.setState(new CancelledState());
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("待支付订单不能完成");
    }

    @Override
    public void refund(Order order) {
        throw new IllegalStateException("待支付订单不能退款");
    }

    @Override
    public String getStateName() {
        return "PENDING_PAY";
    }
}

PaidState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:已支付
 */
@Slf4j
@Component
public class PaidState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("订单已支付,请勿重复支付");
    }

    @Override
    public void ship(Order order) {
        log.info("【订单】已支付 → 发货, orderId={}", order.getOrderId());
        order.setState(new ShippedState());
    }

    @Override
    public void cancel(Order order) {
        log.info("【订单】已支付 → 取消(需退款), orderId={}", order.getOrderId());
        order.setState(new RefundingState());
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("已支付订单不能直接完成");
    }

    @Override
    public void refund(Order order) {
        log.info("【订单】已支付 → 退款, orderId={}", order.getOrderId());
        order.setState(new RefundingState());
    }

    @Override
    public String getStateName() {
        return "PAID";
    }
}

ShippedState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:已发货
 */
@Slf4j
@Component
public class ShippedState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("已发货订单已支付");
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("订单已发货");
    }

    @Override
    public void cancel(Order order) {
        throw new IllegalStateException("已发货订单不能直接取消,需走退货流程");
    }

    @Override
    public void complete(Order order) {
        log.info("【订单】已发货 → 已完成, orderId={}", order.getOrderId());
        order.setState(new CompletedState());
    }

    @Override
    public void refund(Order order) {
        log.info("【订单】已发货 → 退货退款, orderId={}", order.getOrderId());
        order.setState(new RefundingState());
    }

    @Override
    public String getStateName() {
        return "SHIPPED";
    }
}

CompletedState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:已完成
 */
@Slf4j
@Component
public class CompletedState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("已完成订单已支付");
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("已完成订单已发货");
    }

    @Override
    public void cancel(Order order) {
        throw new IllegalStateException("已完成订单不能取消");
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("订单已完成");
    }

    @Override
    public void refund(Order order) {
        log.info("【订单】已完成 → 售后退款, orderId={}", order.getOrderId());
        order.setState(new RefundingState());
    }

    @Override
    public String getStateName() {
        return "COMPLETED";
    }
}

CancelledState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:已取消
 */
@Slf4j
@Component
public class CancelledState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("已取消订单不能支付");
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("已取消订单不能发货");
    }

    @Override
    public void cancel(Order order) {
        throw new IllegalStateException("订单已取消");
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("已取消订单不能完成");
    }

    @Override
    public void refund(Order order) {
        throw new IllegalStateException("已取消订单无需退款");
    }

    @Override
    public String getStateName() {
        return "CANCELLED";
    }
}

RefundingState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:退款中
 */
@Slf4j
@Component
public class RefundingState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("退款中订单不能支付");
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("退款中订单不能发货");
    }

    @Override
    public void cancel(Order order) {
        throw new IllegalStateException("退款中订单不能取消");
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("退款中订单不能完成");
    }

    @Override
    public void refund(Order order) {
        log.info("【订单】退款中 → 退款完成, orderId={}", order.getOrderId());
        order.setState(new RefundedState());
    }

    @Override
    public String getStateName() {
        return "REFUNDING";
    }
}

RefundedState

package com.dabai.mall.order.state;

import com.dabai.mall.order.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * ✅ 具体状态:已退款
 */
@Slf4j
@Component
public class RefundedState implements OrderState {

    @Override
    public void pay(Order order) {
        throw new IllegalStateException("已退款订单不能支付");
    }

    @Override
    public void ship(Order order) {
        throw new IllegalStateException("已退款订单不能发货");
    }

    @Override
    public void cancel(Order order) {
        throw new IllegalStateException("已退款订单已取消");
    }

    @Override
    public void complete(Order order) {
        throw new IllegalStateException("已退款订单已结束");
    }

    @Override
    public void refund(Order order) {
        throw new IllegalStateException("订单已退款");
    }

    @Override
    public String getStateName() {
        return "REFUNDED";
    }
}

4.4 上下文:Order

package com.dabai.mall.order;

import com.dabai.mall.order.state.OrderState;
import com.dabai.mall.order.state.PendingPayState;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.math.BigDecimal;

/**
 * ✅ 上下文:订单
 * <p>
 * 持有当前状态,委托状态处理请求
 *
 * @author 大白商城技术团队
 */
@Slf4j
@Data
public class Order {

    private String orderId;
    private String userId;
    private BigDecimal amount;
    private String status;

    /**
     * ✅ 当前状态(持有状态对象)
     */
    private OrderState state;

    public Order() {
        // 默认待支付
        this.state = new PendingPayState();
    }

    /**
     * ✅ 业务入口:委托给当前状态
     */
    public void pay() {
        log.info("【订单】当前状态: {}, 执行: pay", state.getStateName());
        state.pay(this);
    }

    public void ship() {
        log.info("【订单】当前状态: {}, 执行: ship", state.getStateName());
        state.ship(this);
    }

    public void cancel() {
        log.info("【订单】当前状态: {}, 执行: cancel", state.getStateName());
        state.cancel(this);
    }

    public void complete() {
        log.info("【订单】当前状态: {}, 执行: complete", state.getStateName());
        state.complete(this);
    }

    public void refund() {
        log.info("【订单】当前状态: {}, 执行: refund", state.getStateName());
        state.refund(this);
    }
}

4.5 业务调用方

package com.dabai.mall.order.service;

import com.dabai.mall.order.Order;
import org.springframework.stereotype.Service;

/**
 * ✅ 订单服务:状态模式的调用方
 * <p>
 * 业务方只调 Order.pay(),不关心状态细节
 *
 * @author 大白商城技术团队
 */
@Service
public class OrderService {

    public void pay(String orderId) {
        Order order = loadOrder(orderId);
        order.pay();  // ✅ 委托给当前状态
    }

    public void ship(String orderId) {
        Order order = loadOrder(orderId);
        order.ship();
    }

    public void cancel(String orderId) {
        Order order = loadOrder(orderId);
        order.cancel();
    }

    public void complete(String orderId) {
        Order order = loadOrder(orderId);
        order.complete();
    }

    public void refund(String orderId) {
        Order order = loadOrder(orderId);
        order.refund();
    }

    private Order loadOrder(String orderId) {
        // 实际从 DB 加载
        Order order = new Order();
        order.setOrderId(orderId);
        return order;
    }
}

关键点

关键点解释
业务方只调 order.pay()不知道 6 种状态细节
加新状态?只加 1 个 State 类
改行为?只改 State 类

4.6 单元测试

package com.dabai.mall.order;

import com.dabai.mall.order.state.*;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 状态模式完整单元测试
 */
class OrderStateTest {

    @Test
    void testPendingPayToPaid() {
        Order order = new Order();
        assertEquals("PENDING_PAY", order.getState().getStateName());

        order.pay();
        assertEquals("PAID", order.getState().getStateName());
    }

    @Test
    void testPaidToShipped() {
        Order order = new Order();
        order.pay();
        order.ship();
        assertEquals("SHIPPED", order.getState().getStateName());
    }

    @Test
    void testShippedToCompleted() {
        Order order = new Order();
        order.pay();
        order.ship();
        order.complete();
        assertEquals("COMPLETED", order.getState().getStateName());
    }

    @Test
    void testPendingPayToCancelled() {
        Order order = new Order();
        order.cancel();
        assertEquals("CANCELLED", order.getState().getStateName());
    }

    @Test
    void testPaidToRefunding() {
        Order order = new Order();
        order.pay();
        order.refund();
        assertEquals("REFUNDING", order.getState().getStateName());
    }

    @Test
    void testInvalidTransition() {
        // ✅ 待支付状态不能发货
        Order order = new Order();
        assertThrows(IllegalStateException.class, order::ship);
    }

    @Test
    void testDuplicateAction() {
        // ✅ 不能重复支付
        Order order = new Order();
        order.pay();
        assertThrows(IllegalStateException.class, order::pay);
    }

    @Test
    void testFullStateMachine() {
        // ✅ 完整流程
        Order order = new Order();
        order.pay();
        order.ship();
        order.complete();
        assertEquals("COMPLETED", order.getState().getStateName());
    }
}

五、状态 vs 策略 / 状态机

5.1 一张表看清区别

模式控制方行为切换适用
状态状态机自动自动行为随状态变
策略客户端手动手动多种算法可切换
状态机算法模型规则定义状态转换规则

5.2 一个具体例子区分 3 个模式

大白商城

模式例子
状态订单 6 种状态(行为随状态变)
策略支付 4 种方式(手动选择)
状态机订单状态转换规则

5.3 决策树

行为随状态变?
├── 是
│   ├── 状态之间有自动转换?
│   │   ├── 是 → ✅ 状态模式
│   │   └── 否 → 不用状态
└── 否
    ├── 多种算法可切换?
    │   ├── 是 → ✅ 策略
    │   └── 否 → 普通类

5.4 状态 vs 策略:易混淆

维度状态策略
控制方状态机客户端
行为切换自动手动
状态关系互相转换互相独立
改动点状态类策略类

大白商城选型

场景选哪个原因
订单状态状态状态自动转换
支付方式策略手动选择
退款流程状态状态机驱动
优惠规则策略互相独立

六、Spring StateMachine 怎么用?

6.1 Spring StateMachine 简介

Spring StateMachine 是 Spring 官方提供的状态机库:

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
</dependency>

6.2 状态机配置

/**
 * ✅ Spring 状态机配置
 */
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig 
        extends StateMachineConfigurerAdapter<String, String> {

    @Override
    public void configure(StateMachineStateConfigurer<String, String> states) 
            throws Exception {
        states.withStates()
                .initial("PENDING_PAY")
                .states(EnumSet.allOf(OrderStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<String, String> transitions) 
            throws Exception {
        transitions
            .withExternal()
                .source("PENDING_PAY").target("PAID").event("PAY")
            .and()
            .withExternal()
                .source("PAID").target("SHIPPED").event("SHIP")
            .and()
            .withExternal()
                .source("SHIPPED").target("COMPLETED").event("COMPLETE");
    }
}

6.3 使用状态机

/**
 * ✅ 使用 Spring 状态机
 */
@Service
public class OrderStateMachineService {

    @Autowired
    private StateMachine<String, String> stateMachine;

    public void pay(String orderId) {
        // 发事件,状态机自动转换
        stateMachine.sendEvent(MessageBuilder.withPayload("PAY").build());
    }
}

6.4 大白商城的"抄作业"

/**
 * 借鉴 Spring StateMachine:自定义状态机
 */
public class SimpleStateMachine {

    private final Map<String, Map<String, String>> transitions = new HashMap<>();
    private String currentState;

    public SimpleStateMachine(String initial) {
        this.currentState = initial;
    }

    public SimpleStateMachine from(String from).to(String to).on(String event) {
        transitions.computeIfAbsent(from, k -> new HashMap<>()).put(event, to);
        return this;
    }

    public boolean fire(String event) {
        Map<String, String> map = transitions.get(currentState);
        if (map == null) return false;
        String nextState = map.get(event);
        if (nextState == null) return false;
        currentState = nextState;
        return true;
    }
}

七、状态 + 观察者怎么组合?

7.1 组合 1:状态转换发事件

/**
 * ✅ 状态 + 观察者:状态转换发事件
 */
@Component
public class PaidState implements OrderState {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Override
    public void ship(Order order) {
        // ✅ 状态转换
        order.setState(new ShippedState());
        // ✅ 发事件
        eventPublisher.publishEvent(
            new OrderShippedEvent(this, order.getOrderId(), ...));
    }
}

7.2 组合 2:状态内调观察者

/**
 * ✅ 状态 + 观察者:状态变化时通知
 */
@Component
public abstract class AbstractOrderState implements OrderState {

    @Autowired
    protected List<OrderStateListener> listeners;

    protected void notifyStateChange(Order order, String oldState, String newState) {
        listeners.forEach(l -> l.onStateChange(order, oldState, newState));
    }
}

7.3 组合 3:状态机驱动事件流

/**
 * ✅ 状态机 + 观察者:完整流程
 */
@Component
public class OrderStateMachine {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public void changeState(Order order, OrderEvent event) {
        // 1️⃣ 状态转换
        OrderState oldState = order.getState();
        event.applyTo(order);
        OrderState newState = order.getState();

        // 2️⃣ 发事件
        eventPublisher.publishEvent(new OrderStateChangedEvent(
            this, order.getOrderId(), 
            oldState.getStateName(), 
            newState.getStateName()));
    }
}

八、工程决策 Checklist

8.1 ✅ 这 5 种情况,强烈建议用状态模式

序号场景原因
行为随状态变订单/支付/物流
加新状态不动老代码符合开闭原则
状态之间自动转换状态机驱动
消除 if-else业务清晰
状态可独立测试单元测试友好

8.2 ❌ 这 5 种情况,绝对不要用状态模式

序号场景原因
只有 1-2 个状态过度设计
状态之间无转换用枚举
业务简单switch 够用
状态极少变没必要
追求简单类多

8.3 ⚠️ 状态模式的 6 大常见坑

序号表现解决方案
状态类过多100 个 State抽象公共
状态转换混乱不该转的转了状态机规则
状态可变性状态对象被修改不可变
状态未持久化重启后丢失状态序列化
并发问题同一订单并发
状态对象共享多个订单共享每个订单独立

8.4 面试官视角:状态高频追问

Q1:状态 vs 策略,区别是什么?

答:状态自动转换(状态机驱动),策略手动选择(客户端选)。状态"行为随状态变",策略"行为按场景选"

Q2:状态模式和状态机什么关系?

答:状态机定义规则(什么事件转什么状态),状态模式实现行为(每个状态做什么)。状态机是模型,状态模式是实现

Q3:Spring StateMachine 怎么用?

答:用 @EnableStateMachine + StateMachineConfigurerAdapter 配置状态和转换,sendEvent() 发事件触发转换


九、与其他模式协作

9.1 状态 + 观察者 = 状态变化通知

9.2 状态 + 策略 = 状态内用策略

@Component
public class PaidState implements OrderState {

    @Autowired
    private PaymentContext paymentContext;  // 策略

    @Override
    public void refund(Order order) {
        // ✅ 用策略选择退款渠道
        paymentContext.refund(order.getChannel(), ...);
        order.setState(new RefundingState());
    }
}

9.3 状态 + 工厂方法 = 状态工厂

/**
 * ✅ 状态工厂:按状态名创建状态
 */
public class OrderStateFactory {
    private static final Map<String, OrderState> STATES = new HashMap<>();
    static {
        STATES.put("PENDING_PAY", new PendingPayState());
        STATES.put("PAID", new PaidState());
        STATES.put("SHIPPED", new ShippedState());
    }

    public static OrderState getState(String name) {
        return STATES.get(name);
    }
}

9.4 大白商城模式协作全景图

                    ┌──────────────┐
                    │  状态        │ ← 本篇
                    └──────┬───────┘
                           │
       ┌───────────┬───────┼───────┬───────────┐
       │           │       │       │           │
   ┌───▼───┐  ┌────▼───┐ ┌▼────┐ ┌▼─────┐  ┌───▼────┐
   │策略   │  │观察者  │ │工厂 │ │单例  │  │ 模板   │
   │(行为) │  │(通知) │ │(创建)│ │(状态)│  │ 方法   │
   └───────┘  └────────┘ └──────┘ └──────┘  └───────┘
   26 篇       30 篇     04 篇     02 篇     28 篇

十、本篇小结 + 下篇预告

10.1 本篇小结(5 个核心要点)

  1. 本质:状态 = 行为随状态变加新状态只加 1 个 State 类
  2. 场景:订单/支付/物流/账号/优惠券,行为随状态变
  3. 对比:状态 vs 策略(自动 vs 手动)/ 状态 vs 状态机(实现 vs 模型)。
  4. Spring StateMachine@EnableStateMachine + sendEvent() 触发
  5. 避坑:状态类过多 / 转换混乱 / 状态可变 / 并发问题。

10.2 一句话总结

状态不是"换种方式写 if-else",是"用状态对象封装行为"。大白商城从 1500 行 if-else → 7 个 State 类——加新状态只加 1 个类,业务代码零改动

10.3 知识脑图

状态模式
├── 2 个核心概念
│   ├── 状态(State)
│   └── 上下文(Context)
├── 4 大角色
│   ├── 状态接口(OrderState)
│   ├── 具体状态 A(PendingPayState)
│   ├── 具体状态 B(PaidState)
│   └── 上下文(Order)
├── 7 个具体状态
│   ├── PendingPayState(待支付)
│   ├── PaidState(已支付)
│   ├── ShippedState(已发货)
│   ├── CompletedState(已完成)
│   ├── CancelledState(已取消)
│   ├── RefundingState(退款中)
│   └── RefundedState(已退款)
├── 模式对比
│   ├── vs 策略(自动 vs 手动)
│   ├── vs 状态机(实现 vs 模型)
│   └── vs 枚举(行为 vs 常量)
├── Spring StateMachine
│   ├── @EnableStateMachine
│   ├── StateMachineConfigurerAdapter
│   └── sendEvent()
└── 模式协作
    ├── + 观察者(状态变化通知)
    ├── + 策略(状态内用策略)
    └── + 工厂方法(状态工厂)

10.4 下篇预告

第 33 篇【状态模式 - 源码剖析篇】:JDK / Spring 中的状态实现

下一篇我们会深入源码,回答三个问题:

  1. JDK 的 Thread.State 怎么用状态机?
  2. Spring StateMachine 怎么实现状态转换?
  3. TCP 连接 / 业务流程怎么用状态机?

并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/horizon90/article/details/163994813

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--