欢迎光临
我们一直在努力

大白话说Java设计模式-34-命令模式(业务实战篇)

📌 PDF:AI人工智能 — 大模型微调与部署实战项目

大白话说Java设计模式-34-命令模式(业务实战篇):大白商城订单撤销/重做的"操作封装"

📌 一句话本质:命令模式就是"把请求封装为对象",让请求可以排队、撤销、重做、记录日志。

🏷️ 标签:命令模式 / Java 设计模式 / 撤销重做 / 事务封装 / 大白商城   🎯 适合:初中级后端 / 经常被"操作封装"困扰的工程师


目录

  • 一、业务场景引入:为什么需要"操作封装"?
  • 二、反面教材:硬编码调用的"灾难现场"
  • 三、模式原理:命令的"命令对象 + 调用者 + 接收者"
  • 四、实战代码:大白商城订单命令完整实现
  • 五、命令 vs 策略 / 责任链
  • 六、JDK 的 Runnable 怎么用命令?
  • 七、Spring @Transactional 怎么用命令?
  • 八、工程决策 Checklist
  • 九、与其他模式协作
  • 十、本篇小结 + 下篇预告

一、业务场景引入:为什么需要"操作封装"?

大白商城 2024 年运营要求一个新功能:

“用户下单后 30 分钟内可撤销,撤销后 1 小时内可重做,要支持撤销/重做栈!”

我打开代码一看:

/**
* ❌ 大白商城老的下单代码
*/

public class OrderServiceV0 {

public void pay(String orderId) {
// ❌ 业务逻辑直接调,没法撤销
orderDao.updateStatus(orderId, "PAID");
stockService.decrease(...);
paymentService.pay(...);
}

public void cancel(String orderId) {
// ❌ 撤销时无法回滚
orderDao.updateStatus(orderId, "CANCELLED");
stockService.increase(...);
paymentService.refund(...);
}
}

这种代码的痛点:

序号问题后果
无法撤销 操作不可回退
无法重做 操作不可重放
无法记录日志 操作不可审计
无法队列化 操作不可异步

老板要的"操作可撤销/可重做/可排队",怎么搞?

答案就是——命令模式。

1.1 大白话讲透命令

继续打比方:

场景:用户下单后想撤销 / 重做,运营想看操作日志,运维想异步执行。

  • 错误做法:业务方法直接调,没有"操作"这个概念
  • 正确做法:
  • 把每个操作封装成命令对象:PayOrderCommand / CancelOrderCommand
  • 命令包含所有执行信息(订单 ID / 用户 ID / …)
  • 命令支持 execute() / undo()
  • 命令栈记录历史,撤销 / 重做从栈里取
  • 命令模式 = 把请求封装为对象,让请求可以排队、撤销、重做、记录日志。

    1.2 命令模式的 3 个真实场景

    大白商城里,命令模式用在:

    场景“命令”
    订单操作 下单/支付/取消/退款
    撤销重做 编辑器撤销/重做
    异步任务 异步执行操作
    宏命令 一组操作打包
    事务封装 把操作封装成事务

    任何"需要撤销/重做/排队/记录日志"的场景,都用命令模式。


    二、反面教材:硬编码调用的"灾难现场"

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

    2.1 反面教材 v1:直接调方法

    /**
    * ❌ 反面教材 v1:直接调方法
    */

    public void pay(String orderId) {
    orderDao.updateStatus(orderId, "PAID");
    // 无法撤销
    }

    翻车现场:

    序号问题后果
    无法撤销 操作不可回退
    无法记录 操作不可审计

    2.2 反面教材 v2:撤销逻辑分散

    /**
    * ❌ 反面教材 v2:撤销逻辑分散
    */

    public void cancel(String orderId) {
    // 撤销支付
    paymentService.refund(orderId);
    // 撤销库存
    stockService.increase(...);
    // 撤销订单状态
    orderDao.updateStatus(orderId, "CANCELLED");
    // ❌ 撤销逻辑到处都是
    }

    翻车现场:

    序号问题后果
    撤销逻辑重复 改一处要改 N 处
    撤销不全 漏撤销某个步骤

    2.3 反面教材 v3:历史记录分散

    /**
    * ❌ 反面教材 v3:历史记录分散
    */

    public class OrderServiceV3 {
    // 每个方法自己记日志
    // 没有统一的操作栈
    }

    翻车现场:

    序号问题后果
    日志分散 难审计
    无法重做 操作丢失

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

    /**
    * ❌ 反面教材 v4:完全没抽象
    */

    public class OrderServiceV4 {
    public void process() {
    // 1000 行所有逻辑
    }
    }

    翻车现场:

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

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

    痛点反模式方案能不能解决?
    可撤销 ❌ 全部没有
    可重做 ❌ 全部没有
    可记录 ❌ 全部分散
    可排队 ❌ 全部无法

    必须上命令模式。


    三、模式原理:命令的"命令对象 + 调用者 + 接收者"

    3.1 命令的 4 个核心概念

    命令(Command):

    • 封装请求的对象
    • 包含 receiver + 参数
    • 提供 execute() / undo() 方法

    接收者(Receiver):

    • 真正执行操作的对象
    • 业务方(订单/支付/库存服务)

    调用者(Invoker):

    • 持有命令
    • 触发命令执行

    客户端(Client):

    • 创建命令
    • 设置给调用者

    大白商城例子:

    概念例子
    命令 PayOrderCommand / CancelOrderCommand
    接收者 OrderService / PaymentService
    调用者 OrderController / CommandQueue
    客户端 用户

    3.2 命令的 5 个核心角色

    角色职责例子
    命令接口(Command) 定义命令接口 OrderCommand
    具体命令 A(Concrete Command A) 实现某命令 PayOrderCommand
    具体命令 B(Concrete Command B) 实现某命令 CancelOrderCommand
    调用者(Invoker) 持有命令并执行 CommandQueue
    接收者(Receiver) 真正执行 OrderService

    3.3 一张图看懂命令

    OrderController(调用者)

    │ invoke(command)

    └── PayOrderCommand(命令)

    │ execute()

    └── OrderService(接收者)

    └── updateStatus() / 减库存 / 支付

    3.4 命令的"灵魂三问"

    Q1:命令 vs 策略,区别是什么?

    答:

    • 命令:封装请求(含 receiver + action + 参数)
    • 策略:封装算法(只含算法)
    • 命令"做什么"包含"对谁做",策略只关心"怎么做"
    • 命令支持撤销/重做,策略不支持

    Q2:命令 vs 责任链,区别是什么?

    答:

    • 命令:单命令执行(一个命令一个动作)
    • 责任链:多步骤处理(多个 handler 链式)
    • 命令"封装一个完整操作",责任链"分步骤处理"
    • 命令可撤销,责任链不可

    Q3:命令模式的核心价值是什么?

    答:

    • 可撤销 / 重做(undo/redo 栈)
    • 可排队(command queue)
    • 可记录(command log)
    • 可宏命令(macro command)

    3.5 命令的 3 种实现方式

    方式特点适用
    命令接口 经典命令 标准
    Runnable JDK 函数式命令 简单
    Lambda Java 8+ 函数式

    大白商城主推命令接口方式(支持撤销/重做)。


    四、实战代码:大白商城订单命令完整实现

    下面是大白商城生产环境在用的命令实现,全套代码可直接复制到 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-34</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>mall-design-pattern-34</name>
    <description>大白商城 – 设计模式 34 命令模式</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 命令接口:OrderCommand

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;

    /**
    * ✅ 命令接口:订单命令
    * <p>
    * – execute() 执行命令
    * – undo() 撤销命令
    *
    * @author 大白商城技术团队
    */

    public interface OrderCommand {

    /**
    * ✅ 执行命令
    */

    void execute();

    /**
    * ✅ 撤销命令
    */

    void undo();

    /**
    * 命令名(用于日志)
    */

    String getCommandName();
    }

    4.3 接收者:OrderReceiver

    package com.dabai.mall.command;

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

    /**
    * ✅ 接收者:订单服务
    * <p>
    * 真正执行订单操作
    *
    * @author 大白商城技术团队
    */

    @Slf4j
    @Service
    public class OrderReceiver {

    public void createOrder(Order order) {
    log.info("【订单】创建, orderId={}", order.getOrderId());
    }

    public void payOrder(Order order) {
    log.info("【订单】支付, orderId={}", order.getOrderId());
    }

    public void cancelOrder(Order order) {
    log.info("【订单】取消, orderId={}", order.getOrderId());
    }

    public void shipOrder(Order order) {
    log.info("【订单】发货, orderId={}", order.getOrderId());
    }
    }

    4.4 4 个具体命令

    CreateOrderCommand:

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;
    import lombok.extern.slf4j.Slf4j;

    /**
    * ✅ 具体命令:创建订单
    */

    @Slf4j
    public class CreateOrderCommand implements OrderCommand {

    private final OrderReceiver receiver;
    private final Order order;

    public CreateOrderCommand(OrderReceiver receiver, Order order) {
    this.receiver = receiver;
    this.order = order;
    }

    @Override
    public void execute() {
    log.info("【命令】执行: 创建订单, orderId={}", order.getOrderId());
    receiver.createOrder(order);
    }

    @Override
    public void undo() {
    log.info("【命令】撤销: 取消订单, orderId={}", order.getOrderId());
    receiver.cancelOrder(order);
    }

    @Override
    public String getCommandName() {
    return "CREATE_ORDER";
    }
    }

    PayOrderCommand:

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;
    import lombok.extern.slf4j.Slf4j;

    /**
    * ✅ 具体命令:支付订单
    */

    @Slf4j
    public class PayOrderCommand implements OrderCommand {

    private final OrderReceiver receiver;
    private final Order order;

    public PayOrderCommand(OrderReceiver receiver, Order order) {
    this.receiver = receiver;
    this.order = order;
    }

    @Override
    public void execute() {
    log.info("【命令】执行: 支付订单, orderId={}", order.getOrderId());
    receiver.payOrder(order);
    }

    @Override
    public void undo() {
    log.info("【命令】撤销: 取消支付(退款), orderId={}", order.getOrderId());
    receiver.cancelOrder(order);
    }

    @Override
    public String getCommandName() {
    return "PAY_ORDER";
    }
    }

    CancelOrderCommand:

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;
    import lombok.extern.slf4j.Slf4j;

    /**
    * ✅ 具体命令:取消订单
    */

    @Slf4j
    public class CancelOrderCommand implements OrderCommand {

    private final OrderReceiver receiver;
    private final Order order;

    public CancelOrderCommand(OrderReceiver receiver, Order order) {
    this.receiver = receiver;
    this.order = order;
    }

    @Override
    public void execute() {
    log.info("【命令】执行: 取消订单, orderId={}", order.getOrderId());
    receiver.cancelOrder(order);
    }

    @Override
    public void undo() {
    log.info("【命令】撤销: 恢复订单, orderId={}", order.getOrderId());
    receiver.createOrder(order);
    }

    @Override
    public String getCommandName() {
    return "CANCEL_ORDER";
    }
    }

    ShipOrderCommand:

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;
    import lombok.extern.slf4j.Slf4j;

    /**
    * ✅ 具体命令:发货
    */

    @Slf4j
    public class ShipOrderCommand implements OrderCommand {

    private final OrderReceiver receiver;
    private final Order order;

    public ShipOrderCommand(OrderReceiver receiver, Order order) {
    this.receiver = receiver;
    this.order = order;
    }

    @Override
    public void execute() {
    log.info("【命令】执行: 发货, orderId={}", order.getOrderId());
    receiver.shipOrder(order);
    }

    @Override
    public void undo() {
    log.info("【命令】撤销: 召回发货, orderId={}", order.getOrderId());
    // 实际召回
    }

    @Override
    public String getCommandName() {
    return "SHIP_ORDER";
    }
    }

    配套 Order DTO:

    package com.dabai.mall.order;

    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;

    import java.math.BigDecimal;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Order {
    private String orderId;
    private String userId;
    private BigDecimal amount;
    private String status;
    }

    4.5 调用者:CommandInvoker(含撤销/重做栈)

    package com.dabai.mall.command;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;

    import java.util.Stack;

    /**
    * ✅ 调用者:命令执行器
    * <p>
    * 包含 undo / redo 栈
    *
    * @author 大白商城技术团队
    */

    @Slf4j
    @Service
    public class CommandInvoker {

    /**
    * ✅ 已执行命令栈(用于撤销)
    */

    private final Stack<OrderCommand> undoStack = new Stack<>();

    /**
    * ✅ 已撤销命令栈(用于重做)
    */

    private final Stack<OrderCommand> redoStack = new Stack<>();

    /**
    * ✅ 执行命令
    */

    public void execute(OrderCommand command) {
    command.execute();
    undoStack.push(command);
    // 执行后清空 redo 栈
    redoStack.clear();
    }

    /**
    * ✅ 撤销
    */

    public void undo() {
    if (undoStack.isEmpty()) {
    log.warn("【命令】无可撤销命令");
    return;
    }
    OrderCommand command = undoStack.pop();
    command.undo();
    redoStack.push(command);
    }

    /**
    * ✅ 重做
    */

    public void redo() {
    if (redoStack.isEmpty()) {
    log.warn("【命令】无可重做命令");
    return;
    }
    OrderCommand command = redoStack.pop();
    command.execute();
    undoStack.push(command);
    }
    }

    4.6 业务调用方

    package com.dabai.mall.controller;

    import com.dabai.mall.command.*;
    import com.dabai.mall.order.Order;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;

    /**
    * ✅ 订单 Controller:命令模式的调用方
    * <p>
    * 业务方只调 CommandInvoker,不关心命令细节
    *
    * @author 大白商城技术团队
    */

    @RestController
    @RequestMapping("/api/order")
    public class OrderController {

    @Autowired
    private OrderReceiver receiver;

    @Autowired
    private CommandInvoker invoker;

    @PostMapping("/create")
    public String create(@RequestBody Order order) {
    // ✅ 创建命令
    OrderCommand command = new CreateOrderCommand(receiver, order);
    // ✅ 执行
    invoker.execute(command);
    return "success";
    }

    @PostMapping("/pay")
    public String pay(@RequestBody Order order) {
    OrderCommand command = new PayOrderCommand(receiver, order);
    invoker.execute(command);
    return "success";
    }

    @PostMapping("/cancel")
    public String cancel(@RequestBody Order order) {
    OrderCommand command = new CancelOrderCommand(receiver, order);
    invoker.execute(command);
    return "success";
    }

    @PostMapping("/ship")
    public String ship(@RequestBody Order order) {
    OrderCommand command = new ShipOrderCommand(receiver, order);
    invoker.execute(command);
    return "success";
    }

    @PostMapping("/undo")
    public String undo() {
    invoker.undo();
    return "success";
    }

    @PostMapping("/redo")
    public String redo() {
    invoker.redo();
    return "success";
    }
    }

    4.7 宏命令:组合多个命令

    /**
    * ✅ 宏命令:组合多个命令
    */

    public class MacroCommand implements OrderCommand {

    private final List<OrderCommand> commands = new ArrayList<>();

    public void addCommand(OrderCommand command) {
    commands.add(command);
    }

    @Override
    public void execute() {
    log.info("【宏命令】开始, 包含 {} 个命令", commands.size());
    for (OrderCommand command : commands) {
    command.execute();
    }
    }

    @Override
    public void undo() {
    // ✅ 倒序撤销
    for (int i = commands.size() 1; i >= 0; i) {
    commands.get(i).undo();
    }
    }

    @Override
    public String getCommandName() {
    return "MACRO";
    }
    }

    4.8 单元测试

    package com.dabai.mall.command;

    import com.dabai.mall.order.Order;
    import org.junit.jupiter.api.BeforeEach;
    import org.junit.jupiter.api.Test;

    import java.math.BigDecimal;

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

    /**
    * 命令模式完整单元测试
    */

    class OrderCommandTest {

    private OrderReceiver receiver;
    private CommandInvoker invoker;

    @BeforeEach
    void setUp() {
    receiver = new OrderReceiver();
    invoker = new CommandInvoker();
    }

    @Test
    void testCreateOrder() {
    Order order = Order.builder().orderId("ORDER_001").build();
    OrderCommand command = new CreateOrderCommand(receiver, order);
    invoker.execute(command);
    }

    @Test
    void testPayOrder() {
    Order order = Order.builder().orderId("ORDER_001").build();
    OrderCommand command = new PayOrderCommand(receiver, order);
    invoker.execute(command);
    }

    @Test
    void testCancelOrder() {
    Order order = Order.builder().orderId("ORDER_001").build();
    OrderCommand command = new CancelOrderCommand(receiver, order);
    invoker.execute(command);
    }

    @Test
    void testUndo() {
    Order order = Order.builder().orderId("ORDER_001").build();
    OrderCommand command = new CreateOrderCommand(receiver, order);
    invoker.execute(command);
    invoker.undo();
    }

    @Test
    void testRedo() {
    Order order = Order.builder().orderId("ORDER_001").build();
    OrderCommand command = new CreateOrderCommand(receiver, order);
    invoker.execute(command);
    invoker.undo();
    invoker.redo();
    }

    @Test
    void testUndoEmptyStack() {
    // ✅ 空栈撤销不报错
    invoker.undo();
    }

    @Test
    void testRedoEmptyStack() {
    // ✅ 空栈重做不报错
    invoker.redo();
    }

    @Test
    void testMacroCommand() {
    Order order = Order.builder().orderId("ORDER_001").build();

    MacroCommand macro = new MacroCommand();
    macro.addCommand(new CreateOrderCommand(receiver, order));
    macro.addCommand(new PayOrderCommand(receiver, order));
    macro.addCommand(new ShipOrderCommand(receiver, order));

    invoker.execute(macro);
    invoker.undo(); // 全部撤销
    }

    @Test
    void testUndoClearRedo() {
    // ✅ 执行新命令后 redo 栈被清空
    Order order = Order.builder().orderId("ORDER_001").build();
    invoker.execute(new CreateOrderCommand(receiver, order));
    invoker.execute(new PayOrderCommand(receiver, order));
    invoker.undo();
    invoker.undo();
    invoker.redo(); // ✅ 可重做
    invoker.execute(new CancelOrderCommand(receiver, order)); // ✅ 新命令
    invoker.redo(); // ❌ redo 栈已清空
    }
    }


    五、命令 vs 策略 / 责任链

    5.1 一张表看清区别

    模式关注点撤销适用
    命令 封装请求 ✅ 支持 撤销/重做/队列
    策略 封装算法 多种算法
    责任链 链式处理 链式处理

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

    大白商城:

    模式例子
    命令 订单撤销 / 重做
    策略 支付 4 种方式
    责任链 风控 4 步流程

    5.3 决策树

    需要撤销/重做/队列?
    ├── 是
    │ └── ✅ 命令模式
    └── 否
    ├── 多种算法可切换?
    │ ├── 是 → ✅ 策略
    │ └── 否
    │ ├── 链式处理?
    │ │ ├── 是 → ✅ 责任链
    │ │ └── 否 → 普通类

    5.4 命令 vs 策略:易混淆

    维度命令策略
    关注 请求 算法
    撤销
    队列
    接收者 显式 不需要

    大白商城选型:

    场景选哪个原因
    订单撤销 命令 需要撤销/重做
    支付方式 策略 多种算法
    风控流程 责任链 链式处理
    异步任务 命令 排队执行

    六、JDK 的 Runnable 怎么用命令?

    6.1 Runnable:函数式命令

    源码位置:

    • OpenJDK 17
    • src/java.base/share/classes/java/lang/Runnable.java

    核心源码:

    /**
    * ✅ 命令接口:可执行任务
    */

    @FunctionalInterface
    public interface Runnable {
    /**
    * ✅ execute 方法
    */

    void run();
    }

    关键点:

    关键点解释
    run() 命令执行方法
    @FunctionalInterface Lambda 友好
    无参数无返回值 简化

    6.2 用 Runnable 作为命令

    /**
    * ✅ Runnable 作为命令
    */

    Runnable command = () -> {
    log.info("执行命令");
    };

    new Thread(command).start(); // 异步执行命令

    6.3 Callable:带返回值的命令

    /**
    * ✅ Callable:带返回值的命令
    */

    @FunctionalInterface
    public interface Callable<V> {
    V call() throws Exception;
    }

    6.4 大白商城的"抄作业"

    /**
    * 借鉴 Runnable:异步命令
    */

    public class AsyncCommandExecutor {

    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    public void executeAsync(Runnable command) {
    executor.execute(command);
    }
    }


    七、Spring @Transactional 怎么用命令?

    7.1 @Transactional:事务命令

    Spring 的 @Transactional 注解用命令模式封装事务:

    /**
    * ✅ @Transactional:事务命令
    */

    @Service
    public class OrderService {

    @Transactional
    public void payOrder(String orderId) {
    // 1️⃣ 命令执行
    orderDao.updateStatus(orderId, "PAID");
    stockService.decrease(...);
    // 任何一个失败,整个命令撤销(回滚)
    }
    }

    关键点:

    关键点解释
    @Transactional 事务命令标记
    整体执行 命令执行
    失败回滚 命令撤销

    7.2 TransactionTemplate:命令式事务

    /**
    * ✅ TransactionTemplate:命令式事务
    */

    @Service
    public class OrderTxService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    public void payOrder(String orderId) {
    // ✅ 命令式事务
    transactionTemplate.execute(status -> {
    // 1️⃣ 命令执行
    orderDao.updateStatus(orderId, "PAID");
    stockService.decrease(...);
    return null;
    });
    }
    }

    7.3 大白商城的"抄作业"

    /**
    * 借鉴 @Transactional:命令式事务
    */

    public class CommandExecutor {

    /**
    * ✅ 通用命令执行(含 try-catch)
    */

    public static <T> T execute(Command<T> command) {
    try {
    return command.execute();
    } catch (Exception e) {
    log.error("命令执行失败: {}", command.getName(), e);
    throw e;
    }
    }
    }

    @FunctionalInterface
    public interface Command<T> {
    T execute();
    default String getName() { return "unnamed"; }
    }


    八、工程决策 Checklist

    8.1 ✅ 这 5 种情况,强烈建议用命令模式

    序号场景原因
    需要撤销 / 重做 编辑器/订单
    需要操作队列 异步任务
    需要操作日志 审计
    需要事务封装 @Transactional
    需要宏命令 一组操作

    8.2 ❌ 这 5 种情况,绝对不要用命令模式

    序号场景原因
    业务简单 过度设计
    不需要撤销 复杂度浪费
    单一操作 直接调
    追求简单 类多
    业务经常变 难维护

    8.3 ⚠️ 命令的 6 大常见坑

    序号坑表现解决方案
    撤销不全 撤销不彻底 完整 undo
    redo 栈混乱 顺序错乱 清空栈
    状态不一致 撤销后状态错 状态机
    命令对象多 类爆炸 抽象公共
    receiver 缺失 业务无法执行 显式 receiver
    并发问题 栈不安全 同步

    8.4 面试官视角:命令高频追问

    Q1:命令 vs 策略,区别是什么?

    答:命令封装请求(含 receiver + action + 参数),策略封装算法。命令支持撤销/重做,策略不支持。

    Q2:命令模式怎么实现撤销/重做?

    答:维护两个栈(undo / redo)。execute 时 push undo 栈 + clear redo 栈;undo 时从 undo 栈 pop + push redo 栈;redo 时从 redo 栈 pop + push undo 栈。

    Q3:Spring @Transactional 是命令模式吗?

    答:严格说不是经典命令模式,但用命令思想——把一组操作封装成一个"事务命令",整体执行,失败回滚(撤销)。


    九、与其他模式协作

    9.1 命令 + 撤销/重做 = 撤销重做栈

    9.2 命令 + 宏命令 = 一组命令

    9.3 命令 + 状态 = 命令改变状态

    /**
    * ✅ 命令 + 状态:命令执行后改状态
    */

    public class PayOrderCommand implements OrderCommand {
    @Override
    public void execute() {
    receiver.payOrder(order);
    // ✅ 改状态
    order.setState(new PaidState());
    }
    }

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

    ┌──────────────┐
    │ 命令 │ ← 本篇
    └──────┬───────┘

    ┌───────────┬───────┼───────┬───────────┐
    │ │ │ │ │
    ┌───▼───┐ ┌────▼───┐ ┌▼────┐ ┌▼─────┐ ┌───▼────┐
    │策略 │ │状态 │ │单例 │ │模板 │ │ 责任链 │
    │(算法) │ │(变化) │ │(命令)│ │方法 │ │ (链式) │
    └───────┘ └────────┘ └──────┘ └──────┘ └───────┘
    26 篇 32 篇 02 篇 28 篇 46 篇


    十、本篇小结 + 下篇预告

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

  • 本质:命令 = 封装请求为对象,支持撤销/重做/队列/日志。
  • 场景:订单撤销/编辑器/异步任务/事务封装,需要撤销或队列的场景。
  • 对比:命令 vs 策略(撤销 vs 算法)/ 命令 vs 责任链(单命令 vs 链式)。
  • JDK Runnable:函数式命令,Lambda 友好。
  • 避坑:撤销不全 / redo 栈混乱 / 状态不一致 / 并发问题。
  • 10.2 一句话总结

    命令不是"换种方式调用方法",是"把操作封装成对象,让操作可撤销/可重做/可排队"。大白商城从直接调方法 → 4 个 Command 类 + 撤销/重做栈——用户操作可撤销,运营日志可追溯,运维任务可异步。

    10.3 知识脑图

    命令模式
    ├── 4 个核心概念
    │ ├── 命令(Command)
    │ ├── 接收者(Receiver)
    │ ├── 调用者(Invoker)
    │ └── 客户端(Client)
    ├── 5 大角色
    │ ├── 命令接口(OrderCommand)
    │ ├── 具体命令(4 个)
    │ ├── 调用者(CommandInvoker)
    │ ├── 接收者(OrderReceiver)
    │ └── 客户端(OrderController)
    ├── 4 个具体命令
    │ ├── CreateOrderCommand
    │ ├── PayOrderCommand
    │ ├── CancelOrderCommand
    │ └── ShipOrderCommand
    ├── 1 个宏命令
    │ └── MacroCommand
    ├── 模式对比
    │ ├── vs 策略(撤销 vs 算法)
    │ ├── vs 责任链(单命令 vs 链式)
    │ └── vs 观察者(主动 vs 被动)
    ├── JDK Runnable
    │ ├── Runnable(无返回值)
    │ ├── Callable(有返回值)
    │ └── Executor(执行)
    └── Spring @Transactional
    ├── 注解式事务
    ├── TransactionTemplate
    └── 命令式事务

    10.4 下篇预告

    第 35 篇【命令模式 – 源码剖析篇】:JDK / Spring 中的命令实现

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

  • JDK 的 Runnable / Callable 怎么用命令模式?
  • Spring JdbcTemplate 怎么用命令模式简化 JDBC?
  • 并发 Executor 怎么用命令模式执行任务?
  • 并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


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

    赞(0)
    未经允许不得转载:171主机测评 » 大白话说Java设计模式-34-命令模式(业务实战篇)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址