Appearance
命令模式(Command Pattern)笔记
定义
命令模式是一种行为设计模式,它将请求封装为独立的对象,使你可以参数化客户端对象使用不同的请求、队列或日志请求,并支持可撤销的操作。
英文原文定义:
The Command Pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
核心概念
命令模式将"动作请求者"与"动作执行者"解耦,通过将请求封装为命令对象,可以像普通对象一样存储、传递和操作这些请求。
主要角色
- Command(命令接口):声明执行操作的接口
- ConcreteCommand(具体命令):实现命令接口,绑定接收者与动作
- Invoker(调用者):要求命令执行请求
- Receiver(接收者):知道如何执行与请求相关的操作
- Client(客户端):创建具体命令并设置接收者
优缺点
优点
- 解耦调用者与执行者:调用者无需知道执行细节
- 可扩展性强:容易添加新命令而不影响现有代码
- 支持复合命令:可以组合多个简单命令成复杂命令
- 支持撤销/重做:通过保存命令历史实现
- 支持事务:可以实现命令的原子操作
缺点
- 类数量增加:每个命令都需要单独类
- 可能过度设计:简单操作使用命令模式会增加复杂度
- 性能开销:间接调用可能带来轻微性能损失
解决的问题
命令模式主要解决以下经典问题:
- 行为参数化:需要将操作作为参数传递时
- 操作队列:需要实现操作队列或调度时
- 撤销/重做:需要支持撤销操作功能时
- 事务系统:需要实现原子事务时
- GUI操作:菜单项、按钮等需要绑定不同操作时
实现注意事项
命令粒度:
- 粗粒度:一个命令执行多个操作
- 细粒度:一个命令执行单一原子操作
撤销实现:
- 反向操作:执行与命令相反的操作
- 状态恢复:保存执行前状态并在撤销时恢复
空命令对象:
- 可作为默认命令或占位符
- 简化调用者代码(无需判空)
命令持久化:
- 考虑命令序列化以实现持久化
- 可用于实现宏命令或脚本系统
线程安全:
- 多线程环境下命令对象可能需要同步
- 考虑命令的不可变性设计
实现变体
简单命令:
- 直接实现命令接口
- 适用于大多数基本场景
复合命令(宏命令):
- 包含多个子命令
- 按顺序执行一批命令
异步命令:
- 命令在后台线程执行
- 需要回调机制通知完成
持久化命令:
- 可序列化的命令
- 支持命令的存储和重放
与其他模式的关系
相似模式区分
策略模式:
- 命令:封装操作和接收者
- 策略:封装算法,无接收者概念
职责链模式:
- 命令:明确知道执行者
- 职责链:不确定哪个对象会处理请求
备忘录模式:
- 命令:用于实现撤销操作
- 备忘录:用于保存和恢复对象状态
常见搭配组合
- 命令 + 组合:实现宏命令(命令的批处理)
- 命令 + 备忘录:实现完善的撤销机制
- 命令 + 原型:通过克隆创建命令副本
- 命令 + 责任链:实现命令的动态路由
典型应用场景
- GUI按钮和菜单项操作
- 事务系统
- 撤销/重做功能
- 任务调度系统
- 远程控制
- 游戏中的输入处理
- 批处理脚本
代码示例(文本编辑器实现)
java
// 命令接口
interface Command {
void execute();
void undo();
}
// 接收者:文档类
class Document {
private StringBuilder content = new StringBuilder();
public void write(String text) {
content.append(text);
}
public void delete(int length) {
if (length > content.length()) {
content.setLength(0);
} else {
content.setLength(content.length() - length);
}
}
public String getContent() {
return content.toString();
}
}
// 具体命令:写入命令
class WriteCommand implements Command {
private Document document;
private String text;
public WriteCommand(Document document, String text) {
this.document = document;
this.text = text;
}
@Override
public void execute() {
document.write(text);
}
@Override
public void undo() {
document.delete(text.length());
}
}
// 调用者:菜单项
class MenuItem {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void click() {
command.execute();
}
}
// 客户端代码
public class TextEditor {
public static void main(String[] args) {
Document doc = new Document();
CommandHistory history = new CommandHistory();
// 创建命令
Command cmd1 = new WriteCommand(doc, "Hello ");
Command cmd2 = new WriteCommand(doc, "World!");
// 执行命令
cmd1.execute();
history.push(cmd1);
cmd2.execute();
history.push(cmd2);
System.out.println(doc.getContent()); // 输出: Hello World!
// 撤销
Command lastCmd = history.pop();
if (lastCmd != null) {
lastCmd.undo();
}
System.out.println(doc.getContent()); // 输出: Hello
}
}
// 简单的命令历史记录
class CommandHistory {
private Stack<Command> history = new Stack<>();
public void push(Command cmd) {
history.push(cmd);
}
public Command pop() {
if (history.isEmpty()) {
return null;
}
return history.pop();
}
}