解耦的艺术:深入理解设计模式之命令模式

将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。

这是命令模式(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
        }
    }
}

这个设计有什么问题?​

  1. 紧耦合​:遥控器直接依赖于具体的设备类

  2. 难以扩展​:每增加一个新设备,都要修改RemoteControl类

  3. 违反开闭原则​:对扩展开放,但对修改不开放的原则被破坏

  4. 功能有限​:难以实现撤销、宏命令(一键执行多个命令)等高级功能

二、命令模式的解决方案

命令模式的核心思想是:​将"请求"封装成对象。这样,客户端不需要知道请求的具体细节,只需要调用命令对象的统一接口。

命令模式的 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();
}

// 对于不需要撤销的简单场景非常实用

七、总结

命令模式通过将请求封装成对象,实现了请求发送者与接收者的解耦,让软件设计更加灵活和可扩展。它就像是在请求的发送者和接收者之间架起了一座桥梁,让信息可以更加优雅地传递。

核心价值​:

  • ✅ ​解耦​:发送者与接收者分离

  • ✅ ​灵活​:容易扩展新命令

  • ✅ ​强大​:支持撤销、队列、日志等高级功能

  • ✅ ​复用​:命令对象可以在不同场景下复用

下次当你需要设计一个灵活的操作系统时,记得考虑命令模式------它能让你的代码像智能遥控器一样强大而优雅!

相关推荐
Meteors.5 小时前
23种设计模式——外观模式(Facade Pattern)详解
设计模式·外观模式
胖虎15 小时前
iOS中的设计模式(九)- 外观模式 用外观模式点一份外卖:Swift 实战讲解
设计模式·外观模式
Asort7 小时前
JavaScript设计模式(十六)——迭代器模式:优雅遍历数据的艺术
前端·javascript·设计模式
昨天的猫7 小时前
原来我们写的单例还存在缺陷~~
设计模式
Tiny_React8 小时前
智能体设计模式-CH13:人类参与环节(Human-in-the-Loop)
设计模式
Tiny_React8 小时前
智能体设计模式-CH09:学习与适应(Learning and Adaptation)
设计模式
Tiny_React8 小时前
智能体设计模式-CH10:模型上下文协议(Model Context Protocol)
设计模式
Tiny_React8 小时前
智能体设计模式-CH11:目标设定与监控(Goal Setting and Monitoring)
设计模式
Deschen19 小时前
设计模式-外观模式
java·设计模式·外观模式