代码界的「遥控器大师」:命令模式的魔法卷轴
一、当操作开始「排队领号」
你是否经历过这样的混乱场面?
客厅遥控器有20个按钮,每个都直连不同家电,按错一个全家电器集体蹦迪;
游戏角色同时执行「跳跃+射击+换弹」,结果卡成PPT;
想给老板演示功能,手忙脚乱点错十个步骤...
命令模式就像代码界的万能遥控器——「别直接动手!把操作写进任务卡,让按钮成为魔法卷轴!」 通过将请求封装成对象,让每个操作都能排队、撤销,甚至倒放!
二、遥控器的魔法图纸(UML图)
┌─────────────┐ ┌─────────────┐
│ Client │ │ Invoker │
├─────────────┤ ├─────────────┤
│ │─────────>│ +execute() │
└──────△──────┘ └──────┬──────┘
│ │
┌──────┴──────┐ ┌──────▼──────┐
│ Command │<─────────┤ Receiver │
├─────────────┤ ├─────────────┤
│ +execute() │ │ +action() │
└─────────────┘ └─────────────┘
- 甲方爸爸(Client):创建具体命令
- 秘书(Invoker):触发命令执行(如遥控器按钮)
- 任务卡(Command):封装操作的魔法卷轴
- 打工人(Receiver):真正干活的执行者
三、智能家居的魔法遥控(代码实战)
1. 定义打工人接口(家电基类)
// 家电打工人:所有设备必须会干活
interface HomeDevice {
void on();
void off();
}
// 具体打工人:智能灯
class SmartLight implements HomeDevice {
public void on() { System.out.println("💡 灯光亮起,氛围值+50"); }
public void off() { System.out.println("🌑 灯光熄灭,进入省电模式"); }
}
// 具体打工人:空调
class AirConditioner implements HomeDevice {
public void on() { System.out.println("❄️ 空调启动,26℃清凉世界"); }
public void off() { System.out.println("🔥 空调关闭,准备出汗吧"); }
}
2. 编写魔法卷轴(命令对象)
// 魔法卷轴基类(所有命令的模板)
abstract class DeviceCommand {
protected HomeDevice device;
public DeviceCommand(HomeDevice device) { this.device = device; }
public abstract void execute();
public abstract void undo(); // 撤销功能
}
// 开机咒语
class TurnOnCommand extends DeviceCommand {
public TurnOnCommand(HomeDevice device) { super(device); }
public void execute() { device.on(); }
public void undo() { device.off(); }
}
// 关机咒语
class TurnOffCommand extends DeviceCommand {
public TurnOffCommand(HomeDevice device) { super(device); }
public void execute() { device.off(); }
public void undo() { device.on(); }
}
3. 制作万能遥控器(调用者)
class MagicRemote {
private Map<String, DeviceCommand> buttons = new HashMap<>();
private Stack<DeviceCommand> history = new Stack<>(); // 操作历史
// 绑定按钮
public void setCommand(String button, DeviceCommand cmd) {
buttons.put(button, cmd);
}
// 按下按钮
public void pressButton(String button) {
DeviceCommand cmd = buttons.get(button);
if (cmd != null) {
cmd.execute();
history.push(cmd); // 记录操作
}
}
// 撤销按钮
public void pressUndo() {
if (!history.isEmpty()) {
DeviceCommand cmd = history.pop();
cmd.undo();
}
}
}
4. 来场智能家居秀
public class SmartHome {
public static void main(String[] args) {
// 初始化设备
HomeDevice light = new SmartLight();
HomeDevice ac = new AirConditioner();
// 配置遥控器
MagicRemote remote = new MagicRemote();
remote.setCommand("A", new TurnOnCommand(light));
remote.setCommand("B", new TurnOffCommand(light));
remote.setCommand("C", new TurnOnCommand(ac));
// 秀操作!
remote.pressButton("A"); // 开灯
remote.pressButton("C"); // 开空调
remote.pressUndo(); // 撤销开空调 → 关空调
remote.pressUndo(); // 撤销开灯 → 关灯
}
}
四、遥控器 vs 开关直连:魔法与物理的区别
| 维度 | 命令模式 | 直接调用 |
|---|---|---|
| 解耦程度 | 调用者与执行者完全解耦 | 紧密耦合 |
| 扩展性 | 轻松支持撤销/重做 | 需额外实现历史记录 |
| 灵活性 | 命令可组合成宏命令 | 难以批量操作 |
| 复杂度 | 需要创建命令类 | 简单直接 |
| 现实类比 | 外卖订单系统 | 顾客直接进厨房做饭 |
五、代码遥控器的真实战场
- GUI菜单项:每个按钮点击对应一个命令对象
- 游戏控制:WASD按键映射到角色移动命令
- 事务系统:数据库操作支持回滚
- 任务队列:异步处理网络请求队列
- 宏命令:一键执行「保存+格式化+提交代码」
冷知识:
Photoshop的历史记录面板就是命令模式的经典应用,每个操作都是一个可撤销的命令对象!
六、防魔法反噬指南
- 避免命令爆炸:
当命令超过50个时,考虑用「参数化命令」
例:CreateFileCommand、DeleteFileCommand → FileOperationCommand(type)
- 支持复合命令:
class MacroCommand implements DeviceCommand {
List<DeviceCommand> commands = new ArrayList<>();
void addCommand(DeviceCommand cmd) { commands.add(cmd); }
public void execute() {
commands.forEach(DeviceCommand::execute);
}
public void undo() {
// 反向执行撤销
Collections.reverse(commands);
commands.forEach(DeviceCommand::undo);
}
}
- 线程安全处理:
// 多线程环境加锁
public synchronized void pressButton(String button) {
// ...
}
- 命令持久化:
// 将命令序列化为JSON,支持断点续传
String saveCommands() {
return new Gson().toJson(history);
}
- 与责任链模式联用:
// 命令执行前经过权限校验链
command = new AuthFilterCommand(command);
command = new LogCommand(command);
command.execute();
七、魔法学院毕业总结
命令模式让代码成为时间管理大师:
- ✅ 要:用于需要解耦调用与执行的场景
- ✅ 要:支持撤销、队列、日志等扩展
- ❌ 不要:为每个简单操作创建命令类
- ❌ 不要:忽视命令对象的生命周期管理
当你在IDE中按下Ctrl+Z时,请想起命令模式——那个默默记录你每个操作的数字时光机!