1. 概述

命令模式(Command Pattern)属于行为型设计模式,是 GoF(Gang of Four)经典设计模式之一。它的核心思想很简单:把“执行某个操作”所需的所有信息封装成一个对象,这个对象包含了要调用的方法、方法参数以及目标对象(即执行者)。

这样一来,发起命令的对象(调用方)和实际执行命令的对象(接收方)就实现了解耦。这也是为什么命令模式常被看作是“生产者-消费者”场景的一种实现方式。

本文将带你用两种方式实现命令模式:

  • 传统的面向对象实现
  • Java 8+ 的函数式增强写法(Lambda + 方法引用)

同时也会说明它在哪些真实场景中特别有用,比如实现撤销/重做、宏命令、任务队列等。✅

⚠️ 注意:命令模式不是解决“多态调用”的替代品,而是为了解耦调用逻辑与执行逻辑,支持延迟执行、排队、回滚等高级特性。


2. 面向对象实现

标准的命令模式需要四个角色协同工作:

角色 说明
✅ Command(命令) 定义执行接口,封装动作
✅ Receiver(接收者) 真正干活的人,执行具体逻辑
✅ Invoker(调用者) 持有命令对象,触发执行,但不关心内部细节
✅ Client(客户端) 组装命令、接收者和调用者,控制流程

下面我们通过一个文本编辑器的小例子来串起整个流程。

2.1 命令接口与实现

首先定义一个函数式接口作为命令契约:

@FunctionalInterface
public interface TextFileOperation {
    String execute();
}

接着实现两个具体命令:打开文件和保存文件。

public class OpenTextFileOperation implements TextFileOperation {

    private TextFile textFile;
    
    public OpenTextFileOperation(TextFile textFile) {
        this.textFile = textFile;
    }

    @Override
    public String execute() {
        return textFile.open();
    }
}
public class SaveTextFileOperation implements TextFileOperation {
    
    private TextFile textFile;
    
    public SaveTextFileOperation(TextFile textFile) {
        this.textFile = textFile;
    }

    @Override
    public String execute() {
        return textFile.save();
    }
}

📌 关键点:

  • 每个命令都持有一个 TextFile 实例(Receiver)
  • execute() 方法只是委托调用 Receiver 的对应方法
  • 调用方(Invoker)完全不知道背后是谁在干活

这就是典型的“命令封装行为”的体现。

2.2 接收者(Receiver)

接收者是真正执行业务逻辑的类。在这里就是 TextFile

public class TextFile {
    
    private String name;
    
    public TextFile(String name) {
        this.name = name;
    }

    public String open() {
        return "Opening file " + name;
    }
    
    public String save() {  
        return "Saving file " + name;
    }
    
    // 其他编辑功能:write, copy, paste 等
}

✅ 注意:TextFile 根本不知道“命令模式”这回事,它只是一个普通的 POJO 或服务类。这种低耦合正是我们想要的。

2.3 调用者(Invoker)

调用者负责执行命令,但它只认识 TextFileOperation 接口,对具体实现一无所知:

public class TextFileOperationExecutor {
    
    private final List<TextFileOperation> textFileOperations = new ArrayList<>();
    
    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

💡 特性说明:

  • ✅ 解耦:Invoker 不依赖任何具体命令
  • ✅ 可扩展:未来加个 DeleteTextFileOperation 完全不影响现有代码
  • ✅ 支持历史记录:List 可用于实现 undo/redo(踩坑提醒:注意内存泄漏!)

⚠️ 生产环境建议用双端队列或环形缓冲区管理历史命令,避免无限增长。

2.4 客户端组装逻辑

最后由 Client 来组装整个调用链:

public class Client {
    public static void main(String[] args) {
        TextFileOperationExecutor executor = new TextFileOperationExecutor();
        
        executor.executeOperation(
            new OpenTextFileOperation(new TextFile("file1.txt"))
        );
        executor.executeOperation(
            new SaveTextFileOperation(new TextFile("file2.txt"))
        );
    }
}

输出结果:

Opening file file1.txt
Saving file file2.txt

📌 总结这个流程:

  1. Client 创建 Receiver(TextFile)
  2. Client 将 Receiver 包装成 Command
  3. Client 把 Command 交给 Invoker 执行
  4. Invoker 调用 execute(),触发 Receiver 动作

整个过程清晰、可测试、易维护。✅


3. 函数式增强实现(Java 8+)

从 Java 8 开始,我们可以用 Lambda 和方法引用来简化命令模式的写法,减少样板代码。

3.1 使用 Lambda 表达式

由于 TextFileOperation 是函数式接口,可以直接传 Lambda:

TextFileOperationExecutor executor = new TextFileOperationExecutor();

executor.executeOperation(() -> "Opening file file1.txt");
executor.executeOperation(() -> "Saving file file1.txt");

✅ 优点:

  • 极简,适合简单场景
  • 无需定义额外类

❌ 缺点:

  • 丢失了 Receiver 的封装性
  • 无法复用逻辑(比如多个地方都要打开文件)
  • 不利于后期扩展(比如加权限校验、日志等横切逻辑)

📌 建议:仅用于一次性、简单的命令场景,比如单元测试或脚本化操作。

3.2 使用方法引用

更优雅的方式是结合 Receiver 实例使用方法引用:

TextFileOperationExecutor executor = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");

executor.executeOperation(textFile::open);
executor.executeOperation(textFile::save);

✅ 优势:

  • 代码简洁,又保留了面向对象结构
  • 复用性强,Receiver 可以被多个命令共享
  • 易于调试,堆栈信息清晰

⚠️ 注意:textFile::open 本质上生成了一个实现了 TextFileOperation 的实例,底层还是命令模式,只是写法更现代。

💡 小技巧:在 Spring 或其他 IoC 容器中,可以把 Receiver 当成 Bean 注入,命令通过方法引用动态绑定,灵活性极高。


4. 总结

命令模式的核心价值在于 解耦调用者与执行者,并支持以下高级功能:

功能 实现方式
✅ 撤销/重做 Invoker 维护命令栈
✅ 宏命令 Composite Command 批量执行
✅ 延迟执行 命令对象可序列化后异步处理
✅ 权限控制 在 execute() 前加拦截逻辑
✅ 日志审计 包装命令加日志切面

📌 使用建议:

  • 优先考虑函数式写法(Lambda/方法引用)提升开发效率
  • 复杂业务逻辑仍推荐传统 OO 实现,便于维护和扩展
  • 结合 Spring 的 ApplicationEvent 或消息队列可实现分布式命令

所有示例代码已托管至 GitHub:https://github.com/johnchen902/command-pattern-demo

如果你正在做 Undo/Redo、工作流引擎、远程控制这类系统,命令模式几乎是必选项。掌握它,能让你的设计更灵活、更健壮。


原始标题:The Command Pattern in Java