跳到主要内容

命令模式(Command)

命令模式是一种行为型设计模式,它将请求封装为对象,从而允许用不同的请求对客户进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。

模式定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

核心要点

  1. 封装请求:将请求封装成对象
  2. 解耦调用者与接收者:调用者不需要知道接收者的具体实现
  3. 支持撤销:可以记录命令历史,支持撤销操作
  4. 支持队列:可以将命令放入队列延迟执行

问题场景

假设我们需要实现一个智能家居遥控器,控制多个设备(灯、电视、空调等):

// 问题代码:遥控器直接调用设备方法
public class RemoteControl {
private Light light;
private TV tv;
private AirConditioner ac;

public void pressLightOn() {
light.on();
}

public void pressLightOff() {
light.off();
}

public void pressTVOn() {
tv.on();
}

// 每增加一个设备,就要修改遥控器代码
}

存在的问题

  • 遥控器与具体设备紧耦合
  • 每增加新设备就要修改遥控器
  • 无法动态配置按钮功能
  • 无法实现撤销、宏命令等高级功能

解决方案

使用命令模式,将每个操作封装成独立的命令对象:

// 命令接口
public interface Command {
void execute();
void undo(); // 支持撤销
}

// 接收者:电灯
public class Light {
private String location;

public Light(String location) {
this.location = location;
}

public void on() {
System.out.println(location + " 的灯打开了");
}

public void off() {
System.out.println(location + " 的灯关闭了");
}
}

// 具体命令:开灯
public class LightOnCommand implements Command {
private Light light;

public LightOnCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.on();
}

@Override
public void undo() {
light.off();
}
}

// 具体命令:关灯
public class LightOffCommand implements Command {
private Light light;

public LightOffCommand(Light light) {
this.light = light;
}

@Override
public void execute() {
light.off();
}

@Override
public void undo() {
light.on();
}
}

// 空命令(空对象模式)
public class NoCommand implements Command {
@Override
public void execute() { }

@Override
public void undo() { }
}

// 调用者:遥控器
public class RemoteControl {
private Command[] onCommands;
private Command[] offCommands;
private Command undoCommand;

public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];

Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}

// 设置命令
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}

// 按下开按钮
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand = onCommands[slot];
}

// 按下关按钮
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand = offCommands[slot];
}

// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("\n------ 遥控器 ------\n");
for (int i = 0; i < onCommands.length; i++) {
sb.append("[槽位 ").append(i).append("] ")
.append(onCommands[i].getClass().getSimpleName())
.append(" ")
.append(offCommands[i].getClass().getSimpleName())
.append("\n");
}
sb.append("[撤销] ").append(undoCommand.getClass().getSimpleName()).append("\n");
return sb.toString();
}
}

// 使用示例
public class Client {
public static void main(String[] args) {
RemoteControl remote = new RemoteControl();

Light livingRoomLight = new Light("客厅");
Light bedroomLight = new Light("卧室");

remote.setCommand(0,
new LightOnCommand(livingRoomLight),
new LightOffCommand(livingRoomLight));

remote.setCommand(1,
new LightOnCommand(bedroomLight),
new LightOffCommand(bedroomLight));

System.out.println(remote);

remote.onButtonWasPushed(0);
remote.offButtonWasPushed(0);
remote.undoButtonWasPushed();
}
}

输出

------ 遥控器 ------
[槽位 0] LightOnCommand LightOffCommand
[槽位 1] LightOnCommand LightOffCommand
[槽位 2] NoCommand NoCommand
...

客厅 的灯打开了
客厅 的灯关闭了
客厅 的灯打开了

模式结构

组成部分

  • Command(命令接口):声明执行命令的接口
  • ConcreteCommand(具体命令):实现命令接口,绑定接收者和动作
  • Receiver(接收者):执行实际工作
  • Invoker(调用者):持有命令对象并调用命令
  • Client(客户端):创建具体命令对象并设置接收者

实现方式

1. 基本命令

// 命令接口
public interface Command {
void execute();
}

// 接收者
public class TextEditor {
private StringBuilder text = new StringBuilder();

public void write(String content) {
text.append(content);
System.out.println("写入: " + content);
}

public void delete(int length) {
String deleted = text.substring(text.length() - length);
text.delete(text.length() - length, text.length());
System.out.println("删除: " + deleted);
}

public String getText() {
return text.toString();
}
}

// 具体命令:写入
public class WriteCommand implements Command {
private TextEditor editor;
private String content;

public WriteCommand(TextEditor editor, String content) {
this.editor = editor;
this.content = content;
}

@Override
public void execute() {
editor.write(content);
}
}

// 具体命令:删除
public class DeleteCommand implements Command {
private TextEditor editor;
private int length;

public DeleteCommand(TextEditor editor, int length) {
this.editor = editor;
this.length = length;
}

@Override
public void execute() {
editor.delete(length);
}
}

// 调用者
public class EditorInvoker {
private Command command;

public void setCommand(Command command) {
this.command = command;
}

public void execute() {
command.execute();
}
}

2. 可撤销命令

// 可撤销命令接口
public interface UndoableCommand extends Command {
void undo();
}

// 写入命令(支持撤销)
public class WriteUndoableCommand implements UndoableCommand {
private TextEditor editor;
private String content;

public WriteUndoableCommand(TextEditor editor, String content) {
this.editor = editor;
this.content = content;
}

@Override
public void execute() {
editor.write(content);
}

@Override
public void undo() {
editor.delete(content.length());
}
}

// 命令历史
public class CommandHistory {
private Stack<UndoableCommand> history = new Stack<>();

public void push(UndoableCommand command) {
history.push(command);
}

public UndoableCommand pop() {
if (history.isEmpty()) {
return null;
}
return history.pop();
}

public boolean isEmpty() {
return history.isEmpty();
}
}

// 支持撤销的调用者
public class UndoableInvoker {
private CommandHistory history = new CommandHistory();

public void execute(UndoableCommand command) {
command.execute();
history.push(command);
}

public void undo() {
UndoableCommand command = history.pop();
if (command != null) {
command.undo();
}
}
}

3. 宏命令

// 宏命令(组合多个命令)
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();
}
}
}

// 使用
public class Client {
public static void main(String[] args) {
Light light = new Light("客厅");
TV tv = new TV();

MacroCommand partyMode = new MacroCommand();
partyMode.addCommand(new LightOnCommand(light));
partyMode.addCommand(new TVOnCommand(tv));

// 一键执行多个命令
partyMode.execute();
}
}

4. 队列命令

// 命令队列
public class CommandQueue {
private Queue<Command> queue = new LinkedList<>();

public void addCommand(Command command) {
queue.offer(command);
}

public void executeAll() {
while (!queue.isEmpty()) {
Command command = queue.poll();
command.execute();
}
}

public void executeOne() {
Command command = queue.poll();
if (command != null) {
command.execute();
}
}
}

实际应用案例

1. GUI 事件处理

GUI 按钮的点击事件使用命令模式:

// 命令接口
public interface ActionListener {
void actionPerformed();
}

// 按钮
public class Button {
private String label;
private ActionListener listener;

public Button(String label) {
this.label = label;
}

public void setActionListener(ActionListener listener) {
this.listener = listener;
}

public void click() {
if (listener != null) {
listener.actionPerformed();
}
}
}

// 使用
Button saveButton = new Button("保存");
saveButton.setActionListener(() -> {
System.out.println("保存文件...");
});

saveButton.click();

2. 线程池任务

线程池中的 Runnable 就是命令模式的应用:

// Runnable 就是命令接口
public class EmailTask implements Runnable {
private String to;
private String subject;
private String content;

public EmailTask(String to, String subject, String content) {
this.to = to;
this.subject = subject;
this.content = content;
}

@Override
public void run() {
System.out.println("发送邮件给: " + to);
// 发送邮件逻辑
}
}

// 使用
ExecutorService executor = Executors.newFixedThreadPool(5);
executor.execute(new EmailTask("[email protected]", "主题", "内容"));

3. 数据库事务

数据库事务使用命令模式实现:

// 事务命令
public class TransactionCommand implements Command {
private Connection connection;
private List<Command> commands = new ArrayList<>();

public TransactionCommand(Connection connection) {
this.connection = connection;
}

public void addCommand(Command command) {
commands.add(command);
}

@Override
public void execute() {
try {
connection.setAutoCommit(false);

for (Command command : commands) {
command.execute();
}

connection.commit();
} catch (Exception e) {
connection.rollback();
throw new RuntimeException(e);
}
}
}

4. 文本编辑器撤销/重做

// 文本编辑器
public class TextEditorApp {
private StringBuilder text = new StringBuilder();
private Stack<Command> undoStack = new Stack<>();
private Stack<Command> redoStack = new Stack<>();

public void execute(Command command) {
command.execute();
undoStack.push(command);
redoStack.clear();
}

public void undo() {
if (!undoStack.isEmpty()) {
Command command = undoStack.pop();
command.undo();
redoStack.push(command);
}
}

public void redo() {
if (!redoStack.isEmpty()) {
Command command = redoStack.pop();
command.execute();
undoStack.push(command);
}
}

public void write(String content) {
execute(new WriteCommand(this, content));
}

// 内部命令类
private class WriteCommand implements Command {
private TextEditorApp editor;
private String content;

WriteCommand(TextEditorApp editor, String content) {
this.editor = editor;
this.content = content;
}

@Override
public void execute() {
editor.text.append(content);
}

@Override
public void undo() {
int start = editor.text.length() - content.length();
editor.text.delete(start, editor.text.length());
}
}
}

命令模式 vs 策略模式

特性命令模式策略模式
目的封装请求封装算法
关注点请求的执行算法的选择
可撤销支持不支持
队列支持不支持

优缺点分析

优点

  1. 解耦:调用者与接收者解耦
  2. 可扩展:新增命令不影响现有代码
  3. 可撤销:支持撤销和重做操作
  4. 可队列:支持命令队列和延迟执行
  5. 可组合:支持宏命令

缺点

  1. 类数量增加:每个命令都需要一个类
  2. 复杂度增加:增加了系统的复杂度
  3. 调试困难:命令链可能难以调试

使用建议

何时使用命令模式

  • 需要将请求调用者和接收者解耦
  • 需要支持撤销/重做操作
  • 需要将请求排队或记录日志
  • 需要支持宏命令

何时避免使用命令模式

  • 命令简单,不需要封装
  • 不需要撤销、队列等功能
  • 命令模式会增加不必要的复杂度

最佳实践

  1. 使用空命令:避免空指针检查
  2. 支持撤销:实现 undo 方法
  3. 考虑宏命令:组合多个命令
  4. 命名清晰:命令类名应体现其功能

小结

命令模式将请求封装为对象:

应用场景示例
GUI 事件按钮点击
撤销/重做文本编辑器
任务队列线程池
宏命令一键操作

练习

  1. 实现一个计算器,支持撤销和重做操作
  2. 实现一个智能家居场景,支持宏命令
  3. 实现一个命令队列,支持延迟执行
  4. 实现一个日志记录功能,记录所有执行的命令

参考资源