一、核心概念
命令模式是一种行为型设计模式,它将请求封装为一个对象,从而使你可以用不同的请求对客户进行参数化,支持请求的排队、记录日志,以及支持可撤销的操作。
设计思想
- 解耦请求发送者与接收者:发送者不需要知道接收者的具体接口
- 将"做什么"和"谁来做"分离:命令对象封装了执行操作所需的所有信息
- 支持事务性操作:命令可以支持撤销(undo)和重做(redo)
二、模式结构
类图示意
复制
┌─────────────────┐ ┌───────────────┐ ┌───────────────┐
│ Client │───────│ Invoker │ │ Receiver │
└─────────────────┘ └───────────────┘ └───────────────┘
│ │ │
│ │ commands │
│ ▼ │
│ ┌─────────────────┐ │
└───────────────│ Command │◄──────────────┘
│ (Interface) │
│ +execute() │
│ +undo() │
└─────────────────┘
△
┌────────────────┼────────────────┐
│ │
┌─────────────────────┐ ┌─────────────────────┐
│ ConcreteCommandA │ │ ConcreteCommandB │
│ -receiver: Receiver │ │ -receiver: Receiver │
│ +execute() │ │ +execute() │
│ +undo() │ │ +undo() │
└─────────────────────┘ └─────────────────────┘
核心角色
-
Command (命令接口)
- 声明执行操作的接口
-
ConcreteCommand (具体命令)
- 实现Command接口
- 将接收者绑定到动作
- 调用接收者的操作
-
Invoker (调用者)
- 要求命令执行请求
- 持有命令对象
-
Receiver (接收者)
- 知道如何执行操作
- 实际的工作执行者
-
Client (客户端)
- 创建具体命令并设置接收者
三、Java代码实现
基础实现示例
java
java
下载
复制
// 1. 命令接口
public interface Command {
void execute();
void undo();
}
// 2. 接收者 - 实际执行操作
public class TextEditor {
private StringBuilder text = new StringBuilder();
private int cursorPosition = 0;
public void insert(String newText) {
text.insert(cursorPosition, newText);
cursorPosition += newText.length();
System.out.println("插入文本: " + newText);
System.out.println("当前文本: " + text);
}
public void delete(int length) {
if (cursorPosition - length >= 0) {
int start = cursorPosition - length;
String deleted = text.substring(start, cursorPosition);
text.delete(start, cursorPosition);
cursorPosition = start;
System.out.println("删除文本: " + deleted);
System.out.println("当前文本: " + text);
}
}
public void setCursor(int position) {
this.cursorPosition = Math.max(0, Math.min(position, text.length()));
}
public String getText() {
return text.toString();
}
}
// 3. 具体命令 - 插入文本
public class InsertCommand implements Command {
private TextEditor editor;
private String text;
private int position;
public InsertCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
this.position = 0; // 简化处理
}
@Override
public void execute() {
editor.insert(text);
}
@Override
public void undo() {
editor.delete(text.length());
}
}
// 4. 具体命令 - 删除文本
public class DeleteCommand implements Command {
private TextEditor editor;
private int length;
private String deletedText; // 用于撤销
public DeleteCommand(TextEditor editor, int length) {
this.editor = editor;
this.length = length;
}
@Override
public void execute() {
// 实际应用中需要保存被删除的文本以便撤销
editor.delete(length);
}
@Override
public void undo() {
// 重新插入被删除的文本
editor.insert(deletedText);
}
}
// 5. 调用者 - 支持撤销/重做
public class CommandInvoker {
private Stack<Command> history = new Stack<>();
private Stack<Command> redoStack = new Stack<>();
public void executeCommand(Command command) {
command.execute();
history.push(command);
redoStack.clear(); // 执行新命令时清空重做栈
}
public void undo() {
if (!history.isEmpty()) {
Command command = history.pop();
command.undo();
redoStack.push(command);
}
}
public void redo() {
if (!redoStack.isEmpty()) {
Command command = redoStack.pop();
command.execute();
history.push(command);
}
}
public void showHistory() {
System.out.println("命令历史:");
for (Command cmd : history) {
System.out.println(" " + cmd.getClass().getSimpleName());
}
}
}
// 6. 客户端使用
public class CommandPatternDemo {
public static void main(String[] args) {
// 创建接收者
TextEditor editor = new TextEditor();
// 创建调用者
CommandInvoker invoker = new CommandInvoker();
// 创建并执行命令
Command insertHello = new InsertCommand(editor, "Hello ");
invoker.executeCommand(insertHello);
Command insertWorld = new InsertCommand(editor, "World!");
invoker.executeCommand(insertWorld);
// 撤销操作
System.out.println("\n--- 执行撤销 ---");
invoker.undo();
// 重做操作
System.out.println("\n--- 执行重做 ---");
invoker.redo();
// 显示历史
invoker.showHistory();
}
}
高级示例:支持宏命令
java
java
下载
复制
// 宏命令 - 组合多个命令
public class MacroCommand implements Command {
private List<Command> commands = new ArrayList<>();
public void addCommand(Command command) {
commands.add(command);
}
public void removeCommand(Command command) {
commands.remove(command);
}
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
@Override
public void undo() {
// 按相反顺序撤销
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
}
}
// 客户端使用宏命令
public class MacroCommandDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandInvoker invoker = new CommandInvoker();
// 创建宏命令
MacroCommand initDocument = new MacroCommand();
initDocument.addCommand(new InsertCommand(editor, "标题\n"));
initDocument.addCommand(new InsertCommand(editor, "===正文===\n"));
initDocument.addCommand(new InsertCommand(editor, "第一段内容"));
// 执行宏命令
invoker.executeCommand(initDocument);
System.out.println("最终文本: " + editor.getText());
}
}
四、应用场景
1. GUI操作与按钮事件
java
java
下载
复制
// 按钮点击事件
public class Button {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void onClick() {
if (command != null) {
command.execute();
}
}
}
// 菜单项
public class MenuItem {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void onSelect() {
command.execute();
}
}
2. 任务队列与线程池
java
java
下载
复制
public class TaskScheduler {
private Queue<Command> taskQueue = new LinkedList<>();
public void addTask(Command task) {
taskQueue.offer(task);
}
public void processTasks() {
ExecutorService executor = Executors.newFixedThreadPool(5);
while (!taskQueue.isEmpty()) {
Command task = taskQueue.poll();
executor.submit(() -> {
try {
task.execute();
} catch (Exception e) {
// 记录日志,但不影响其他任务
System.err.println("任务执行失败: " + e.getMessage());
}
});
}
executor.shutdown();
}
}
3. 事务性系统
java
java
下载
复制
// 数据库事务命令
public class TransactionManager {
private List<Command> commands = new ArrayList<>();
private List<Command> executedCommands = new ArrayList<>();
public void addCommand(Command command) {
commands.add(command);
}
public void commit() {
try {
for (Command cmd : commands) {
cmd.execute();
executedCommands.add(cmd);
}
commands.clear();
} catch (Exception e) {
rollback();
throw new RuntimeException("事务提交失败", e);
}
}
public void rollback() {
for (int i = executedCommands.size() - 1; i >= 0; i--) {
executedCommands.get(i).undo();
}
executedCommands.clear();
}
}
4. 远程过程调用(RPC)
java
java
下载
复制
// 网络命令
public class NetworkCommand implements Command, Serializable {
private String serviceName;
private String methodName;
private Object[] parameters;
@Override
public void execute() {
// 通过网络发送命令到服务器执行
RemoteExecutor.execute(this);
}
@Override
public void undo() {
// 远程撤销操作
RemoteExecutor.undo(this);
}
}
5. 游戏开发
java
java
下载
复制
// 游戏动作命令
public class GameActionCommand implements Command {
private GameCharacter character;
private Action action;
private Position previousPosition;
@Override
public void execute() {
previousPosition = character.getPosition();
character.performAction(action);
}
@Override
public void undo() {
character.setPosition(previousPosition);
character.undoAction(action);
}
}
五、优缺点分析
优点
- 单一职责原则:解耦触发操作的类与执行操作的对象
- 开闭原则:无需修改现有代码即可添加新命令
- 支持撤销/重做:轻松实现操作的回滚
- 命令队列:支持延迟执行、任务调度
- 宏命令:支持组合简单命令创建复杂操作
- 易于测试:命令对象可以独立测试
缺点
- 类数量增加:每个具体命令都需要一个单独的类
- 复杂度增加:简单操作使用命令模式可能过度设计
- 内存占用:维护命令历史可能消耗较多内存
- 执行效率:间接调用比直接调用效率稍低
六、改进与变体
1. 使用函数式接口简化(Java 8+)
java
java
下载
复制
@FunctionalInterface
public interface SimpleCommand {
void execute();
default SimpleCommand andThen(SimpleCommand after) {
return () -> {
execute();
after.execute();
};
}
}
// 使用Lambda表达式
public class FunctionalCommandDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
SimpleCommand insert = () -> editor.insert("Hello");
SimpleCommand append = () -> editor.insert(" World");
// 链式调用
insert.andThen(append).execute();
}
}
2. 支持参数的命令
java
java
下载
复制
public interface ParamCommand<T> {
void execute(T parameter);
void undo(T parameter);
}
public class InsertAtCommand implements ParamCommand<String> {
private TextEditor editor;
private int position;
@Override
public void execute(String text) {
editor.setCursor(position);
editor.insert(text);
}
@Override
public void undo(String text) {
editor.delete(text.length());
}
}
3. 命令工厂模式
java
java
下载
复制
public class CommandFactory {
private Map<String, Supplier<Command>> commands = new HashMap<>();
public CommandFactory() {
// 注册命令
commands.put("insert", InsertCommand::new);
commands.put("delete", DeleteCommand::new);
}
public Command createCommand(String type, Object... args) {
Supplier<Command> supplier = commands.get(type);
if (supplier != null) {
// 这里需要根据参数动态创建命令实例
return supplier.get();
}
throw new IllegalArgumentException("未知命令类型: " + type);
}
}
4. 持久化命令
java
java
下载
复制
public abstract class PersistentCommand implements Command, Serializable {
private Date timestamp = new Date();
private String userId;
public abstract String toLogString();
public void saveToDatabase() {
// 保存命令到数据库
Database.save(this.getClass().getSimpleName(),
toLogString(), timestamp, userId);
}
}
七、注意事项
1. 避免过度设计
- 简单操作不要使用命令模式
- 只在确实需要解耦、撤销、队列等功能时使用
2. 内存管理
- 命令历史可能导致内存泄漏
- 考虑实现命令的序列化以存储到磁盘
- 设置命令历史的最大长度
java
java
下载
复制
public class LimitedHistoryInvoker extends CommandInvoker {
private final int maxHistorySize;
public LimitedHistoryInvoker(int maxHistorySize) {
this.maxHistorySize = maxHistorySize;
}
@Override
public void executeCommand(Command command) {
super.executeCommand(command);
// 限制历史记录大小
if (history.size() > maxHistorySize) {
// 移除最旧的命令
// 注意:可能需要特殊处理,如合并命令或持久化到磁盘
}
}
}
3. 线程安全
java
java
下载
复制
public class ThreadSafeInvoker {
private final Stack<Command> history = new Stack<>();
private final Lock lock = new ReentrantLock();
public void executeCommand(Command command) {
lock.lock();
try {
command.execute();
history.push(command);
} finally {
lock.unlock();
}
}
}
4. 错误处理
java
java
下载
复制
public interface RobustCommand extends Command {
boolean canExecute();
boolean canUndo();
void onExecuteError(Exception e);
void onUndoError(Exception e);
}
5. 性能考虑
- 对于高频操作,考虑使用对象池
- 避免在命令中存储大量数据
- 考虑使用享元模式共享接收者状态
八、与其他模式的关系
与策略模式对比
- 命令模式:关注"做什么"和"何时做",强调动作的封装和执行时机
- 策略模式:关注"如何做",强调算法或策略的替换
与备忘录模式结合
java
java
下载
复制
// 结合备忘录实现更完善的撤销
public class MementoCommand implements Command {
private Receiver receiver;
private Memento memento;
@Override
public void execute() {
memento = receiver.createMemento();
// 执行操作
}
@Override
public void undo() {
receiver.restoreMemento(memento);
}
}
与组合模式结合
- 宏命令是组合模式的典型应用
- 可以创建命令树结构
九、实际应用建议
适用场景
- 需要操作队列的场景:批处理、任务调度
- 需要支持撤销/重做的场景:文本编辑器、图形编辑器
- 需要记录操作日志的场景:审计系统、操作追踪
- 需要解耦调用者和接收者的场景:GUI框架、事件处理
- 需要支持事务的场景:数据库操作、支付系统
不适用场景
- 简单的一次性操作
- 性能要求极高的场景
- 命令间无共同接口的场景
最佳实践
- 尽量使命令接口简单,通常只需要
execute()和undo()方法 - 考虑使用泛型使命令更灵活
- 为复杂命令提供构建器
- 使用依赖注入管理命令依赖
- 为生产环境添加适当的日志和监控
十、总结
命令模式通过将请求封装为对象,提供了强大的灵活性和可扩展性。它在需要支持撤销、事务、队列、日志等功能的系统中表现出色。虽然会引入额外的复杂性,但在适当的场景下,它能显著提高代码的可维护性和可扩展性。
现代Java开发中,可以结合函数式编程特性简化命令模式的实现,同时保持其核心优势。在实际应用中,应根据具体需求权衡使用,避免不必要的过度设计。