解耦的艺术:深入理解设计模式之命令模式
将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
这是命令模式(Command Pattern)的经典定义。听起来有些抽象?别担心,让我们从一个真实的问题开始,逐步揭开它的神秘面纱。
一、一个头疼的问题:遥控器的设计
假设你正在为一个智能家居系统设计一个万能遥控器。这个遥控器有7个可编程的插槽(按钮),每个插槽可以控制不同的家电设备:灯、空调、音响、车库门等。
最初的设计可能是这样的:
public class RemoteControl {private Light livingRoomLight;private AirConditioner bedroomAC;private Stereo livingRoomStereo;// ... 更多设备引用public void onButtonPressed(int slot) {switch(slot) {case 0:livingRoomLight.turnOn();break;case 1:bedroomAC.turnOn();break;case 2:livingRoomStereo.turnOn();break;// ... 更多case}}public void offButtonPressed(int slot) {switch(slot) {case 0:livingRoomLight.turnOff();break;case 1:bedroomAC.turnOff();break;case 2:livingRoomStereo.turnOff();break;// ... 更多case}}
}
这个设计有什么问题?
-
紧耦合:遥控器直接依赖于具体的设备类
-
难以扩展:每增加一个新设备,都要修改RemoteControl类
-
违反开闭原则:对扩展开放,但对修改不开放的原则被破坏
-
功能有限:难以实现撤销、宏命令(一键执行多个命令)等高级功能
二、命令模式的解决方案
命令模式的核心思想是:将"请求"封装成对象。这样,客户端不需要知道请求的具体细节,只需要调用命令对象的统一接口。
命令模式的 UML 结构
Client(客户端)|v
Invoker(调用者/触发者) → Command(命令接口)| | execute()v | undo()
Receiver(接收者/执行者) ConcreteCommand(具体命令)
代码实现
1. 命令接口(Command Interface)
public interface Command {void execute();void undo(); // 支持撤销操作
}
2. 具体命令(Concrete Commands)
// 开灯命令
public class LightOnCommand implements Command {private Light light;public LightOnCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.turnOn();}@Overridepublic void undo() {light.turnOff();}
}// 关灯命令
public class LightOffCommand implements Command {private Light light;public LightOffCommand(Light light) {this.light = light;}@Overridepublic void execute() {light.turnOff();}@Overridepublic void undo() {light.turnOn();}
}// 空调温度设置命令
public class SetTemperatureCommand implements Command {private AirConditioner ac;private int temperature;private int previousTemperature;public SetTemperatureCommand(AirConditioner ac, int temperature) {this.ac = ac;this.temperature = temperature;}@Overridepublic void execute() {previousTemperature = ac.getTemperature(); // 保存之前的状态ac.setTemperature(temperature);}@Overridepublic void undo() {ac.setTemperature(previousTemperature); // 恢复到之前的状态}
}
3. 接收者(Receivers)
// 灯类
public class Light {private boolean isOn = false;public void turnOn() {isOn = true;System.out.println("灯已打开");}public void turnOff() {isOn = false;System.out.println("灯已关闭");}
}// 空调类
public class AirConditioner {private int temperature = 26;public void setTemperature(int temp) {this.temperature = temp;System.out.println("空调温度设置为: " + temp + "°C");}public int getTemperature() {return temperature;}
}
4. 调用者(Invoker) - 我们的遥控器
public class RemoteControl {private Command[] onCommands;private Command[] offCommands;private Command undoCommand; // 记录最后执行的命令,用于撤销public RemoteControl() {onCommands = new Command[7];offCommands = new Command[7];undoCommand = new NoCommand(); // 空命令,避免null检查// 初始化所有按钮为空命令Command noCommand = new NoCommand();for (int i = 0; i < 7; i++) {onCommands[i] = noCommand;offCommands[i] = noCommand;}}public void setCommand(int slot, Command onCommand, Command offCommand) {onCommands[slot] = onCommand;offCommands[slot] = offCommand;}public void onButtonWasPressed(int slot) {onCommands[slot].execute();undoCommand = onCommands[slot]; // 记录用于撤销}public void offButtonWasPressed(int slot) {offCommands[slot].execute();undoCommand = offCommands[slot]; // 记录用于撤销}public void undoButtonWasPressed() {undoCommand.undo(); // 执行撤销操作}
}// 空命令对象(Null Object模式)
public class NoCommand implements Command {@Overridepublic void execute() {// 什么都不做}@Overridepublic void undo() {// 什么都不做}
}
5. 客户端使用
public class SmartHomeApp {public static void main(String[] args) {// 创建接收者Light livingRoomLight = new Light();AirConditioner bedroomAC = new AirConditioner();// 创建命令对象Command lightOn = new LightOnCommand(livingRoomLight);Command lightOff = new LightOffCommand(livingRoomLight);Command acOn = new SetTemperatureCommand(bedroomAC, 22);Command acOff = new SetTemperatureCommand(bedroomAC, 26);// 创建调用者(遥控器)RemoteControl remote = new RemoteControl();// 设置命令到插槽remote.setCommand(0, lightOn, lightOff); // 插槽0控制灯remote.setCommand(1, acOn, acOff); // 插槽1控制空调// 使用遥控器System.out.println("=== 测试遥控器 ===");remote.onButtonWasPressed(0); // 开灯remote.onButtonWasPressed(1); // 开空调(设置22度)remote.undoButtonWasPressed(); // 撤销:空调回到之前温度System.out.println("=== 测试宏命令 ===");// 创建宏命令:一键开启"影院模式"Command[] partyOn = {lightOn, acOn};Command partyModeOn = new MacroCommand(partyOn);remote.setCommand(2, partyModeOn, lightOff);remote.onButtonWasPressed(2); // 一键开启所有设备!}
}// 宏命令:一次执行多个命令
public class MacroCommand implements Command {private Command[] commands;public MacroCommand(Command[] commands) {this.commands = commands;}@Overridepublic void execute() {for (Command command : commands) {command.execute();}}@Overridepublic void undo() {// 按相反顺序撤销for (int i = commands.length - 1; i >= 0; i--) {commands[i].undo();}}
}
三、命令模式的强大之处
1. 完美的解耦
调用者(遥控器)完全不知道接收者(具体设备)的存在,它只与命令接口交互。
2. 易于扩展
添加新设备时,只需要创建新的命令类,无需修改现有代码。
3. 支持高级功能
-
撤销/重做:记录命令历史
-
事务处理:一系列命令要么全部成功,要么全部失败
-
宏命令:组合多个命令
-
日志记录:记录所有执行过的命令
-
队列请求:将命令放入队列,延迟执行
4. 支持异步执行
命令对象可以很容易地在不同线程中执行。
四、实际应用场景
1. GUI 操作
// 菜单项点击命令
JMenuItem saveItem = new JMenuItem("Save");
saveItem.addActionListener(new ActionListener() { // 这其实就是一个命令对象!@Overridepublic void actionPerformed(ActionEvent e) {// 执行保存操作}
});
2. 数据库事务
public class Transaction {private List<Command> commands = new ArrayList<>();public void addCommand(Command cmd) {commands.add(cmd);}public void commit() {try {for (Command cmd : commands) {cmd.execute();}} catch (Exception e) {rollback(); // 执行失败,回滚throw e;}}public void rollback() {for (int i = commands.size() - 1; i >= 0; i--) {commands.get(i).undo();}}
}
3. 游戏开发
// 游戏角色动作命令
public class MoveCommand implements Command {private GameCharacter character;private Direction direction;private Position previousPosition;@Overridepublic void execute() {previousPosition = character.getPosition();character.move(direction);}@Overridepublic void undo() {character.setPosition(previousPosition);}
}// 支持回放功能
public class GameReplay {private List<Command> commandHistory = new ArrayList<>();public void recordCommand(Command cmd) {commandHistory.add(cmd);}public void replay() {for (Command cmd : commandHistory) {cmd.execute();}}
}
4. Android 中的例子
// 异步任务命令
class NetworkRequestCommand(private val url: String,private val callback: (Result<String>) -> Unit
) : Command {override fun execute() {CoroutineScope(Dispatchers.IO).launch {try {val result = apiService.getData(url)withContext(Dispatchers.Main) {callback(Result.success(result))}} catch (e: Exception) {withContext(Dispatchers.Main) {callback(Result.failure(e))}}}}override fun undo() {// 取消网络请求}
}
五、与其他模式的关系
-
与策略模式:命令模式关注的是请求的封装和传递,策略模式关注的是算法的选择和替换。
-
与备忘录模式:可以结合使用来实现复杂的撤销功能。
-
与责任链模式:命令可以作为责任链中的处理对象。
六、最佳实践和注意事项
✅ 使用场景:
-
需要将操作抽象为对象
-
需要支持撤销/重做功能
-
需要将请求排队或记录请求日志
-
需要支持事务操作
⚠️ 注意事项:
-
如果命令太简单,可能会产生过多的命令类
-
需要考虑命令对象的生命周期管理
-
复杂的撤销功能可能需要备忘录模式辅助
🎯 实践建议:
// 使用Lambda简化简单命令(Java 8+)
remote.setCommand(0, () -> light.turnOn(), () -> light.turnOff()
);// 使用函数式接口(更简洁)
@FunctionalInterface
public interface SimpleCommand {void execute();
}// 对于不需要撤销的简单场景非常实用
七、总结
命令模式通过将请求封装成对象,实现了请求发送者与接收者的解耦,让软件设计更加灵活和可扩展。它就像是在请求的发送者和接收者之间架起了一座桥梁,让信息可以更加优雅地传递。
核心价值:
-
✅ 解耦:发送者与接收者分离
-
✅ 灵活:容易扩展新命令
-
✅ 强大:支持撤销、队列、日志等高级功能
-
✅ 复用:命令对象可以在不同场景下复用
下次当你需要设计一个灵活的操作系统时,记得考虑命令模式——它能让你的代码像智能遥控器一样强大而优雅!
