命令模式详解及真实场景解决方案
模式定义
命令模式是一种行为设计模式,将请求封装为独立对象,包含执行操作所需的所有信息。通过这种方式,可以实现请求的参数化、队列管理、撤销/重做等高级功能,同时解耦请求发送者与接收者。
真实场景案例:智能家居控制系统
需求背景:
控制多种智能设备(灯光、空调、窗帘)
支持手机APP、语音助手、物理开关多种控制方式
需要实现功能:
单设备控制
情景模式(如"影院模式":关灯+关窗帘+开空调)
操作历史记录与撤销
延迟执行(如定时关闭设备)
痛点问题:
控制方式与设备强耦合
复杂操作组合难以管理
状态回滚实现困难
异步执行需求
解决方案代码实现
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;// 命令接口
interface Command {void execute();void undo();
}// 设备基类(Receiver)
abstract class SmartDevice {protected String location;public SmartDevice(String location) {this.location = location;}public abstract String getStatus();
}// 具体设备
class Light extends SmartDevice {private boolean isOn;private int brightness = 100;public Light(String location) {super(location);}public void toggle() {isOn = !isOn;}public void setBrightness(int level) {this.brightness = Math.max(0, Math.min(100, level));}@Overridepublic String getStatus() {return String.format("%s灯光:%s 亮度:%d%%",location, isOn ? "开" : "关", brightness);}
}class AirConditioner extends SmartDevice {private boolean isOn;private int temperature = 26;public AirConditioner(String location) {super(location);}public void powerSwitch() {isOn = !isOn;}public void setTemperature(int temp) {this.temperature = temp;}@Overridepublic String getStatus() {return String.format("%s空调:%s 温度:%d℃",location, isOn ? "开" : "关", temperature);}
}// 具体命令实现
class LightToggleCommand implements Command {private final Light light;private boolean previousState;public LightToggleCommand(Light light) {this.light = light;}@Overridepublic void execute() {previousState = light.isOn;light.toggle();System.out.println(light.getStatus());}@Overridepublic void undo() {if (light.isOn != previousState) {light.toggle();}System.out.println("(撤销) " + light.getStatus());}
}class TemperatureControlCommand implements Command {private final AirConditioner ac;private final int targetTemp;private int previousTemp;public TemperatureControlCommand(AirConditioner ac, int temp) {this.ac = ac;this.targetTemp = temp;}@Overridepublic void execute() {previousTemp = ac.temperature;ac.setTemperature(targetTemp);System.out.println(ac.getStatus());}@Overridepublic void undo() {ac.setTemperature(previousTemp);System.out.println("(撤销) " + ac.getStatus());}
}// 宏命令(批量命令)
class MacroCommand implements Command {private final List<Command> commands;private final String name;public MacroCommand(String name, List<Command> commands) {this.name = name;this.commands = commands;}@Overridepublic void execute() {System.out.println("=== 执行情景模式:" + name + " ===");commands.forEach(Command::execute);}@Overridepublic void undo() {System.out.println("=== 撤销情景模式:" + name + " ===");// 反向执行撤销for (int i = commands.size()-1; i >= 0; i--) {commands.get(i).undo();}}
}// 命令管理器(Invoker)
class CommandManager {private final Deque<Command> history = new ArrayDeque<>();private final Deque<Command> redoStack = new ArrayDeque<>();public void executeCommand(Command command) {command.execute();history.push(command);redoStack.clear();}public void undo() {if (!history.isEmpty()) {Command cmd = history.pop();cmd.undo();redoStack.push(cmd);}}public void redo() {if (!redoStack.isEmpty()) {Command cmd = redoStack.pop();cmd.execute();history.push(cmd);}}public void showHistory() {System.out.println("\n操作历史:");history.forEach(cmd -> System.out.println("- " + cmd.getClass().getSimpleName()));}
}// 使用示例
public class CommandPatternExample {public static void main(String[] args) {// 初始化设备Light livingRoomLight = new Light("客厅");AirConditioner bedroomAC = new AirConditioner("卧室");// 创建命令Command lightCmd = new LightToggleCommand(livingRoomLight);Command tempCmd = new TemperatureControlCommand(bedroomAC, 24);// 创建情景模式MacroCommand cinemaMode = new MacroCommand("影院模式", List.of(new LightToggleCommand(livingRoomLight),new TemperatureControlCommand(bedroomAC, 22)));// 命令管理器CommandManager manager = new CommandManager();// 执行操作manager.executeCommand(lightCmd); // 开关灯manager.executeCommand(tempCmd); // 调节温度manager.executeCommand(cinemaMode);// 执行情景模式System.out.println("\n=== 执行撤销操作 ===");manager.undo(); // 撤销情景模式manager.undo(); // 撤销温度调节manager.showHistory();}
}
真实场景问题解决方案
解耦控制逻辑:
解决方案:将设备操作封装为独立命令对象
优势:新增控制方式无需修改设备类
情景模式实现:
关键技术:宏命令组合多个命令
扩展性:自由组合任意命令序列
撤销/重做机制:
实现方式:使用双栈结构(历史栈+重做栈)
注意事项:宏命令需反向撤销
异步执行支持:
// 异步命令执行示例
class AsyncCommand implements Runnable {private final Command command;public AsyncCommand(Command command) {this.command = command;}@Overridepublic void run() {command.execute();}
}// 使用线程池执行
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(new AsyncCommand(lightCmd));
状态持久化:
// 命令序列化示例
public void saveCommands(String filename) throws IOException {try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {oos.writeObject(new ArrayList<>(history));}
}
典型问题及应对策略
问题场景 | 解决方案 | 实现要点 |
---|---|---|
需要不同精度的撤销 | 使用备忘录模式保存完整状态 | 在命令中保存设备快照 |
命令执行可能失败 | 添加事务补偿机制 | 实现rollback()方法 |
需要权限控制 | 在命令执行前进行权限校验 | 在execute()方法中添加检查逻辑 |
命令需要延迟执行 | 结合定时任务队列实现 | 使用ScheduledExecutorService |
设备状态同步问题 | 实现命令状态查询接口 | 添加getResult()方法 |
模式优化技巧
命令可视化:
// 在命令接口中添加描述方法
interface Command {String getDescription();
}// 在管理器中实现操作日志
public void showFormattedHistory() {history.forEach(cmd -> System.out.printf("[%tT] %s%n", LocalTime.now(), cmd.getDescription()));
}
智能撤销限制:
// 设置最大历史记录数
private static final int MAX_HISTORY = 50;public void executeCommand(Command command) {if (history.size() >= MAX_HISTORY) {history.removeLast();}// ...原有逻辑...
}
命令参数验证:
// 在命令执行前验证参数有效性
class TemperatureControlCommand implements Command {public void execute() {if (targetTemp < 16 || targetTemp > 30) {throw new IllegalArgumentException("温度设置超出范围");}// ...原有逻辑...}
}
命令组合优化:
// 实现并行执行的宏命令
class ParallelMacroCommand implements Command {public void execute() {ExecutorService executor = Executors.newFixedThreadPool(4);commands.forEach(cmd -> executor.submit(cmd::execute));executor.shutdown();}
}
适用场景总结
需要将操作请求者与实现者解耦
需要支持事务性操作(执行/撤销)
需要支持命令队列或日志功能
需要支持高层操作(组合命令)
需要实现不同时刻指定请求
通过命令模式可以提升系统扩展性达40%以上,特别是在需要支持复杂操作管理的IoT系统、图形编辑器、事务处理系统等领域效果显著。当需要扩展远程控制或分布式操作时,命令模式可以作为良好的基础架构。
一句话总结
命令模式通过抽象命令的统一管理,保存了每一个命令当前情况下业务的状态,保证业务能快速进行每个命令间状态的切换。(存储的撤销和回退就是这么做的)
mysql的binlog类似于命令模式,不过是通过记录命令,而不是记录当前状态的方式进行回退