命令模式(Command)
命令模式是一种行为型设计模式,它将请求封装为对象,从而允许用不同的请求对客户进行参数化、对请求排队或记录请求日志,以及支持可撤销的操作。
模式定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
核心要点
- 封装请求:将请求封装成对象
- 解耦调用者与接收者:调用者不需要知道接收者的具体实现
- 支持撤销:可以记录命令历史,支持撤销操作
- 支持队列:可以将命令放入队列延迟执行
问题场景
假设我们需要实现一个智能家居遥控器,控制多个设备(灯、电视、空调等):
// 问题代码:遥控器直接调用设备方法
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 策略模式
| 特性 | 命令模式 | 策略模式 |
|---|---|---|
| 目的 | 封装请求 | 封装算法 |
| 关注点 | 请求的执行 | 算法的选择 |
| 可撤销 | 支持 | 不支持 |
| 队列 | 支持 | 不支持 |
优缺点分析
优点
- 解耦:调用者与接收者解耦
- 可扩展:新增命令不影响现有代码
- 可撤销:支持撤销和重做操作
- 可队列:支持命令队列和延迟执行
- 可组合:支持宏命令
缺点
- 类数量增加:每个命令都需要一个类
- 复杂度增加:增加了系统的复杂度
- 调试困难:命令链可能难以调试
使用建议
何时使用命令模式
- 需要将请求调用者和接收者解耦
- 需要支持撤销/重做操作
- 需要将请求排队或记录日志
- 需要支持宏命令
何时避免使用命令模式
- 命令简单,不需要封装
- 不需要撤销、队列等功能
- 命令模式会增加不必要的复杂度
最佳实践
- 使用空命令:避免空指针检查
- 支持撤销:实现 undo 方法
- 考虑宏命令:组合多个命令
- 命名清晰:命令类名应体现其功能
小结
命令模式将请求封装为对象:
| 应用场景 | 示例 |
|---|---|
| GUI 事件 | 按钮点击 |
| 撤销/重做 | 文本编辑器 |
| 任务队列 | 线程池 |
| 宏命令 | 一键操作 |
练习
- 实现一个计算器,支持撤销和重做操作
- 实现一个智能家居场景,支持宏命令
- 实现一个命令队列,支持延迟执行
- 实现一个日志记录功能,记录所有执行的命令