命令模式
大约 10 分钟
命令模式
什么是命令模式
命令模式(Command Pattern)是一种行为型设计模式,它将请求封装成对象,从而使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式的核心思想是:
- 将请求封装成对象
- 将发出请求的对象和执行请求的对象解耦
- 支持撤销操作和队列请求
为什么需要命令模式
在实际开发中,我们经常需要将操作封装起来,以便于:
- 参数化其他对象
- 将操作排队执行
- 记录操作日志
- 支持撤销操作
- 实现事务处理
命令模式通过将操作封装成命令对象,使得这些操作可以像普通对象一样被处理,从而提供了更大的灵活性。
命令模式的结构
命令模式包含以下几个角色:
- 命令(Command):声明执行操作的接口
- 具体命令(ConcreteCommand):将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现Execute
- 客户端(Client):创建一个具体命令对象并设定它的接收者
- 调用者(Invoker):要求该命令执行这个请求
- 接收者(Receiver):知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者
命令模式的实现
基本实现
// 命令接口
interface Command {
void execute();
}
// 接收者
class Receiver {
public void action() {
System.out.println("接收者执行操作");
}
}
// 具体命令
class ConcreteCommand implements Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("具体命令执行");
receiver.action();
}
}
// 调用者
class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void executeCommand() {
System.out.println("调用者执行命令");
command.execute();
}
}
// 使用示例
public class CommandDemo {
public static void main(String[] args) {
// 创建接收者
Receiver receiver = new Receiver();
// 创建命令
Command command = new ConcreteCommand(receiver);
// 创建调用者并设置命令
Invoker invoker = new Invoker();
invoker.setCommand(command);
// 执行命令
invoker.executeCommand();
}
}
实际应用示例
// 遥控器示例
// 命令接口
interface Command {
void execute();
void undo();
}
// 空命令(空对象模式)
class NoCommand implements Command {
@Override
public void execute() {
System.out.println("无操作");
}
@Override
public void undo() {
System.out.println("无撤销操作");
}
}
// 灯光类(接收者)
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 + " 灯关闭");
}
}
// 风扇类(接收者)
class CeilingFan {
public static final int HIGH = 3;
public static final int MEDIUM = 2;
public static final int LOW = 1;
public static final int OFF = 0;
private String location;
private int speed;
public CeilingFan(String location) {
this.location = location;
this.speed = OFF;
}
public void high() {
speed = HIGH;
System.out.println(location + " 风扇高速");
}
public void medium() {
speed = MEDIUM;
System.out.println(location + " 风扇中速");
}
public void low() {
speed = LOW;
System.out.println(location + " 风扇低速");
}
public void off() {
speed = OFF;
System.out.println(location + " 风扇关闭");
}
public int getSpeed() {
return speed;
}
}
// 灯光开命令
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();
}
}
// 灯光关命令
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();
}
}
// 风扇开命令
class CeilingFanOnCommand implements Command {
private CeilingFan ceilingFan;
public CeilingFanOnCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
@Override
public void execute() {
ceilingFan.medium();
}
@Override
public void undo() {
ceilingFan.off();
}
}
// 风扇关命令
class CeilingFanOffCommand implements Command {
private CeilingFan ceilingFan;
public CeilingFanOffCommand(CeilingFan ceilingFan) {
this.ceilingFan = ceilingFan;
}
@Override
public void execute() {
ceilingFan.off();
}
@Override
public void undo() {
ceilingFan.medium(); // 简化实现,实际应该记录之前的速度
}
}
// 遥控器类(调用者)
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() {
System.out.println("--- 撤销操作 ---");
undoCommand.undo();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("\n------ 遥控器 ------\n");
for (int i = 0; i < onCommands.length; i++) {
sb.append("[slot " + i + "] " +
onCommands[i].getClass().getSimpleName() + " " +
offCommands[i].getClass().getSimpleName() + "\n");
}
sb.append("[undo] " + undoCommand.getClass().getSimpleName() + "\n");
return sb.toString();
}
}
// 使用示例
public class RemoteControlDemo {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
// 创建设备
Light livingRoomLight = new Light("客厅");
Light kitchenLight = new Light("厨房");
CeilingFan ceilingFan = new CeilingFan("客厅");
// 创建命令
LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
// 设置命令
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
// 显示遥控器状态
System.out.println(remoteControl);
// 测试命令执行
System.out.println("--- 测试命令执行 ---");
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.onButtonWasPushed(1);
remoteControl.offButtonWasPushed(1);
remoteControl.onButtonWasPushed(2);
remoteControl.offButtonWasPushed(2);
// 测试撤销操作
System.out.println("\n--- 测试撤销操作 ---");
remoteControl.onButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
remoteControl.offButtonWasPushed(0);
remoteControl.undoButtonWasPushed();
}
}
命令模式的应用场景
- GUI菜单和工具栏:每个菜单项或工具按钮都可以看作是一个命令
- 撤销/重做操作:支持撤销和重做功能
- 宏命令:将多个命令组合成一个宏命令
- 线程池:将任务封装成命令对象提交给线程池执行
- 事务处理:将一系列操作封装成命令,支持回滚
- 日志系统:将操作记录到日志中,用于系统恢复
命令模式的优缺点
优点
- 降低系统耦合度:将调用操作的对象与知道如何执行该操作的对象解耦
- 扩展性好:增加新的命令很容易,符合开闭原则
- 支持撤销操作:可以方便地实现命令的撤销和重做功能
- 支持队列请求:可以将命令对象存储在队列中,实现异步处理
- 支持日志记录:可以将命令对象存储在日志中,用于系统恢复
缺点
- 增加系统复杂度:可能会导致某些系统有过多的具体命令类
- 内存开销:每个命令对象都需要占用一定的内存空间
命令模式与其他模式的比较
与策略模式的区别
- 命令模式:封装请求,支持撤销操作
- 策略模式:封装算法,支持算法切换
与模板方法模式的区别
- 命令模式:通过组合实现,支持运行时切换
- 模板方法模式:通过继承实现,算法骨架固定
与责任链模式的区别
- 命令模式:封装单个请求
- 责任链模式:将请求沿着处理者链传递
命令模式的变体
1. 宏命令
// 宏命令
class MacroCommand implements Command {
private Command[] commands;
public MacroCommand(Command[] commands) {
this.commands = commands;
}
@Override
public void execute() {
System.out.println("执行宏命令:");
for (Command command : commands) {
command.execute();
}
}
@Override
public void undo() {
System.out.println("撤销宏命令:");
// 逆序撤销
for (int i = commands.length - 1; i >= 0; i--) {
commands[i].undo();
}
}
}
2. 带日志的命令
// 带日志的命令
abstract class LoggedCommand implements Command {
protected String commandName;
public LoggedCommand(String commandName) {
this.commandName = commandName;
}
@Override
public void execute() {
logExecution();
doExecute();
}
protected abstract void doExecute();
private void logExecution() {
System.out.println("[" + new java.util.Date() + "] 执行命令: " + commandName);
}
}
实际项目中的应用
// 文本编辑器示例
// 文本编辑器(接收者)
class TextEditor {
private StringBuilder content = new StringBuilder();
private List<String> history = new ArrayList<>();
public void append(String text) {
history.add(content.toString());
content.append(text);
System.out.println("添加文本: " + text);
}
public void delete(int start, int end) {
if (start >= 0 && end <= content.length() && start < end) {
history.add(content.toString());
content.delete(start, end);
System.out.println("删除文本: 位置" + start + "到" + end);
}
}
public void insert(int position, String text) {
if (position >= 0 && position <= content.length()) {
history.add(content.toString());
content.insert(position, text);
System.out.println("在位置" + position + "插入文本: " + text);
}
}
public void undo() {
if (!history.isEmpty()) {
String previousState = history.remove(history.size() - 1);
content = new StringBuilder(previousState);
System.out.println("撤销操作");
}
}
public String getContent() {
return content.toString();
}
}
// 抽象命令
abstract class TextCommand implements Command {
protected TextEditor editor;
public TextCommand(TextEditor editor) {
this.editor = editor;
}
@Override
public abstract void execute();
@Override
public void undo() {
editor.undo();
}
}
// 添加文本命令
class AppendCommand extends TextCommand {
private String text;
public AppendCommand(TextEditor editor, String text) {
super(editor);
this.text = text;
}
@Override
public void execute() {
editor.append(text);
}
}
// 删除文本命令
class DeleteCommand extends TextCommand {
private int start;
private int end;
public DeleteCommand(TextEditor editor, int start, int end) {
super(editor);
this.start = start;
this.end = end;
}
@Override
public void execute() {
editor.delete(start, end);
}
}
// 插入文本命令
class InsertCommand extends TextCommand {
private int position;
private String text;
public InsertCommand(TextEditor editor, int position, String text) {
super(editor);
this.position = position;
this.text = text;
}
@Override
public void execute() {
editor.insert(position, text);
}
}
// 命令管理器
class CommandManager {
private List<Command> commandHistory = new ArrayList<>();
private int currentCommandIndex = -1;
public void executeCommand(Command command) {
// 执行命令
command.execute();
// 添加到历史记录
commandHistory.add(command);
currentCommandIndex++;
// 清除重做历史
while (commandHistory.size() > currentCommandIndex + 1) {
commandHistory.remove(commandHistory.size() - 1);
}
}
public void undo() {
if (currentCommandIndex >= 0) {
Command command = commandHistory.get(currentCommandIndex);
command.undo();
currentCommandIndex--;
} else {
System.out.println("没有可撤销的操作");
}
}
public void redo() {
if (currentCommandIndex < commandHistory.size() - 1) {
currentCommandIndex++;
Command command = commandHistory.get(currentCommandIndex);
command.execute();
} else {
System.out.println("没有可重做的操作");
}
}
}
// 使用示例
public class TextEditorDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandManager commandManager = new CommandManager();
// 执行一系列操作
System.out.println("=== 执行操作 ===");
commandManager.executeCommand(new AppendCommand(editor, "Hello "));
System.out.println("当前内容: " + editor.getContent());
commandManager.executeCommand(new AppendCommand(editor, "World!"));
System.out.println("当前内容: " + editor.getContent());
commandManager.executeCommand(new InsertCommand(editor, 5, ", "));
System.out.println("当前内容: " + editor.getContent());
commandManager.executeCommand(new DeleteCommand(editor, 0, 5));
System.out.println("当前内容: " + editor.getContent());
// 撤销操作
System.out.println("\n=== 撤销操作 ===");
commandManager.undo();
System.out.println("撤销后内容: " + editor.getContent());
commandManager.undo();
System.out.println("撤销后内容: " + editor.getContent());
// 重做操作
System.out.println("\n=== 重做操作 ===");
commandManager.redo();
System.out.println("重做后内容: " + editor.getContent());
commandManager.redo();
System.out.println("重做后内容: " + editor.getContent());
}
}
// 餐厅点餐系统示例
// 厨师(接收者)
class Chef {
private String name;
public Chef(String name) {
this.name = name;
}
public void cookBurger() {
System.out.println(name + " 正在制作汉堡");
}
public void cookFries() {
System.out.println(name + " 正在制作薯条");
}
public void cookDrink() {
System.out.println(name + " 正在准备饮料");
}
}
// 订单(命令)
interface Order {
void execute();
void cancel();
String getDescription();
double getPrice();
}
// 汉堡订单
class BurgerOrder implements Order {
private Chef chef;
private int quantity;
public BurgerOrder(Chef chef, int quantity) {
this.chef = chef;
this.quantity = quantity;
}
@Override
public void execute() {
System.out.println("下单 " + quantity + " 个汉堡");
chef.cookBurger();
}
@Override
public void cancel() {
System.out.println("取消 " + quantity + " 个汉堡订单");
}
@Override
public String getDescription() {
return quantity + " 个汉堡";
}
@Override
public double getPrice() {
return quantity * 15.0;
}
}
// 薯条订单
class FriesOrder implements Order {
private Chef chef;
private String size;
public FriesOrder(Chef chef, String size) {
this.chef = chef;
this.size = size;
}
@Override
public void execute() {
System.out.println("下单 " + size + " 份薯条");
chef.cookFries();
}
@Override
public void cancel() {
System.out.println("取消 " + size + " 份薯条订单");
}
@Override
public String getDescription() {
return size + " 份薯条";
}
@Override
public double getPrice() {
switch (size) {
case "小": return 8.0;
case "中": return 12.0;
case "大": return 16.0;
default: return 12.0;
}
}
}
// 饮料订单
class DrinkOrder implements Order {
private Chef chef;
private String type;
private String size;
public DrinkOrder(Chef chef, String type, String size) {
this.chef = chef;
this.type = type;
this.size = size;
}
@Override
public void execute() {
System.out.println("下单 " + size + " 杯" + type);
chef.cookDrink();
}
@Override
public void cancel() {
System.out.println("取消 " + size + " 杯" + type + " 订单");
}
@Override
public String getDescription() {
return size + " 杯" + type;
}
@Override
public double getPrice() {
switch (size) {
case "小": return 6.0;
case "中": return 8.0;
case "大": return 10.0;
default: return 8.0;
}
}
}
// 服务员(调用者)
class Waiter {
private List<Order> orders = new ArrayList<>();
public void takeOrder(Order order) {
orders.add(order);
System.out.println("服务员记录订单: " + order.getDescription());
}
public void sendOrdersToKitchen() {
System.out.println("\n=== 厨房开始制作 ===");
for (Order order : orders) {
order.execute();
}
orders.clear();
}
public void cancelAllOrders() {
System.out.println("\n=== 取消所有订单 ===");
for (Order order : orders) {
order.cancel();
}
orders.clear();
}
public double calculateTotal() {
double total = 0;
for (Order order : orders) {
total += order.getPrice();
}
return total;
}
}
// 使用示例
public class RestaurantDemo {
public static void main(String[] args) {
// 创建厨师
Chef chef = new Chef("张师傅");
// 创建服务员
Waiter waiter = new Waiter();
// 顾客点餐
System.out.println("=== 顾客点餐 ===");
waiter.takeOrder(new BurgerOrder(chef, 2));
waiter.takeOrder(new FriesOrder(chef, "大"));
waiter.takeOrder(new DrinkOrder(chef, "可乐", "中"));
waiter.takeOrder(new DrinkOrder(chef, "雪碧", "小"));
// 计算总价
System.out.println("订单总价: $" + waiter.calculateTotal());
// 发送订单到厨房
waiter.sendOrdersToKitchen();
// 模拟另一种情况:取消订单
System.out.println("\n=== 模拟取消订单 ===");
Waiter waiter2 = new Waiter();
waiter2.takeOrder(new BurgerOrder(chef, 1));
waiter2.takeOrder(new FriesOrder(chef, "中"));
System.out.println("订单总价: $" + waiter2.calculateTotal());
waiter2.cancelAllOrders();
}
}
总结
命令模式是一种非常实用的行为型设计模式,它通过将请求封装成对象,实现了调用者和接收者之间的解耦,并支持撤销操作、队列请求等功能。在实际开发中,当我们需要将操作参数化、支持撤销重做、实现事务处理时,命令模式是一个很好的选择。
使用命令模式的关键点:
- 识别出需要封装的操作
- 定义命令接口
- 创建具体命令类封装操作
- 创建接收者实现具体业务逻辑
- 创建调用者管理命令的执行
命令模式的优点是可以支持撤销操作、队列请求、日志记录等功能,符合开闭原则,但也需要注意可能会增加系统的复杂度和内存开销。在现代Java开发中,命令模式广泛应用于GUI应用、文本编辑器、游戏开发、事务处理等场景。