命令模式(Command Pattern)
一、模式概述
命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
核心思想:将"请求"封装成对象,使得请求的发送者和接收者解耦。调用者(Invoker)不需要知道具体执行什么操作,只需要调用命令对象的 execute 方法即可。
二、角色说明
| 角色 | 说明 |
|---|---|
| Command(抽象命令) | 声明执行操作的接口(execute、undo) |
| ConcreteCommand(具体命令) | 将接收者与动作绑定,实现 execute 和 undo |
| Invoker(调用者) | 要求命令执行请求,持有命令对象的引用 |
| Receiver(接收者) | 知道如何实施与执行请求相关的操作 |
| Client(客户端) | 创建具体命令对象并设置其接收者 |
三、生活中的例子
- 餐厅点餐:顾客(调用者)写菜单(命令),服务员传递菜单,厨师(接收者)执行
- 遥控器:按下按钮(命令),电视(接收者)执行对应操作
- 事务日志:数据库操作封装为命令,支持回滚(undo)和重做(redo)
- 任务队列:将任务封装为命令,放入队列中异步执行
四、示例1:智能家居遥控器
问题场景
一个智能遥控器可以控制灯光、空调、音响等多个设备,支持执行和撤销操作。
java
import java.util.Stack;
// ========== 接收者 ==========
/**
* 灯光(接收者)
*/
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 void dim(int level) {
System.out.println(" [灯光] " + location + " 灯亮度调至 " + level + "%");
}
}
/**
* 空调(接收者)
*/
class AirConditioner {
private String location;
public AirConditioner(String location) {
this.location = location;
}
public void on() {
System.out.println(" [空调] " + location + " 空调已开启");
}
public void off() {
System.out.println(" [空调] " + location + " 空调已关闭");
}
public void setTemperature(int temp) {
System.out.println(" [空调] " + location + " 温度设为 " + temp + "°C");
}
}
/**
* 音响(接收者)
*/
class Stereo {
public void on() {
System.out.println(" [音响] 音响已开启");
}
public void off() {
System.out.println(" [音响] 音响已关闭");
}
public void setVolume(int volume) {
System.out.println(" [音响] 音量设为 " + volume);
}
public void setSource(String source) {
System.out.println(" [音响] 音源设为: " + source);
}
}
/**
* 电视(接收者)
*/
class Television {
public void on() {
System.out.println(" [电视] 电视已开启");
}
public void off() {
System.out.println(" [电视] 电视已关闭");
}
public void setChannel(int channel) {
System.out.println(" [电视] 切换到频道 " + channel);
}
}
// ========== 抽象命令 ==========
/**
* 抽象命令接口
*/
interface SmartCommand {
void execute();
void undo();
String getDescription();
}
// ========== 具体命令 ==========
/**
* 开灯命令
*/
class LightOnCommand implements SmartCommand {
private Light light;
private boolean previousState;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
previousState = false; // 记录之前的关灯状态
light.on();
}
@Override
public void undo() {
light.off(); // 撤销就是关灯
}
@Override
public String getDescription() {
return "开灯";
}
}
/**
* 关灯命令
*/
class LightOffCommand implements SmartCommand {
private Light light;
private boolean previousState;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
previousState = true; // 记录之前的开灯状态
light.off();
}
@Override
public void undo() {
light.on(); // 撤销就是开灯
}
@Override
public String getDescription() {
return "关灯";
}
}
/**
* 空调制冷命令
*/
class ACOnCommand implements SmartCommand {
private AirConditioner ac;
private int previousTemp;
public ACOnCommand(AirConditioner ac) {
this.ac = ac;
}
@Override
public void execute() {
previousTemp = 26; // 记录之前的温度
ac.on();
ac.setTemperature(24);
}
@Override
public void undo() {
ac.off();
ac.setTemperature(previousTemp);
}
@Override
public String getDescription() {
return "空调制冷";
}
}
/**
* 音响开机命令
*/
class StereoOnCommand implements SmartCommand {
private Stereo stereo;
public StereoOnCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.on();
stereo.setVolume(15);
stereo.setSource("蓝牙");
}
@Override
public void undo() {
stereo.off();
}
@Override
public String getDescription() {
return "音响开机";
}
}
/**
* 电视换台命令
*/
class TVSetChannelCommand implements SmartCommand {
private Television tv;
private int channel;
private int previousChannel;
public TVSetChannelCommand(Television tv, int channel) {
this.tv = tv;
this.channel = channel;
}
@Override
public void execute() {
previousChannel = 1; // 记录之前的频道
tv.setChannel(channel);
}
@Override
public void undo() {
tv.setChannel(previousChannel);
}
@Override
public String getDescription() {
return "电视换台到频道 " + channel;
}
}
/**
* 空命令(占位用,避免空指针检查)
*/
class NoCommand implements SmartCommand {
@Override
public void execute() {
// 什么都不做
}
@Override
public void undo() {
// 什么都不做
}
@Override
public String getDescription() {
return "无操作";
}
}
// ========== 宏命令(组合多个命令) ==========
/**
* 宏命令:一次性执行多个命令
*/
class MacroCommand implements SmartCommand {
private SmartCommand[] commands;
private String description;
public MacroCommand(String description, SmartCommand... commands) {
this.description = description;
this.commands = commands;
}
@Override
public void execute() {
System.out.println(" 执行宏命令: " + description);
for (SmartCommand command : commands) {
command.execute();
}
}
@Override
public void undo() {
System.out.println(" 撤销宏命令: " + description);
// 撤销时反向执行
for (int i = commands.length - 1; i >= 0; i--) {
commands[i].undo();
}
}
@Override
public String getDescription() {
return description;
}
}
// ========== 调用者 ==========
/**
* 智能遥控器(调用者)
* 持有命令对象的引用,调用命令的 execute 方法
*/
class SmartRemote {
private SmartCommand[] slotCommands; // 按钮槽位
private SmartCommand[] slotUndos; // 撤销槽位
private Stack<SmartCommand> history; // 命令历史(支持撤销)
public SmartRemote(int slots) {
this.slotCommands = new SmartCommand[slots];
this.slotUndos = new SmartCommand[slots];
this.history = new Stack<>();
// 初始化为空命令
for (int i = 0; i < slots; i++) {
slotCommands[i] = new NoCommand();
slotUndos[i] = new NoCommand();
}
}
/**
* 设置按钮对应的命令
*/
public void setCommand(int slot, SmartCommand onCommand, SmartCommand offCommand) {
slotCommands[slot] = onCommand;
slotUndos[slot] = offCommand;
}
/**
* 按下按钮(执行命令)
*/
public void pressOnButton(int slot) {
System.out.println("按下按钮 " + (slot + 1) + ": " + slotCommands[slot].getDescription());
slotCommands[slot].execute();
history.push(slotCommands[slot]); // 记录到历史
}
/**
* 按下关闭按钮
*/
public void pressOffButton(int slot) {
System.out.println("按下按钮 " + (slot + 1) + ": " + slotUndos[slot].getDescription());
slotUndos[slot].execute();
history.push(slotUndos[slot]);
}
/**
* 撤销上一步操作
*/
public void pressUndo() {
if (!history.isEmpty()) {
SmartCommand lastCommand = history.pop();
System.out.println("撤销操作: " + lastCommand.getDescription());
lastCommand.undo();
} else {
System.out.println("没有可撤销的操作!");
}
}
}
// ========== 客户端代码 ==========
public class SmartHomeDemo {
public static void main(String[] args) {
// 创建接收者
Light livingRoomLight = new Light("客厅");
Light bedroomLight = new Light("卧室");
AirConditioner ac = new AirConditioner("客厅");
Stereo stereo = new Stereo();
Television tv = new Television();
// 创建命令
SmartCommand livingLightOn = new LightOnCommand(livingRoomLight);
SmartCommand livingLightOff = new LightOffCommand(livingRoomLight);
SmartCommand bedroomLightOn = new LightOnCommand(bedroomLight);
SmartCommand bedroomLightOff = new LightOffCommand(bedroomLight);
SmartCommand acOn = new ACOnCommand(ac);
SmartCommand stereoOn = new StereoOnCommand(stereo);
SmartCommand tvChannel = new TVSetChannelCommand(tv, 5);
// 创建遥控器
SmartRemote remote = new SmartRemote(5);
// 设置按钮
remote.setCommand(0, livingLightOn, livingLightOff);
remote.setCommand(1, bedroomLightOn, bedroomLightOff);
remote.setCommand(2, acOn, new NoCommand());
remote.setCommand(3, stereoOn, new NoCommand());
remote.setCommand(4, tvChannel, new NoCommand());
// 使用遥控器
System.out.println("===== 操作1:打开客厅灯 =====");
remote.pressOnButton(0);
System.out.println("\n===== 操作2:打开卧室灯 =====");
remote.pressOnButton(1);
System.out.println("\n===== 操作3:打开空调 =====");
remote.pressOnButton(2);
System.out.println("\n===== 撤销上一步 =====");
remote.pressUndo();
System.out.println("\n===== 宏命令:回家模式 =====");
SmartCommand homeMode = new MacroCommand("回家模式",
livingLightOn, acOn, stereoOn);
homeMode.execute();
System.out.println("\n===== 撤销回家模式 =====");
homeMode.undo();
}
}五、示例2:文本编辑器撤销/重做系统
问题场景
文本编辑器支持输入、删除操作,并支持撤销(undo)和重做(redo)。
java
import java.util.Stack;
import java.util.ArrayList;
import java.util.List;
// ========== 接收者 ==========
/**
* 文本编辑器(接收者)
*/
class TextEditor {
private StringBuilder content = new StringBuilder();
public void insert(int position, String text) {
content.insert(position, text);
System.out.println(" [编辑器] 在位置 " + position + " 插入: \"" + text + "\"");
System.out.println(" [当前内容] \"" + content + "\"");
}
public String delete(int position, int length) {
String deleted = content.substring(position, position + length);
content.delete(position, position + length);
System.out.println(" [编辑器] 从位置 " + position + " 删除 " + length + " 个字符: \"" + deleted + "\"");
System.out.println(" [当前内容] \"" + content + "\"");
return deleted;
}
public String getContent() {
return content.toString();
}
}
// ========== 抽象命令 ==========
/**
* 编辑命令(支持撤销和重做)
*/
interface EditCommand {
void execute();
void undo();
void redo();
}
// ========== 具体命令 ==========
/**
* 输入命令
*/
class InsertCommand implements EditCommand {
private TextEditor editor;
private int position;
private String text;
public InsertCommand(TextEditor editor, int position, String text) {
this.editor = editor;
this.position = position;
this.text = text;
}
@Override
public void execute() {
editor.insert(position, text);
}
@Override
public void undo() {
editor.delete(position, text.length());
}
@Override
public void redo() {
editor.insert(position, text);
}
}
/**
* 删除命令
*/
class DeleteCommand implements EditCommand {
private TextEditor editor;
private int position;
private int length;
private String deletedText; // 保存删除的文本,用于撤销
public DeleteCommand(TextEditor editor, int position, int length) {
this.editor = editor;
this.position = position;
this.length = length;
}
@Override
public void execute() {
deletedText = editor.delete(position, length);
}
@Override
public void undo() {
editor.insert(position, deletedText);
}
@Override
public void redo() {
editor.delete(position, length);
}
}
// ========== 命令管理器(支持 undo/redo) ==========
/**
* 命令管理器
* 管理命令历史,支持撤销和重做
*/
class CommandHistory {
private Stack<EditCommand> undoStack = new Stack<>();
private Stack<EditCommand> redoStack = new Stack<>();
/**
* 执行命令并记录
*/
public void executeCommand(EditCommand command) {
command.execute();
undoStack.push(command);
redoStack.clear(); // 新命令清空重做栈
System.out.println(" [历史] 已执行,可撤销 " + undoStack.size() + " 步");
}
/**
* 撤销
*/
public boolean undo() {
if (undoStack.isEmpty()) {
System.out.println(" [历史] 没有可撤销的操作!");
return false;
}
EditCommand command = undoStack.pop();
command.undo();
redoStack.push(command);
System.out.println(" [历史] 已撤销,可重做 " + redoStack.size() + " 步");
return true;
}
/**
* 重做
*/
public boolean redo() {
if (redoStack.isEmpty()) {
System.out.println(" [历史] 没有可重做的操作!");
return false;
}
EditCommand command = redoStack.pop();
command.redo();
undoStack.push(command);
System.out.println(" [历史] 已重做,可撤销 " + undoStack.size() + " 步");
return true;
}
/**
* 显示历史
*/
public void showHistory() {
System.out.println(" [历史] 撤销栈: " + undoStack.size() + " 步");
System.out.println(" [历史] 重做栈: " + redoStack.size() + " 步");
}
}
// ========== 客户端代码 ==========
public class TextEditorDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandHistory history = new CommandHistory();
System.out.println("===== 输入文本 =====");
history.executeCommand(new InsertCommand(editor, 0, "Hello"));
history.executeCommand(new InsertCommand(editor, 5, " World"));
history.executeCommand(new InsertCommand(editor, 11, " Java"));
System.out.println("\n===== 撤销最后一步 =====");
history.undo();
System.out.println("\n===== 再撤销一步 =====");
history.undo();
System.out.println("\n===== 重做 =====");
history.redo();
System.out.println("\n===== 删除文本 =====");
history.executeCommand(new DeleteCommand(editor, 5, 6));
System.out.println("\n===== 撤销删除 =====");
history.undo();
System.out.println("\n===== 最终内容 =====");
System.out.println(" 编辑器内容: \"" + editor.getContent() + "\"");
history.showHistory();
}
}六、UML 类图
┌────────────┐ 调用 ┌──────────────┐
│ Client │─────────────▶│ Invoker │
└────────────┘ │ (Remote) │
│ +pressButton()│
└───────┬───────┘
│ 持有
▼
┌──────────────┐
│ Command │ ◀── 抽象命令
│ +execute() │
│ +undo() │
└──────┬───────┘
│ 实现
┌──────────────┼──────────────┐
│ │ │
┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
│Concrete │ │Concrete │ │Macro │
│Command1 │ │Command2 │ │Command │
│+execute() │ │+execute() │ │+execute() │
└─────┬──────┘ └─────┬──────┘ └───────────┘
│ │
▼ ▼
┌─────────────────────────┐
│ Receiver │
│ (Light, TV, Stereo) │
└─────────────────────────┘七、命令队列(异步执行)
java
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 命令队列:支持异步执行命令
*/
class CommandQueue {
private final BlockingQueue<SmartCommand> queue = new LinkedBlockingQueue<>();
private volatile boolean running = true;
public void start() {
Thread worker = new Thread(() -> {
while (running) {
try {
SmartCommand command = queue.take(); // 阻塞等待命令
System.out.println("[命令队列] 执行命令: " + command.getDescription());
command.execute();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}, "CommandWorker");
worker.setDaemon(true);
worker.start();
}
public void addCommand(SmartCommand command) {
try {
queue.put(command);
System.out.println("[命令队列] 命令入队: " + command.getDescription()
+ " (队列大小: " + queue.size() + ")");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void stop() {
running = false;
}
}
// ========== 客户端代码 ==========
public class CommandQueueDemo {
public static void main(String[] args) throws InterruptedException {
CommandQueue queue = new CommandQueue();
queue.start();
// 添加命令到队列
Light light = new Light("客厅");
queue.addCommand(new LightOnCommand(light));
queue.addCommand(new LightOffCommand(light));
queue.addCommand(new LightOnCommand(light));
Thread.sleep(1000); // 等待命令执行完成
queue.stop();
}
}八、实际应用场景
java
/**
* 1. Spring 事务管理中的命令模式
*
* @Transactional
* public void transferMoney(Account from, Account to, double amount) {
* from.debit(amount); // 命令1
* to.credit(amount); // 命令2
* // 事务提交或回滚(undo)
* }
*/
/**
* 2. 数据库操作日志
*
* 所有数据库操作都封装为命令对象,
* 可以记录到日志中,支持回放和审计
*/
/**
* 3. 任务调度系统
*
* 定时任务 = 命令 + 触发器
* Timer.schedule(command, delay, period)
*/
/**
* 4. GUI 按钮和菜单
*
* 每个按钮绑定一个命令对象
* button.addActionListener(e -> command.execute());
*/九、优缺点分析
优点
- 解耦:调用者和接收者之间没有直接关系
- 可扩展:新增命令不需要修改现有代码(开闭原则)
- 支持撤销/重做:通过记录命令历史实现 undo/redo
- 支持命令队列:可以将命令放入队列中异步执行
- 支持宏命令:可以组合多个命令为一个复合命令
- 支持日志和事务:可以记录命令的执行历史
缺点
- 类数量增加:每个命令都需要一个具体的命令类
- 复杂度增加:对于简单场景,引入命令模式反而增加了复杂度
- 过度设计:如果只是简单的请求-响应,不需要命令模式
十、使用场景
- GUI 按钮和菜单:将用户操作封装为命令
- 撤销/重做功能:文本编辑器、图形编辑器
- 任务队列:将任务封装为命令,异步执行
- 事务管理:数据库操作的提交和回滚
- 日志记录:记录所有操作,支持回放
- 宏命令:将多个命令组合为一个复合命令
十一、与其他模式的关系
- 策略模式 vs 命令模式:策略模式关注算法的选择,命令模式关注请求的封装
- 备忘录模式:命令模式常与备忘录模式结合,实现撤销功能
- 组合模式:宏命令使用组合模式来组合多个命令
- 观察者模式:命令模式可以与观察者模式结合,实现命令的监听
十二、最佳实践
- 为每个命令实现 undo 方法:这是命令模式的核心价值之一
- 使用空命令模式:避免空指针检查(Null Object Pattern)
- 考虑命令队列:对于耗时操作,使用命令队列异步执行
- 合理设计命令粒度:命令太粗或太细都不好
- 使用 Lambda 简化:对于简单命令,可以使用 Lambda 表达式
java
// 使用 Lambda 简单命令
interface SimpleCommand {
void execute();
}
// 使用 Lambda 创建命令
SimpleCommand lightOn = () -> System.out.println("灯已打开");
lightOn.execute();总结:命令模式将请求封装为对象,使得请求的发送者和接收者解耦。它在撤销/重做、任务队列、事务管理等场景中非常有用。关键在于理解"命令"就是"将操作封装为对象",这样就可以像操作数据一样操作命令(存储、传递、排队、撤销等)。