设计模式之:命令模式
文章目录
- 什么是命令模式?
- 核心思想
- 生活中的命令模式
- 模式结构
- 基础示例:智能家居系统
- 1. 命令接口
- 2. 接收者 - 家电设备
- 3. 具体命令类
- 4. 调用者 - 遥控器
- 5. 客户端使用
- 完整示例:文本编辑器
- 1. 文本编辑器接收者
- 2. 编辑器命令
- 3. 编辑器调用者
- 4. 文本编辑器客户端
- 命令模式的优点
- 1. 解耦调用者和接收者
- 2. 支持撤销和重做
- 3. 支持命令队列和日志
- 命令模式的缺点
- 1. 可能产生大量命令类
- 2. 增加系统复杂度
- 适用场景
- 最佳实践
- 1. 使用宏命令
- 2. 实现空对象
- 3. 考虑命令的生命周期
- 命令模式 vs 其他模式
- 总结
什么是命令模式?
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成一个对象,从而让你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
核心思想
命令模式的核心思想是:将请求封装成对象,使得可以用不同的请求对客户端进行参数化,并支持请求的排队、记录、撤销等操作。
生活中的命令模式
想象一下餐厅的点餐流程:
- 顾客(客户端)创建订单(命令对象)
- 服务员(调用者)接收订单并交给厨房
- 厨师(接收者)根据订单准备食物
- 订单可以取消、修改或重复使用
订单就是命令对象!
模式结构
命令模式包含五个核心角色:
- 命令接口(Command):声明执行操作的接口
- 具体命令(Concrete Command):实现命令接口,绑定接收者和动作
- 接收者(Receiver):知道如何执行请求的具体操作
- 调用者(Invoker):要求命令执行请求
- 客户端(Client):创建具体命令并设置接收者
基础示例:智能家居系统
1. 命令接口
/*** 命令接口 - 声明执行操作的接口*/
public interface Command {/*** 执行命令*/void execute();/*** 撤销命令*/void undo();/*** 获取命令描述*/String getDescription();
}
2. 接收者 - 家电设备
/*** 电灯 - 接收者* 知道如何执行具体的操作*/
public class Light {private final String location;private boolean isOn;private int brightness; // 亮度级别 0-100public Light(String location) {this.location = location;this.isOn = false;this.brightness = 50; // 默认亮度System.out.println("💡 初始化电灯: " + location);}public void turnOn() {this.isOn = true;System.out.println("💡 " + location + " 电灯已打开");}public void turnOff() {this.isOn = false;System.out.println("💡 " + location + " 电灯已关闭");}public void setBrightness(int level) {if (level < 0) level = 0;if (level > 100) level = 100;this.brightness = level;System.out.println("💡 " + location + " 亮度设置为: " + level + "%");}public void dim() {setBrightness(brightness - 10);}public void brighten() {setBrightness(brightness + 10);}public boolean isOn() {return isOn;}public int getBrightness() {return brightness;}public String getStatus() {return String.format("电灯[%s] - 状态: %s, 亮度: %d%%", location, isOn ? "开" : "关", brightness);}
}/*** 空调 - 接收者*/
public class AirConditioner {private final String location;private boolean isOn;private int temperature;private String mode; // 制冷/制热/送风public AirConditioner(String location) {this.location = location;this.isOn = false;this.temperature = 26;this.mode = "制冷";System.out.println("❄️ 初始化空调: " + location);}public void turnOn() {this.isOn = true;System.out.println("❄️ " + location + " 空调已打开");}public void turnOff() {this.isOn = false;System.out.println("❄️ " + location + " 空调已关闭");}public void setTemperature(int temperature) {this.temperature = temperature;System.out.println("❄️ " + location + " 温度设置为: " + temperature + "°C");}public void setMode(String mode) {this.mode = mode;System.out.println("❄️ " + location + " 模式设置为: " + mode);}public boolean isOn() {return isOn;}public String getStatus() {return String.format("空调[%s] - 状态: %s, 温度: %d°C, 模式: %s", location, isOn ? "开" : "关", temperature, mode);}
}/*** 电视 - 接收者*/
public class Television {private final String location;private boolean isOn;private int volume;private int channel;public Television(String location) {this.location = location;this.isOn = false;this.volume = 20;this.channel = 1;System.out.println("📺 初始化电视: " + location);}public void turnOn() {this.isOn = true;System.out.println("📺 " + location + " 电视已打开");}public void turnOff() {this.isOn = false;System.out.println("📺 " + location + " 电视已关闭");}public void setVolume(int volume) {this.volume = volume;System.out.println("📺 " + location + " 音量设置为: " + volume);}public void setChannel(int channel) {this.channel = channel;System.out.println("📺 " + location + " 频道切换到: " + channel);}public void volumeUp() {setVolume(volume + 5);}public void volumeDown() {setVolume(volume - 5);}public void channelUp() {setChannel(channel + 1);}public void channelDown() {setChannel(channel - 1);}public boolean isOn() {return isOn;}public String getStatus() {return String.format("电视[%s] - 状态: %s, 频道: %d, 音量: %d", location, isOn ? "开" : "关", channel, volume);}
}
3. 具体命令类
/*** 电灯开关命令 - 具体命令*/
public class LightOnCommand implements Command {private final Light light;private int previousBrightness;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {previousBrightness = light.getBrightness();light.turnOn();light.setBrightness(80); // 默认打开时设置合适亮度}@Overridepublic void undo() {light.turnOff();light.setBrightness(previousBrightness);}@Overridepublic String getDescription() {return "打开电灯: " + light.getStatus();}
}/*** 电灯关闭命令*/
public class LightOffCommand implements Command {private final Light light;private int previousBrightness;private boolean wasOn;public LightOffCommand(Light light) {this.light = light;}@Overridepublic void execute() {wasOn = light.isOn();previousBrightness = light.getBrightness();light.turnOff();}@Overridepublic void undo() {if (wasOn) {light.turnOn();light.setBrightness(previousBrightness);}}@Overridepublic String getDescription() {return "关闭电灯: " + light.getStatus();}
}/*** 电灯调光命令*/
public class LightDimCommand implements Command {private final Light light;private int previousBrightness;public LightDimCommand(Light light) {this.light = light;}@Overridepublic void execute() {previousBrightness = light.getBrightness();light.dim();}@Overridepublic void undo() {light.setBrightness(previousBrightness);}@Overridepublic String getDescription() {return "调暗电灯: " + light.getStatus();}
}/*** 空调开关命令*/
public class AirConditionerOnCommand implements Command {private final AirConditioner airConditioner;private int previousTemperature;private String previousMode;private boolean wasOn;public AirConditionerOnCommand(AirConditioner airConditioner) {this.airConditioner = airConditioner;}@Overridepublic void execute() {wasOn = airConditioner.isOn();previousTemperature = 26; // 简化处理previousMode = "制冷";airConditioner.turnOn();airConditioner.setTemperature(24);airConditioner.setMode("制冷");}@Overridepublic void undo() {if (!wasOn) {airConditioner.turnOff();}// 在实际应用中,应该恢复之前的设置}@Overridepublic String getDescription() {return "打开空调: " + airConditioner.getStatus();}
}/*** 电视开关命令*/
public class TelevisionOnCommand implements Command {private final Television television;private int previousVolume;private int previousChannel;private boolean wasOn;public TelevisionOnCommand(Television television) {this.television = television;}@Overridepublic void execute() {wasOn = television.isOn();previousVolume = television.isOn() ? television.getStatus().length() : 20; // 简化previousChannel = 1;television.turnOn();television.setChannel(1);television.setVolume(25);}@Overridepublic void undo() {if (!wasOn) {television.turnOff();}}@Overridepublic String getDescription() {return "打开电视: " + television.getStatus();}
}/*** 宏命令 - 同时执行多个命令*/
public class MacroCommand implements Command {private final List<Command> commands;private final String name;public MacroCommand(String name, Command... commands) {this.name = name;this.commands = new ArrayList<>(Arrays.asList(commands));}public void addCommand(Command command) {commands.add(command);}@Overridepublic void execute() {System.out.println("🎮 执行宏命令: " + name);for (Command command : commands) {command.execute();}}@Overridepublic void undo() {System.out.println("↩️ 撤销宏命令: " + name);// 按相反顺序撤销for (int i = commands.size() - 1; i >= 0; i--) {commands.get(i).undo();}}@Overridepublic String getDescription() {return "宏命令[" + name + "] - 包含 " + commands.size() + " 个操作";}
}
4. 调用者 - 遥控器
import java.util.Stack;/*** 智能遥控器 - 调用者* 负责触发命令的执行*/
public class SmartRemoteControl {private final Map<String, Command> commandSlots;private final Stack<Command> commandHistory;private Command lastCommand;public SmartRemoteControl() {this.commandSlots = new HashMap<>();this.commandHistory = new Stack<>();this.lastCommand = null;System.out.println("🎛️ 初始化智能遥控器");}/*** 设置命令到指定槽位*/public void setCommand(String slot, Command command) {commandSlots.put(slot, command);System.out.println("📝 设置命令到槽位 [" + slot + "]: " + command.getDescription());}/*** 按下按钮执行命令*/public void pressButton(String slot) {Command command = commandSlots.get(slot);if (command != null) {System.out.println("\n🔘 按下按钮: " + slot);command.execute();commandHistory.push(command);lastCommand = command;} else {System.out.println("❌ 未找到槽位: " + slot);}}/*** 撤销上一个命令*/public void pressUndo() {if (!commandHistory.isEmpty()) {Command command = commandHistory.pop();System.out.println("\n↩️ 执行撤销操作");command.undo();} else {System.out.println("❌ 没有可撤销的命令");}}/*** 显示遥控器状态*/public void displayStatus() {System.out.println("\n📋 遥控器状态:");System.out.println("-".repeat(50));commandSlots.forEach((slot, command) -> {System.out.printf("🔘 %-15s -> %s%n", slot, command.getDescription());});System.out.printf("📜 命令历史: %d 个命令%n", commandHistory.size());}/*** 清除命令历史*/public void clearHistory() {commandHistory.clear();System.out.println("🗑️ 清除命令历史");}
}
5. 客户端使用
/*** 智能家居客户端*/
public class SmartHomeClient {public static void main(String[] args) {System.out.println("=== 命令模式演示 - 智能家居系统 ===\n");// 创建接收者 - 家电设备Light livingRoomLight = new Light("客厅");Light bedroomLight = new Light("卧室");AirConditioner livingRoomAC = new AirConditioner("客厅");Television livingRoomTV = new Television("客厅");// 创建命令Command livingRoomLightOn = new LightOnCommand(livingRoomLight);Command livingRoomLightOff = new LightOffCommand(livingRoomLight);Command livingRoomLightDim = new LightDimCommand(livingRoomLight);Command livingRoomACOn = new AirConditionerOnCommand(livingRoomAC);Command livingRoomTVOn = new TelevisionOnCommand(livingRoomTV);// 创建宏命令MacroCommand eveningMode = new MacroCommand("晚间模式", livingRoomLightOn, livingRoomTVOn);MacroCommand leaveHomeMode = new MacroCommand("离家模式", livingRoomLightOff, new LightOffCommand(bedroomLight));// 创建调用者 - 遥控器SmartRemoteControl remote = new SmartRemoteControl();// 配置遥控器remote.setCommand("灯开", livingRoomLightOn);remote.setCommand("灯关", livingRoomLightOff);remote.setCommand("调光", livingRoomLightDim);remote.setCommand("空调", livingRoomACOn);remote.setCommand("电视", livingRoomTVOn);remote.setCommand("晚间", eveningMode);remote.setCommand("离家", leaveHomeMode);// 显示遥控器状态remote.displayStatus();// 演示1:基础命令执行demonstrateBasicCommands(remote);// 演示2:撤销功能demonstrateUndoFeature(remote);// 演示3:宏命令demonstrateMacroCommands(remote);// 演示4:命令队列demonstrateCommandQueue();}/*** 演示基础命令执行*/private static void demonstrateBasicCommands(SmartRemoteControl remote) {System.out.println("\n1. 基础命令执行演示:");System.out.println("=" .repeat(40));remote.pressButton("灯开");remote.pressButton("调光");remote.pressButton("电视");remote.pressButton("空调");}/*** 演示撤销功能*/private static void demonstrateUndoFeature(SmartRemoteControl remote) {System.out.println("\n2. 撤销功能演示:");System.out.println("=" .repeat(40));remote.pressButton("灯关");remote.pressUndo(); // 应该重新打开灯remote.pressButton("调光");remote.pressButton("调光");remote.pressUndo(); // 撤销一次调光remote.pressUndo(); // 再撤销一次调光}/*** 演示宏命令*/private static void demonstrateMacroCommands(SmartRemoteControl remote) {System.out.println("\n3. 宏命令演示:");System.out.println("=" .repeat(40));System.out.println("执行晚间模式:");remote.pressButton("晚间");System.out.println("\n执行离家模式:");remote.pressButton("离家");System.out.println("\n撤销离家模式:");remote.pressUndo();}/*** 演示命令队列*/private static void demonstrateCommandQueue() {System.out.println("\n4. 命令队列演示:");System.out.println("=" .repeat(40));CommandProcessor processor = new CommandProcessor();Light kitchenLight = new Light("厨房");Television kitchenTV = new Television("厨房");processor.addCommand(new LightOnCommand(kitchenLight));processor.addCommand(new TelevisionOnCommand(kitchenTV));processor.addCommand(new LightDimCommand(kitchenLight));System.out.println("处理命令队列...");processor.processCommands();}
}/*** 命令处理器 - 支持命令队列*/
class CommandProcessor {private final Queue<Command> commandQueue;public CommandProcessor() {this.commandQueue = new LinkedList<>();}public void addCommand(Command command) {commandQueue.offer(command);System.out.println("📥 添加命令到队列: " + command.getDescription());}public void processCommands() {System.out.println("🔄 开始处理命令队列...");while (!commandQueue.isEmpty()) {Command command = commandQueue.poll();System.out.println("⚡ 执行: " + command.getDescription());command.execute();try {Thread.sleep(500); // 模拟处理时间} catch (InterruptedException e) {Thread.currentThread().interrupt();}}System.out.println("✅ 所有命令处理完成");}
}
完整示例:文本编辑器
让我们通过一个更复杂的文本编辑器示例来深入理解命令模式。
1. 文本编辑器接收者
import java.util.ArrayList;
import java.util.List;/*** 文本编辑器 - 接收者* 知道如何执行文本操作*/
public class TextEditor {private StringBuilder content;private int cursorPosition;private List<String> clipboard;public TextEditor() {this.content = new StringBuilder();this.cursorPosition = 0;this.clipboard = new ArrayList<>();System.out.println("📝 初始化文本编辑器");}public void insertText(String text) {content.insert(cursorPosition, text);cursorPosition += text.length();System.out.println("📝 插入文本: \"" + text + "\"");displayContent();}public void deleteText(int length) {if (cursorPosition >= length) {int start = cursorPosition - length;String deleted = content.substring(start, cursorPosition);content.delete(start, cursorPosition);cursorPosition = start;System.out.println("🗑️ 删除文本: \"" + deleted + "\"");displayContent();}}public void copyText(int start, int end) {if (start >= 0 && end <= content.length() && start < end) {String copied = content.substring(start, end);clipboard.add(copied);System.out.println("📋 复制文本: \"" + copied + "\"");}}public void pasteText() {if (!clipboard.isEmpty()) {String textToPaste = clipboard.get(clipboard.size() - 1);insertText(textToPaste);}}public void setCursorPosition(int position) {if (position >= 0 && position <= content.length()) {this.cursorPosition = position;System.out.println("📍 光标位置设置为: " + position);}}public void boldText(int start, int end) {if (start >= 0 && end <= content.length() && start < end) {content.insert(end, "**");content.insert(start, "**");cursorPosition = end + 2;System.out.println("🔷 加粗文本范围: " + start + "-" + end);displayContent();}}public void undoInsert(int position, int length) {content.delete(position, position + length);cursorPosition = position;displayContent();}public void undoDelete(int position, String text) {content.insert(position, text);cursorPosition = position + text.length();displayContent();}public String getContent() {return content.toString();}public int getCursorPosition() {return cursorPosition;}public void displayContent() {String display = content.toString();if (display.length() > 50) {display = display.substring(0, 47) + "...";}System.out.println("📄 内容: \"" + display + "\" [光标位置: " + cursorPosition + "]");}public void displayFullContent() {System.out.println("📄 完整内容: \"" + content.toString() + "\"");}
}
2. 编辑器命令
/*** 插入文本命令*/
public class InsertTextCommand implements Command {private final TextEditor editor;private final String text;private int insertPosition;public InsertTextCommand(TextEditor editor, String text) {this.editor = editor;this.text = text;}@Overridepublic void execute() {insertPosition = editor.getCursorPosition();editor.insertText(text);}@Overridepublic void undo() {editor.undoInsert(insertPosition, text.length());}@Overridepublic String getDescription() {return "插入文本: \"" + text + "\"";}
}/*** 删除文本命令*/
public class DeleteTextCommand implements Command {private final TextEditor editor;private final int length;private int deletePosition;private String deletedText;public DeleteTextCommand(TextEditor editor, int length) {this.editor = editor;this.length = length;}@Overridepublic void execute() {deletePosition = editor.getCursorPosition() - length;if (deletePosition >= 0) {int end = editor.getCursorPosition();deletedText = editor.getContent().substring(deletePosition, end);editor.deleteText(length);}}@Overridepublic void undo() {if (deletedText != null) {editor.undoDelete(deletePosition, deletedText);}}@Overridepublic String getDescription() {return "删除 " + length + " 个字符";}
}/*** 复制文本命令*/
public class CopyTextCommand implements Command {private final TextEditor editor;private final int start;private final int end;public CopyTextCommand(TextEditor editor, int start, int end) {this.editor = editor;this.start = start;this.end = end;}@Overridepublic void execute() {editor.copyText(start, end);}@Overridepublic void undo() {// 复制操作通常不需要撤销System.out.println("⚠️ 复制操作不可撤销");}@Overridepublic String getDescription() {return "复制文本范围: " + start + "-" + end;}
}/*** 粘贴文本命令*/
public class PasteTextCommand implements Command {private final TextEditor editor;private int pastePosition;private String pastedText;public PasteTextCommand(TextEditor editor) {this.editor = editor;}@Overridepublic void execute() {pastePosition = editor.getCursorPosition();// 这里简化处理,实际应该从剪贴板获取pastedText = "已复制的文本";editor.pasteText();}@Overridepublic void undo() {if (pastedText != null) {editor.undoInsert(pastePosition, pastedText.length());}}@Overridepublic String getDescription() {return "粘贴文本";}
}/*** 加粗文本命令*/
public class BoldTextCommand implements Command {private final TextEditor editor;private final int start;private final int end;public BoldTextCommand(TextEditor editor, int start, int end) {this.editor = editor;this.start = start;this.end = end;}@Overridepublic void execute() {editor.boldText(start, end);}@Overridepublic void undo() {// 简化处理,实际应该记录更详细的状态System.out.println("↩️ 撤销加粗操作");}@Overridepublic String getDescription() {return "加粗文本: " + start + "-" + end;}
}
3. 编辑器调用者
import java.util.Stack;/*** 文本编辑器控制器 - 调用者*/
public class EditorController {private final TextEditor editor;private final Stack<Command> history;private final Stack<Command> redoStack;public EditorController(TextEditor editor) {this.editor = editor;this.history = new Stack<>();this.redoStack = new Stack<>();System.out.println("🎮 初始化编辑器控制器");}public void executeCommand(Command command) {command.execute();history.push(command);redoStack.clear(); // 执行新命令时清空重做栈System.out.println("✅ 执行: " + command.getDescription());}public void undo() {if (!history.isEmpty()) {Command command = history.pop();command.undo();redoStack.push(command);System.out.println("↩️ 撤销: " + command.getDescription());} else {System.out.println("❌ 没有可撤销的操作");}}public void redo() {if (!redoStack.isEmpty()) {Command command = redoStack.pop();command.execute();history.push(command);System.out.println("↪️ 重做: " + command.getDescription());} else {System.out.println("❌ 没有可重做的操作");}}public void showHistory() {System.out.println("\n📜 操作历史:");System.out.println("-".repeat(40));for (int i = 0; i < history.size(); i++) {System.out.printf("%2d. %s%n", i + 1, history.get(i).getDescription());}}public void showEditorStatus() {System.out.println("\n📊 编辑器状态:");editor.displayFullContent();}
}
4. 文本编辑器客户端
/*** 文本编辑器客户端*/
public class TextEditorClient {public static void main(String[] args) {System.out.println("=== 命令模式演示 - 文本编辑器 ===\n");// 创建接收者TextEditor editor = new TextEditor();// 创建调用者EditorController controller = new EditorController(editor);// 演示1:基础文本操作demonstrateBasicOperations(controller, editor);// 演示2:撤销重做功能demonstrateUndoRedo(controller);// 演示3:复杂操作序列demonstrateComplexOperations(controller, editor);// 显示最终状态controller.showEditorStatus();controller.showHistory();}private static void demonstrateBasicOperations(EditorController controller, TextEditor editor) {System.out.println("1. 基础文本操作演示:");System.out.println("=" .repeat(40));controller.executeCommand(new InsertTextCommand(editor, "Hello, "));controller.executeCommand(new InsertTextCommand(editor, "Command Pattern!"));controller.executeCommand(new InsertTextCommand(editor, " 这是一个命令模式的演示。"));// 设置光标位置并删除editor.setCursorPosition(7); // 移动到 "Hello, " 后面controller.executeCommand(new DeleteTextCommand(editor, 8)); // 删除 "Command"}private static void demonstrateUndoRedo(EditorController controller) {System.out.println("\n2. 撤销重做功能演示:");System.out.println("=" .repeat(40));System.out.println("撤销两次操作:");controller.undo();controller.undo();System.out.println("\n重做一次操作:");controller.redo();System.out.println("\n再次撤销:");controller.undo();}private static void demonstrateComplexOperations(EditorController controller, TextEditor editor) {System.out.println("\n3. 复杂操作序列演示:");System.out.println("=" .repeat(40));// 插入新内容controller.executeCommand(new InsertTextCommand(editor, "\n\n新段落开始。"));controller.executeCommand(new InsertTextCommand(editor, " 这是重要内容。"));// 复制和粘贴(简化演示)editor.setCursorPosition(0);controller.executeCommand(new CopyTextCommand(editor, 0, 5));controller.executeCommand(new PasteTextCommand(editor));// 加粗文本controller.executeCommand(new BoldTextCommand(editor, 10, 15));System.out.println("\n执行多次撤销:");for (int i = 0; i < 3; i++) {controller.undo();}}
}
命令模式的优点
1. 解耦调用者和接收者
// 调用者不需要知道接收者的具体细节
// 只需要知道如何触发命令
2. 支持撤销和重做
// 命令对象可以保存状态
// 很容易实现撤销/重做功能
3. 支持命令队列和日志
// 可以轻松实现命令队列
// 支持命令的延迟执行和日志记录
命令模式的缺点
1. 可能产生大量命令类
// 每个操作都需要一个命令类
// 可能导致类的数量增加
2. 增加系统复杂度
// 引入了额外的抽象层
// 对于简单操作可能过于复杂
适用场景
- 需要支持撤销/重做操作时
- 需要将操作参数化时
- 需要支持命令队列或日志时
- 需要支持事务操作时
最佳实践
1. 使用宏命令
// 将多个命令组合成一个宏命令
// 支持批量操作
2. 实现空对象
// 实现一个空命令对象
// 避免空指针检查
3. 考虑命令的生命周期
// 对于需要长时间运行的命令
// 考虑实现取消功能
命令模式 vs 其他模式
| 模式 | 目的 | 特点 |
|---|---|---|
| 命令模式 | 封装请求 | 支持撤销、队列、参数化 |
| 策略模式 | 封装算法 | 算法可以互相替换 |
| 责任链模式 | 处理请求 | 多个对象可以处理请求 |
总结
命令模式就像是"任务委托书",把请求打包成对象,让它们可以被传递、存储和执行。
核心价值:
- 将请求封装成对象
- 支持撤销和重做操作
- 支持命令队列和日志
- 解耦请求发送者和接收者
使用场景:
- 需要实现撤销/重做功能时
- 需要将操作参数化时
- 需要支持事务操作时
简单记忆:
请求太多难管理?命令模式来解决!
打包成对象,撤销重做都容易。
掌握命令模式,能够让你构建出支持复杂操作流程的系统,提供更好的用户体验!
