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

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

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

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

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


目录


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

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

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

我打开代码一看:

java 复制代码
/**
 * ❌ 大白商城老的下单代码
 */
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 大白话讲透命令

继续打比方:

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

  • 错误做法 :业务方法直接调,没有"操作"这个概念
  • 正确做法
    1. 把每个操作封装成命令对象PayOrderCommand / CancelOrderCommand
    2. 命令包含所有执行信息(订单 ID / 用户 ID / ...)
  1. 命令支持 execute() / undo()
  2. 命令栈 记录历史,撤销 / 重做从栈里取

命令模式 = 把请求封装为对象,让请求可以排队、撤销、重做、记录日志

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

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

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

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


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

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

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

java 复制代码
/**
 * ❌ 反面教材 v1:直接调方法
 */
public void pay(String orderId) {
    orderDao.updateStatus(orderId, "PAID");
    // 无法撤销
}

翻车现场

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

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

java 复制代码
/**
 * ❌ 反面教材 v2:撤销逻辑分散
 */
public void cancel(String orderId) {
    // 撤销支付
    paymentService.refund(orderId);
    // 撤销库存
    stockService.increase(...);
    // 撤销订单状态
    orderDao.updateStatus(orderId, "CANCELLED");
    // ❌ 撤销逻辑到处都是
}

翻车现场

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

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

java 复制代码
/**
 * ❌ 反面教材 v3:历史记录分散
 */
public class OrderServiceV3 {
    // 每个方法自己记日志
    // 没有统一的操作栈
}

翻车现场

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

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

java 复制代码
/**
 * ❌ 反面教材 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 复制代码
<?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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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

java 复制代码
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(含撤销/重做栈)

java 复制代码
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 业务调用方

java 复制代码
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 宏命令:组合多个命令

java 复制代码
/**
 * ✅ 宏命令:组合多个命令
 */
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 单元测试

java 复制代码
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

核心源码

java 复制代码
/**
 * ✅ 命令接口:可执行任务
 */
@FunctionalInterface
public interface Runnable {
    /**
     * ✅ execute 方法
     */
    void run();
}

关键点

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

6.2 用 Runnable 作为命令

java 复制代码
/**
 * ✅ Runnable 作为命令
 */
Runnable command = () -> {
    log.info("执行命令");
};

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

6.3 Callable:带返回值的命令

java 复制代码
/**
 * ✅ Callable:带返回值的命令
 */
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

6.4 大白商城的"抄作业"

java 复制代码
/**
 * 借鉴 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 注解用命令模式封装事务:

java 复制代码
/**
 * ✅ @Transactional:事务命令
 */
@Service
public class OrderService {

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

关键点

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

7.2 TransactionTemplate:命令式事务

java 复制代码
/**
 * ✅ 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 大白商城的"抄作业"

java 复制代码
/**
 * 借鉴 @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 命令 + 状态 = 命令改变状态

java 复制代码
/**
 * ✅ 命令 + 状态:命令执行后改状态
 */
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 个核心要点)

  1. 本质 :命令 = 封装请求为对象支持撤销/重做/队列/日志
  2. 场景 :订单撤销/编辑器/异步任务/事务封装,需要撤销或队列的场景
  3. 对比:命令 vs 策略(撤销 vs 算法)/ 命令 vs 责任链(单命令 vs 链式)。
  4. JDK Runnable函数式命令,Lambda 友好。
  5. 避坑:撤销不全 / 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 中的命令实现

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

  1. JDK 的 Runnable / Callable 怎么用命令模式?
  2. Spring JdbcTemplate 怎么用命令模式简化 JDBC?
  3. 并发 Executor 怎么用命令模式执行任务?

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


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

相关推荐
净重21克1 小时前
万字长文!TLS协议升级避坑手册:JDK老项目适配TLS1.2/1.3全流程拆解
java·springboot·java安全
一直C1 小时前
【数据结构】哈希表+算法复杂度与经典排序查找(C语言)
java·linux·开发语言·数据结构·算法·ubuntu·散列表
paopaokaka_luck1 小时前
基于springboot3+vue3的车间生产管理系统(Echarts图形化分析、BI报表)
java·前端·spring boot·学习·echarts
Zane19941 小时前
遍历时删元素为什么报错:modCount 这个"计数器"在背后盯着你
java·后端
电商API_180079052472 小时前
速卖通商品采集API技术文章
java·开发语言·c++·api·跨境电商·商品详情
郑州光合科技余经理2 小时前
海外版外卖系统架构:订单怎么流转、权限怎么分
java·开发语言·前端·系统架构·uni-app·php·ai编程
用户202252215062 小时前
字节发布"豆包工作",我从AI PM角度看到的不是功能,是上下文
设计模式
一木 之林3 小时前
五、C++新特性、关键字与编译原理
java·jvm·算法
han_hanker3 小时前
新手公司项目实战
java·maven