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
📌 总结这个流程:
- Client 创建 Receiver(TextFile)
- Client 将 Receiver 包装成 Command
- Client 把 Command 交给 Invoker 执行
- 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、工作流引擎、远程控制这类系统,命令模式几乎是必选项。掌握它,能让你的设计更灵活、更健壮。