1. 命令模式概述
命令模式(Command Pattern)是一种行为设计模式,它将请求封装成对象,从而允许用户使用不同的请求、队列或日志来参数化其他对象。这种模式的核心思想是把"做什么"(操作内容)和"谁来做"(执行者)解耦,就像把操作指令录制到录像带中,可以随时播放、回放或转发。
在实际开发中,命令模式特别适合以下场景:
- 需要实现操作的撤销/重做功能(如文本编辑器)
- 需要将操作排队、记录或远程执行(如任务队列系统)
- 需要支持宏命令(一组命令的组合执行)
- 需要支持事务性操作(要么全部执行成功,要么全部不执行)
提示:命令模式与策略模式的区别在于,策略模式关注的是"如何做"(算法替换),而命令模式关注的是"做什么"和"什么时候做"(操作封装与调度)。
2. 模式结构与核心组件
2.1 UML类图解析
标准的命令模式包含以下核心角色:
-
Command(命令接口)
- 声明执行操作的接口,通常包含
execute()方法 - 可能包含
undo()方法用于撤销操作
- 声明执行操作的接口,通常包含
-
ConcreteCommand(具体命令)
- 实现Command接口
- 持有接收者对象的引用
- 在
execute()方法中调用接收者的相关操作
-
Receiver(接收者)
- 知道如何执行与请求相关的操作
- 任何类都可以作为接收者
-
Invoker(调用者)
- 持有命令对象
- 在某个时间点调用命令对象的
execute()方法
-
Client(客户端)
- 创建具体命令对象并设置其接收者
- 将命令对象交给调用者
2.2 代码实现示例
java复制// 命令接口
interface Command {
void execute();
void undo();
}
// 具体命令
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
// 接收者
class Light {
void on() { System.out.println("Light is on"); }
void off() { System.out.println("Light is off"); }
}
// 调用者
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
// 客户端
public class Client {
public static void main(String[] args) {
Light light = new Light();
Command lightOn = new LightOnCommand(light);
RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton(); // 输出: Light is on
}
}
3. 模式应用场景与实战技巧
3.1 典型应用场景
-
GUI按钮和菜单项
- 每个按钮绑定一个命令对象
- 相同的命令可以被多个UI元素共享
- 支持快捷键绑定和手势命令
-
事务系统
- 每个操作封装为命令
- 失败时可以执行补偿操作
- 支持操作日志和重试机制
-
宏命令
- 组合多个命令为一个复合命令
- 支持批量执行和撤销
- 示例:Photoshop的动作录制功能
-
线程池任务
- 将任务封装为命令对象
- 放入队列由工作线程执行
- 支持任务优先级和调度
3.2 实战技巧与优化
-
智能命令 vs 傻瓜命令
- 智能命令:包含所有业务逻辑(接收者简单或不存在)
- 傻瓜命令:只委托给接收者(推荐方式,更符合单一职责)
-
撤销/重做实现
- 维护历史命令栈
- 每个命令实现精确的逆向操作
- 对于不可逆操作,采用补偿方式
-
命令组合
- 实现宏命令(Composite模式的应用)
- 支持批量执行和原子性保证
-
日志与持久化
- 命令对象可序列化
- 支持操作日志和崩溃恢复
- 可用于实现时间旅行调试
4. 高级应用与模式变体
4.1 异步命令模式
java复制interface AsyncCommand extends Command {
CompletableFuture<Void> executeAsync();
}
class FileDownloadCommand implements AsyncCommand {
private String url;
private String savePath;
public FileDownloadCommand(String url, String savePath) {
this.url = url;
this.savePath = savePath;
}
public void execute() {
executeAsync().join();
}
public CompletableFuture<Void> executeAsync() {
return CompletableFuture.runAsync(() -> {
// 实际下载逻辑
System.out.println("Downloading from " + url);
});
}
public void undo() {
new File(savePath).delete();
}
}
4.2 事务性命令
java复制class TransactionalCommand implements Command {
private List<Command> commands = new ArrayList<>();
public void addCommand(Command cmd) {
commands.add(cmd);
}
public void execute() {
List<Command> executed = new ArrayList<>();
try {
for (Command cmd : commands) {
cmd.execute();
executed.add(cmd);
}
} catch (Exception e) {
// 回滚已执行的命令
for (int i = executed.size()-1; i >= 0; i--) {
executed.get(i).undo();
}
throw e;
}
}
public void undo() {
for (int i = commands.size()-1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
4.3 命令模式与事件溯源
事件溯源(Event Sourcing)是命令模式的进阶应用:
- 每个状态变更都表示为命令事件
- 通过重放事件序列重建状态
- 支持时间查询和状态回滚
实现要点:
- 命令对象包含所有必要数据
- 事件存储保证持久化和顺序
- 快照机制优化重建性能
5. 常见问题与解决方案
5.1 性能优化
-
命令对象复用
- 对于无状态命令,可以共享实例
- 使用对象池减少GC压力
-
轻量级命令
- 避免在命令中保存大对象
- 使用引用或ID代替实际数据
-
批量执行
- 合并多个小命令为批量命令
- 减少上下文切换开销
5.2 设计陷阱
-
过度设计
- 简单操作直接调用可能更合适
- 只在需要解耦、队列或撤销时使用
-
命令膨胀
- 避免为每个微小操作创建命令
- 合理划分命令粒度
-
撤销实现困难
- 某些操作难以逆向(如发送邮件)
- 考虑补偿操作而非严格撤销
5.3 调试技巧
-
命令日志
- 记录执行的命令序列
- 支持重放复现问题
-
空命令对象
- 实现Null Object模式
- 避免调用者处理null检查
-
命令监控
- 统计命令执行时间和成功率
- 实现熔断机制
6. 与其他模式的关系
-
与责任链模式
- 命令可以组成责任链
- 每个处理器决定是否执行命令
-
与备忘录模式
- 备忘录保存状态以便恢复
- 命令使用备忘录实现撤销
-
与观察者模式
- 命令执行结果可以通知观察者
- 实现异步回调机制
-
与策略模式
- 策略关注算法替换
- 命令关注操作封装
在实际项目中,我经常将命令模式与这些模式组合使用。比如在一个电商订单系统中,订单处理流程使用责任链传递命令对象,每个处理环节可以修改命令内容或决定是否继续传递,同时使用备忘录模式保存中间状态以便异常恢复。