将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
这是命令模式(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;
}
@Override
public void execute() {
light.turnOn();
}
@Override
public void undo() {
light.turnOff();
}
}
// 关灯命令
public class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.turnOff();
}
@Override
public 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;
}
@Override
public void execute() {
previousTemperature = ac.getTemperature(); // 保存之前的状态
ac.setTemperature(temperature);
}
@Override
public 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 {
@Override
public void execute() {
// 什么都不做
}
@Override
public 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;
}
@Override
public void execute() {
for (Command command : commands) {
command.execute();
}
}
@Override
public 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() { // 这其实就是一个命令对象!
@Override
public 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;
@Override
public void execute() {
previousPosition = character.getPosition();
character.move(direction);
}
@Override
public 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();
}
// 对于不需要撤销的简单场景非常实用
七、总结
命令模式通过将请求封装成对象,实现了请求发送者与接收者的解耦,让软件设计更加灵活和可扩展。它就像是在请求的发送者和接收者之间架起了一座桥梁,让信息可以更加优雅地传递。
核心价值:
-
✅ 解耦:发送者与接收者分离
-
✅ 灵活:容易扩展新命令
-
✅ 强大:支持撤销、队列、日志等高级功能
-
✅ 复用:命令对象可以在不同场景下复用
下次当你需要设计一个灵活的操作系统时,记得考虑命令模式------它能让你的代码像智能遥控器一样强大而优雅!