命令模式:解耦请求与执行的优雅之道

引言:软件设计的解耦艺术

在软件开发中,我们常面临请求发起者请求执行者之间的紧密耦合问题。这种耦合会导致:

系统扩展困难:新增命令需要修改现有代码
功能复用受限:相同操作无法在不同上下文中重用
撤销/重做复杂:操作历史难以追踪和管理

命令模式正是为解决这类问题而生的设计模式。它将请求封装成独立对象,允许你参数化客户端使用不同的请求、队列或日志请求,并支持可撤销的操作。本文将深入解析命令模式的原理、实现及高级应用场景。


一、模式定义与核心思想

1.1 官方定义

命令模式 (Command Pattern):将请求封装成对象,从而让你使用不同的请求、队列或日志请求来参数化其他对象,并支持可撤销的操作。

1.2 设计哲学

核心原则

解耦调用与执行:调用者无需知道接收者细节
命令对象化:将操作封装为可传递的对象
支持扩展:新增命令不影响现有系统结构


二、模式结构解析

2.1 UML类图

classDiagram
    class Invoker {
        -command: Command
        +setCommand(Command)
        +executeCommand()
    }
    
    interface Command {
        <<interface>>
        +execute()
        +undo()
    }
    
    class ConcreteCommand {
        -receiver: Receiver
        -state
        +execute()
        +undo()
    }
    
    class Receiver {
        +action()
    }
    
    Invoker o--> Command
    Command <|.. ConcreteCommand
    ConcreteCommand --> Receiver

2.2 关键角色

角色 职责 示例
Invoker 触发命令执行 遥控器按钮
Command 声明执行接口 命令接口
ConcreteCommand 实现具体命令 开灯命令
Receiver 实际执行操作 电灯设备

三、代码实战:智能家居控制系统

3.1 场景描述

实现智能家居控制系统:

控制多种设备:灯、空调、音响
支持单命令执行和宏命令(组合命令)
实现撤销操作功能

3.2 核心实现

// 命令接口
public interface Command {
            
    void execute();
    void undo();
}

// 接收者:电灯
public class Light {
            
    public void on() {
            
        System.out.println("电灯已打开");
    }
    
    public void off() {
            
        System.out.println("电灯已关闭");
    }
}

// 具体命令:开灯命令
public class LightOnCommand implements Command {
            
    private final Light light;
    
    public LightOnCommand(Light light) {
            
        this.light = light;
    }
    
    @Override
    public void execute() {
            
        light.on();
    }
    
    @Override
    public void undo() {
            
        light.off();
    }
}

// 接收者:空调
public class AirConditioner {
            
    private int temperature = 26;
    
    public void setTemperature(int temp) {
            
        this.temperature = temp;
        System.out.println("空调温度设置为: " + temp + "℃");
    }
    
    public int getTemperature() {
            
        return temperature;
    }
}

// 具体命令:设置空调温度
public class SetTemperatureCommand implements Command {
            
    private final AirConditioner ac;
    private final int newTemperature;
    private int prevTemperature;
    
    public SetTemperatureCommand(AirConditioner ac, int temperature) {
            
        this.ac = ac;
        this.newTemperature = temperature;
    }
    
    @Override
    public void execute() {
            
        prevTemperature = ac.getTemperature();
        ac.setTemperature(newTemperature);
    }
    
    @Override
    public void undo() {
            
        ac.setTemperature(prevTemperature);
    }
}

// 宏命令:组合多个命令
public class MacroCommand implements Command {
            
    private final List<Command> commands = new ArrayList<>();
    
    public void addCommand(Command command) {
            
        commands.add(command);
    }
    
    @Override
    public void execute() {
            
        for (Command command : commands) {
            
            command.execute();
        }
    }
    
    @Override
    public void undo() {
            
        // 倒序执行撤销
        for (int i = commands.size() - 1; i >= 0; i--) {
            
            commands.get(i).undo();
        }
    }
}

// 调用者:遥控器
public class RemoteControl {
            
    private final Map<String, Command> commands = new HashMap<>();
    private final Stack<Command> history = new Stack<>();
    
    public void setCommand(String button, Command command) {
            
        commands.put(button, command);
    }
    
    public void pressButton(String button) {
            
        if (commands.containsKey(button)) {
            
            Command command = commands.get(button);
            command.execute();
            history.push(command);
        }
    }
    
    public void undoLastCommand() {
            
        if (!history.isEmpty()) {
            
            Command lastCommand = history.pop();
            lastCommand.undo();
        }
    }
}

3.3 客户端使用

public class SmartHomeApp {
            
    public static void main(String[] args) {
            
        // 创建设备
        Light livingRoomLight = new Light();
        AirConditioner bedroomAC = new AirConditioner();
        
        // 创建命令
        Command lightOn = new LightOnCommand(livingRoomLight);
        Command setAC25 = new SetTemperatureCommand(bedroomAC, 25);
        
        // 创建宏命令:回家场景
        MacroCommand homeScene = new MacroCommand();
        homeScene.addCommand(lightOn);
        homeScene.addCommand(setAC25);
        
        // 配置遥控器
        RemoteControl remote = new RemoteControl();
        remote.setCommand("Light", lightOn);
        remote.setCommand("AC25", setAC25);
        remote.setCommand("Home", homeScene);
        
        // 执行命令
        remote.pressButton("Home"); // 执行回家场景
        
        // 撤销操作
        remote.undoLastCommand(); // 撤销回家场景
    }
}

四、命令模式进阶技巧

4.1 命令队列与日志

实现命令日志:

public class CommandLogger {
            
    private final List<Command> log = new ArrayList<>();
    
    public void logCommand(Command command) {
            
        log.add(command);
        // 持久化到文件
        saveToFile(command);
    }
    
    public void replay() {
            
        for (Command command : log) {
            
            command.execute();
        }
    }
    
    private void saveToFile(Command command) {
            
        // 序列化命令对象到文件
    }
}

4.2 命令参数化

public class DelayCommand implements Command {
            
    private final Command command;
    private final long delayMillis;
    
    public DelayCommand(Command command, long delayMillis) {
            
        this.command = command;
        this.delayMillis = delayMillis;
    }
    
    @Override
    public void execute() {
            
        try {
            
            Thread.sleep(delayMillis);
            command.execute();
        } catch (InterruptedException e) {
            
            Thread.currentThread().interrupt();
        }
    }
    
    @Override
    public void undo() {
            
        command.undo();
    }
}

// 使用示例
Command delayedLightOn = new DelayCommand(new LightOnCommand(light), 5000);

4.3 空对象模式应用

public class NoCommand implements Command {
            
    @Override
    public void execute() {
            
        // 空实现
    }
    
    @Override
    public void undo() {
            
        // 空实现
    }
}

// 在遥控器中初始化
public RemoteControl(int slots) {
            
    for (int i = 0; i < slots; i++) {
            
        commands.add(new NoCommand());
    }
}

五、应用场景分析

5.1 典型应用场景

场景 命令模式应用 优势
GUI操作 菜单项、按钮点击 解耦UI与业务逻辑
事务系统 数据库事务操作 支持回滚机制
工作流引擎 任务执行与撤销 实现复杂流程控制
游戏开发 角色动作管理 支持重放和撤销
异步任务 线程池任务队列 任务封装与调度

5.2 使用时机判断

当出现以下情况时考虑命令模式:

需要将操作参数化
需要支持操作队列或日志
需要实现撤销/重做功能
需要解耦请求发送者和接收者


六、模式优劣辩证

6.1 优势 ✅

6.2 劣势 ❌

类数量膨胀:每个命令都需要一个具体类
复杂度增加:简单操作可能过度设计
性能开销:多层间接调用影响性能
内存占用:历史记录可能消耗大量内存


七、与策略模式对比

维度 命令模式 策略模式
目的 封装操作请求 封装算法
已关注点 请求的执行与撤销 算法的可替换性
状态 通常包含状态信息 通常无状态
典型应用 操作历史、事务 算法选择、计算策略

关键区别:命令模式已关注操作的封装与生命周期管理,策略模式已关注算法的灵活替换


八、在开源框架中的应用

8.1 Java AWT/Swing

// Action接口本质上就是命令模式
public interface Action extends ActionListener {
            
    void actionPerformed(ActionEvent e);
    // 其他方法:setEnabled, isEnabled等
}

// 具体命令实现
public class SaveAction implements Action {
            
    @Override
    public void actionPerformed(ActionEvent e) {
            
        // 执行保存操作
    }
}

// 绑定到菜单项
JMenuItem saveItem = new JMenuItem(new SaveAction());

8.2 Spring JdbcTemplate

// JdbcTemplate中的回调接口
public interface PreparedStatementCreator {
            
    PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}

// 使用示例
jdbcTemplate.update(new PreparedStatementCreator() {
            
    @Override
    public PreparedStatement createPreparedStatement(Connection con) {
            
        PreparedStatement ps = con.prepareStatement("INSERT...");
        ps.setString(1, "value");
        return ps;
    }
});

8.3 Java线程池

// Runnable接口本质上是一个命令
ExecutorService executor = Executors.newFixedThreadPool(5);

// 提交命令
executor.execute(new Runnable() {
            
    @Override
    public void run() {
            
        // 执行任务
    }
});

九、最佳实践指南

9.1 设计建议

命令接口轻量化

// 精简的命令接口
public interface Command {
              
    void execute();
    // 可选:void undo();
}

使用Lambda表达式简化

// Java 8+ 的命令实现
Command lightOn = () -> light.turnOn();
Command lightOff = () -> light.turnOff();

// 带参数的命令
BiConsumer<AirConditioner, Integer> setTemp = 
    (ac, temp) -> ac.setTemperature(temp);

命令对象池化

public class CommandPool {
              
    private static final Map<String, Command> pool = new HashMap<>();
    
    static {
              
        pool.put("LightOn", new LightOnCommand());
        // 其他命令初始化
    }
    
    public static Command getCommand(String key) {
              
        return pool.get(key);
    }
}

9.2 性能优化

命令复用:无状态命令可共享实例
轻量级命令:避免在命令中存储大量数据
懒加载:延迟创建资源密集型命令

9.3 撤销功能实现策略

策略 原理 适用场景
逆操作 执行相反操作 简单状态变更
备忘录 保存状态快照 复杂对象状态
历史记录 存储命令序列 完整操作历史

十、总结:命令模式的核心价值

命令模式通过封装请求实现了:

设计启示
将操作视为一等公民,封装为对象,赋予其生命周期管理能力

正如《设计模式》作者GoF所强调:

“命令模式将请求封装成对象,让你可以参数化客户端使用不同的请求、队列或日志请求,并支持可撤销的操作”


扩展思考

如何实现分布式命令系统?
命令模式如何与事件溯源结合?
在微服务架构下命令模式有哪些新应用?

附录:命令模式经典实现对比

实现方式 优点 缺点 适用场景
传统实现 完全符合GoF 类数量多 复杂系统
Lambda表达式 简洁灵活 不支持撤销 简单命令
方法引用 高度简洁 功能受限 已有方法适配
函数式接口 轻量级 类型安全弱 简单回调

选择建议

需要撤销功能 → 传统实现
简单操作 → Lambda表达式
适配现有方法 → 方法引用

命令模式是构建灵活、可扩展系统的关键模式,特别适合需要支持操作历史、事务管理或复杂工作流的应用场景

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容