命令模式 (Command Pattern)
概述
命令模式是一种行为型设计模式,它将请求封装为一个对象,从而使你可以用不同的请求参数化客户端对象,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式的核心思想是将请求的发起者(调用者)与请求的接收者(执行者)解耦,通过一个命令对象来连接它们。这样,调用者不需要知道接收者的具体实现,只需要知道如何使用命令对象即可。
核心要点
- 请求封装:将请求封装为一个对象
- 解耦调用者和接收者:调用者不需要知道接收者的具体实现
- 支持可撤销操作:命令对象可以记录执行状态,支持撤销和重做
- 支持事务:可以将多个命令组合成一个复合命令,实现事务功能
- 支持队列:可以将命令放入队列中,按顺序执行
应用场景
- 需要将请求的发起者与接收者解耦时
- 需要支持可撤销操作时
- 需要支持事务处理时
- 需要将请求排队或记录请求日志时
- 需要支持命令宏(将多个命令组合成一个命令)时
结构
命令模式包含以下角色:
- 命令(Command):定义执行操作的接口
- 具体命令(Concrete Command):实现命令接口,持有接收者对象的引用,执行具体的操作
- 接收者(Receiver):执行命令的真正对象,知道如何执行与命令相关的操作
- 调用者(Invoker):持有命令对象的引用,调用命令对象执行请求
- 客户端(Client):创建具体命令对象,并设置其接收者
实现示例
1. 基本命令模式实现
java
// 命令接口
public interface Command {
void execute(); // 执行命令
void undo(); // 撤销命令
}
// 接收者:灯
public class Light {
private String location;
private boolean isOn = false;
public Light(String location) {
this.location = location;
}
public void on() {
isOn = true;
System.out.println(location + " 的灯已打开");
}
public void off() {
isOn = false;
System.out.println(location + " 的灯已关闭");
}
public boolean isOn() {
return isOn;
}
}
// 接收者:音响
public class Stereo {
private String location;
private int volume = 0;
private boolean isOn = false;
public Stereo(String location) {
this.location = location;
}
public void on() {
isOn = true;
System.out.println(location + " 的音响已打开");
}
public void off() {
isOn = false;
System.out.println(location + " 的音响已关闭");
}
public void setCD() {
System.out.println(location + " 的音响已设置为CD模式");
}
public void setVolume(int volume) {
this.volume = volume;
System.out.println(location + " 的音响音量已设置为 " + volume);
}
}
// 具体命令:开灯命令
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 StereoOnWithCDCommand implements Command {
private Stereo stereo; // 持有接收者对象的引用
private int previousVolume; // 记录之前的音量,用于撤销
public StereoOnWithCDCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
previousVolume = stereo.getVolume(); // 记录之前的音量
stereo.on();
stereo.setCD();
stereo.setVolume(11); // 设置新的音量
}
@Override
public void undo() {
stereo.off(); // 关闭音响
// 如果需要恢复到之前的状态,可以:
// stereo.on();
// stereo.setVolume(previousVolume);
}
}
// 具体命令:关闭音响命令
public class StereoOffCommand implements Command {
private Stereo stereo; // 持有接收者对象的引用
public StereoOffCommand(Stereo stereo) {
this.stereo = stereo;
}
@Override
public void execute() {
stereo.off();
}
@Override
public void undo() {
stereo.on();
stereo.setCD();
stereo.setVolume(11);
}
}
// 空命令(用于初始化按钮,避免空指针异常)
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; // 撤销命令
private final int SLOTS = 7; // 遥控器有7个按钮槽
public RemoteControl() {
// 初始化命令数组,使用空命令避免空指针异常
onCommands = new Command[SLOTS];
offCommands = new Command[SLOTS];
Command noCommand = new NoCommand();
for (int i = 0; i < SLOTS; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
undoCommand = noCommand;
}
// 设置按钮命令
public void setCommand(int slot, Command onCommand, Command offCommand) {
if (slot >= 0 && slot < SLOTS) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
}
// 按下开按钮
public void onButtonWasPushed(int slot) {
if (slot >= 0 && slot < SLOTS) {
onCommands[slot].execute();
undoCommand = onCommands[slot]; // 记录最后一次执行的命令
}
}
// 按下关按钮
public void offButtonWasPushed(int slot) {
if (slot >= 0 && slot < SLOTS) {
offCommands[slot].execute();
undoCommand = offCommands[slot]; // 记录最后一次执行的命令
}
}
// 按下撤销按钮
public void undoButtonWasPushed() {
undoCommand.undo();
}
// 打印遥控器状态
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\n------ 遥控器 ------\n");
for (int i = 0; i < SLOTS; i++) {
stringBuilder.append("[槽 ")
.append(i)
.append("] ")
.append(onCommands[i].getClass().getSimpleName())
.append(" ")
.append(offCommands[i].getClass().getSimpleName())
.append("\n");
}
stringBuilder.append("[撤销] ")
.append(undoCommand.getClass().getSimpleName())
.append("\n");
return stringBuilder.toString();
}
}
// 客户端
public class RemoteLoader {
public static void main(String[] args) {
// 创建遥控器(调用者)
RemoteControl remoteControl = new RemoteControl();
// 创建接收者
Light livingRoomLight = new Light("客厅");
Light kitchenLight = new Light("厨房");
Stereo livingRoomStereo = new Stereo("客厅");
// 创建命令,设置接收者
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
StereoOnWithCDCommand stereoOnWithCD = new StereoOnWithCDCommand(livingRoomStereo);
StereoOffCommand stereoOff = new StereoOffCommand(livingRoomStereo);
// 设置遥控器按钮的命令
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, stereoOnWithCD, stereoOff);
// 打印遥控器状态
System.out.println(remoteControl);
// 测试按钮功能
System.out.println("\n--- 测试按钮功能 ---");
remoteControl.onButtonWasPushed(0); // 打开客厅灯
remoteControl.offButtonWasPushed(0); // 关闭客厅灯
remoteControl.onButtonWasPushed(1); // 打开厨房灯
remoteControl.offButtonWasPushed(1); // 关闭厨房灯
remoteControl.onButtonWasPushed(2); // 打开客厅音响并播放CD
remoteControl.offButtonWasPushed(2); // 关闭客厅音响
// 测试撤销功能
System.out.println("\n--- 测试撤销功能 ---");
remoteControl.onButtonWasPushed(0); // 打开客厅灯
remoteControl.undoButtonWasPushed(); // 撤销,关闭客厅灯
remoteControl.onButtonWasPushed(2); // 打开客厅音响并播放CD
remoteControl.undoButtonWasPushed(); // 撤销,关闭客厅音响
}
}实际应用示例:文本编辑器的操作历史
下面是一个实际应用的例子,展示如何使用命令模式实现文本编辑器的撤销/重做功能:
java
import java.util.Stack;
// 命令接口
public interface EditorCommand {
void execute(); // 执行命令
void undo(); // 撤销命令
}
// 接收者:文本编辑器
public class TextEditor {
private StringBuilder text = new StringBuilder();
private int cursorPosition = 0;
// 插入文本
public void insertText(String textToInsert) {
text.insert(cursorPosition, textToInsert);
cursorPosition += textToInsert.length();
System.out.println("插入文本: \"" + textToInsert + "\"");
display();
}
// 删除文本
public String deleteText(int length) {
if (length <= 0 || cursorPosition - length < 0) {
return "";
}
String deletedText = text.substring(cursorPosition - length, cursorPosition);
text.delete(cursorPosition - length, cursorPosition);
cursorPosition -= length;
System.out.println("删除文本: \"" + deletedText + "\"");
display();
return deletedText;
}
// 设置光标位置
public void setCursorPosition(int position) {
if (position >= 0 && position <= text.length()) {
cursorPosition = position;
System.out.println("光标位置设置为: " + position);
display();
}
}
// 显示编辑器内容
public void display() {
System.out.println("编辑器内容: \"" + text + "\"");
System.out.println("光标位置: " + cursorPosition);
}
// 获取文本内容
public String getText() {
return text.toString();
}
// 获取光标位置
public int getCursorPosition() {
return cursorPosition;
}
}
// 具体命令:插入文本命令
public class InsertTextCommand implements EditorCommand {
private TextEditor editor; // 文本编辑器
private String textToInsert; // 要插入的文本
private int position; // 插入位置
public InsertTextCommand(TextEditor editor, String textToInsert) {
this.editor = editor;
this.textToInsert = textToInsert;
this.position = editor.getCursorPosition(); // 记录当前光标位置
}
@Override
public void execute() {
editor.setCursorPosition(position); // 设置光标到插入位置
editor.insertText(textToInsert); // 插入文本
}
@Override
public void undo() {
// 撤销插入操作,删除刚刚插入的文本
editor.setCursorPosition(position + textToInsert.length());
editor.deleteText(textToInsert.length());
}
}
// 具体命令:删除文本命令
public class DeleteTextCommand implements EditorCommand {
private TextEditor editor; // 文本编辑器
private String deletedText; // 被删除的文本
private int position; // 删除前的光标位置
public DeleteTextCommand(TextEditor editor, int length) {
this.editor = editor;
this.position = editor.getCursorPosition(); // 记录删除前的光标位置
this.deletedText = editor.deleteText(length); // 执行删除操作
}
@Override
public void execute() {
// 再次执行删除操作
editor.setCursorPosition(position);
editor.deleteText(deletedText.length());
}
@Override
public void undo() {
// 撤销删除操作,重新插入被删除的文本
editor.setCursorPosition(position - deletedText.length());
editor.insertText(deletedText);
}
}
// 具体命令:移动光标命令
public class MoveCursorCommand implements EditorCommand {
private TextEditor editor; // 文本编辑器
private int oldPosition; // 移动前的光标位置
private int newPosition; // 移动后的光标位置
public MoveCursorCommand(TextEditor editor, int newPosition) {
this.editor = editor;
this.oldPosition = editor.getCursorPosition(); // 记录移动前的光标位置
this.newPosition = newPosition; // 设置新的光标位置
}
@Override
public void execute() {
editor.setCursorPosition(newPosition); // 移动光标
}
@Override
public void undo() {
editor.setCursorPosition(oldPosition); // 恢复到移动前的位置
}
}
// 调用者:编辑器控制器
public class EditorController {
private Stack<EditorCommand> undoStack = new Stack<>(); // 撤销栈
private Stack<EditorCommand> redoStack = new Stack<>(); // 重做栈
private TextEditor editor; // 文本编辑器
public EditorController(TextEditor editor) {
this.editor = editor;
}
// 执行命令
public void executeCommand(EditorCommand command) {
command.execute(); // 执行命令
undoStack.push(command); // 将命令加入撤销栈
redoStack.clear(); // 清空重做栈
}
// 撤销命令
public void undo() {
if (!undoStack.isEmpty()) {
EditorCommand command = undoStack.pop(); // 从撤销栈中弹出命令
command.undo(); // 执行撤销操作
redoStack.push(command); // 将命令加入重做栈
System.out.println("撤销操作完成");
} else {
System.out.println("没有可撤销的操作");
}
}
// 重做命令
public void redo() {
if (!redoStack.isEmpty()) {
EditorCommand command = redoStack.pop(); // 从重做栈中弹出命令
command.execute(); // 重新执行命令
undoStack.push(command); // 将命令加入撤销栈
System.out.println("重做操作完成");
} else {
System.out.println("没有可重做的操作");
}
}
// 显示编辑历史
public void displayHistory() {
System.out.println("\n撤销历史:");
if (undoStack.isEmpty()) {
System.out.println(" 空");
} else {
for (int i = undoStack.size() - 1; i >= 0; i--) {
System.out.println(" " + (undoStack.size() - i) + ": " + undoStack.get(i).getClass().getSimpleName());
}
}
System.out.println("\n重做历史:");
if (redoStack.isEmpty()) {
System.out.println(" 空");
} else {
for (int i = redoStack.size() - 1; i >= 0; i--) {
System.out.println(" " + (redoStack.size() - i) + ": " + redoStack.get(i).getClass().getSimpleName());
}
}
System.out.println();
}
}
// 客户端:文本编辑器用户
public class TextEditorDemo {
public static void main(String[] args) {
// 创建文本编辑器
TextEditor editor = new TextEditor();
// 创建编辑器控制器
EditorController controller = new EditorController(editor);
// 执行编辑操作
System.out.println("=== 执行编辑操作 ===");
controller.executeCommand(new InsertTextCommand(editor, "Hello"));
controller.executeCommand(new InsertTextCommand(editor, " World"));
controller.executeCommand(new MoveCursorCommand(editor, 5)); // 移动到"Hello"后
controller.executeCommand(new InsertTextCommand(editor, ","));
// 显示历史
controller.displayHistory();
// 执行撤销操作
System.out.println("=== 执行撤销操作 ===");
controller.undo(); // 撤销插入逗号
controller.undo(); // 撤销移动光标
// 显示历史
controller.displayHistory();
// 执行重做操作
System.out.println("=== 执行重做操作 ===");
controller.redo(); // 重做移动光标
controller.redo(); // 重做插入逗号
// 显示历史
controller.displayHistory();
// 执行删除操作
System.out.println("=== 执行删除操作 ===");
controller.executeCommand(new MoveCursorCommand(editor, 12)); // 移动到末尾
controller.executeCommand(new DeleteTextCommand(editor, 6)); // 删除"World"
// 显示历史
controller.displayHistory();
// 执行撤销删除
System.out.println("=== 撤销删除 ===");
controller.undo(); // 撤销删除操作
// 最终结果
System.out.println("\n=== 最终结果 ===");
editor.display();
}
}优缺点
优点
- 解耦调用者和接收者:调用者不需要知道接收者的具体实现
- 支持可撤销操作:命令对象可以记录执行状态,支持撤销和重做
- 支持事务:可以将多个命令组合成一个复合命令,实现事务功能
- 支持队列:可以将命令放入队列中,按顺序执行
- 符合开闭原则:添加新的命令不需要修改现有代码
缺点
- 增加了类的数量:每个命令都需要一个对应的命令类,增加了系统中类的数量
- 命令对象可能会占用大量内存:特别是当系统中有大量命令对象时
- 可能会导致系统变得复杂:特别是当需要支持多级撤销和重做时
与其他模式的关系
- 命令模式与组合模式:可以使用组合模式创建复合命令
- 命令模式与备忘录模式:都支持撤销操作,但命令模式关注的是操作的撤销,备忘录模式关注的是状态的恢复
- 命令模式与原型模式:可以使用原型模式复制命令对象,用于撤销和重做
总结
命令模式是一种强大的行为型设计模式,它将请求封装为一个对象,从而使请求的发起者与接收者解耦。命令模式支持可撤销操作、事务处理、请求排队等功能,在实际应用中广泛用于GUI操作、数据库事务、日志记录等场景。使用命令模式需要注意控制命令对象的数量和内存占用,避免系统变得过于复杂。