Skip to content

命令模式(Command Pattern) ​

一、模式概述 ​

命令模式是一种行为型设计模式,它将一个请求封装为一个对象,从而使你可以用不同的请求对客户端进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

核心思想:将"请求"封装成对象,使得请求的发送者和接收者解耦。调用者(Invoker)不需要知道具体执行什么操作,只需要调用命令对象的 execute 方法即可。

二、角色说明 ​

角色说明
Command(抽象命令)声明执行操作的接口(execute、undo)
ConcreteCommand(具体命令)将接收者与动作绑定,实现 execute 和 undo
Invoker(调用者)要求命令执行请求,持有命令对象的引用
Receiver(接收者)知道如何实施与执行请求相关的操作
Client(客户端)创建具体命令对象并设置其接收者

三、生活中的例子 ​

  • 餐厅点餐:顾客(调用者)写菜单(命令),服务员传递菜单,厨师(接收者)执行
  • 遥控器:按下按钮(命令),电视(接收者)执行对应操作
  • 事务日志:数据库操作封装为命令,支持回滚(undo)和重做(redo)
  • 任务队列:将任务封装为命令,放入队列中异步执行

四、示例1:智能家居遥控器 ​

问题场景 ​

一个智能遥控器可以控制灯光、空调、音响等多个设备,支持执行和撤销操作。

java
import java.util.Stack;

// ========== 接收者 ==========

/**
 * 灯光(接收者)
 */
class Light {
    private String location;

    public Light(String location) {
        this.location = location;
    }

    public void on() {
        System.out.println("  [灯光] " + location + " 灯已打开");
    }

    public void off() {
        System.out.println("  [灯光] " + location + " 灯已关闭");
    }

    public void dim(int level) {
        System.out.println("  [灯光] " + location + " 灯亮度调至 " + level + "%");
    }
}

/**
 * 空调(接收者)
 */
class AirConditioner {
    private String location;

    public AirConditioner(String location) {
        this.location = location;
    }

    public void on() {
        System.out.println("  [空调] " + location + " 空调已开启");
    }

    public void off() {
        System.out.println("  [空调] " + location + " 空调已关闭");
    }

    public void setTemperature(int temp) {
        System.out.println("  [空调] " + location + " 温度设为 " + temp + "°C");
    }
}

/**
 * 音响(接收者)
 */
class Stereo {
    public void on() {
        System.out.println("  [音响] 音响已开启");
    }

    public void off() {
        System.out.println("  [音响] 音响已关闭");
    }

    public void setVolume(int volume) {
        System.out.println("  [音响] 音量设为 " + volume);
    }

    public void setSource(String source) {
        System.out.println("  [音响] 音源设为: " + source);
    }
}

/**
 * 电视(接收者)
 */
class Television {
    public void on() {
        System.out.println("  [电视] 电视已开启");
    }

    public void off() {
        System.out.println("  [电视] 电视已关闭");
    }

    public void setChannel(int channel) {
        System.out.println("  [电视] 切换到频道 " + channel);
    }
}

// ========== 抽象命令 ==========

/**
 * 抽象命令接口
 */
interface SmartCommand {
    void execute();
    void undo();
    String getDescription();
}

// ========== 具体命令 ==========

/**
 * 开灯命令
 */
class LightOnCommand implements SmartCommand {
    private Light light;
    private boolean previousState;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        previousState = false;  // 记录之前的关灯状态
        light.on();
    }

    @Override
    public void undo() {
        light.off();  // 撤销就是关灯
    }

    @Override
    public String getDescription() {
        return "开灯";
    }
}

/**
 * 关灯命令
 */
class LightOffCommand implements SmartCommand {
    private Light light;
    private boolean previousState;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        previousState = true;  // 记录之前的开灯状态
        light.off();
    }

    @Override
    public void undo() {
        light.on();  // 撤销就是开灯
    }

    @Override
    public String getDescription() {
        return "关灯";
    }
}

/**
 * 空调制冷命令
 */
class ACOnCommand implements SmartCommand {
    private AirConditioner ac;
    private int previousTemp;

    public ACOnCommand(AirConditioner ac) {
        this.ac = ac;
    }

    @Override
    public void execute() {
        previousTemp = 26;  // 记录之前的温度
        ac.on();
        ac.setTemperature(24);
    }

    @Override
    public void undo() {
        ac.off();
        ac.setTemperature(previousTemp);
    }

    @Override
    public String getDescription() {
        return "空调制冷";
    }
}

/**
 * 音响开机命令
 */
class StereoOnCommand implements SmartCommand {
    private Stereo stereo;

    public StereoOnCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.on();
        stereo.setVolume(15);
        stereo.setSource("蓝牙");
    }

    @Override
    public void undo() {
        stereo.off();
    }

    @Override
    public String getDescription() {
        return "音响开机";
    }
}

/**
 * 电视换台命令
 */
class TVSetChannelCommand implements SmartCommand {
    private Television tv;
    private int channel;
    private int previousChannel;

    public TVSetChannelCommand(Television tv, int channel) {
        this.tv = tv;
        this.channel = channel;
    }

    @Override
    public void execute() {
        previousChannel = 1;  // 记录之前的频道
        tv.setChannel(channel);
    }

    @Override
    public void undo() {
        tv.setChannel(previousChannel);
    }

    @Override
    public String getDescription() {
        return "电视换台到频道 " + channel;
    }
}

/**
 * 空命令(占位用,避免空指针检查)
 */
class NoCommand implements SmartCommand {
    @Override
    public void execute() {
        // 什么都不做
    }

    @Override
    public void undo() {
        // 什么都不做
    }

    @Override
    public String getDescription() {
        return "无操作";
    }
}

// ========== 宏命令(组合多个命令) ==========

/**
 * 宏命令:一次性执行多个命令
 */
class MacroCommand implements SmartCommand {
    private SmartCommand[] commands;
    private String description;

    public MacroCommand(String description, SmartCommand... commands) {
        this.description = description;
        this.commands = commands;
    }

    @Override
    public void execute() {
        System.out.println("  执行宏命令: " + description);
        for (SmartCommand command : commands) {
            command.execute();
        }
    }

    @Override
    public void undo() {
        System.out.println("  撤销宏命令: " + description);
        // 撤销时反向执行
        for (int i = commands.length - 1; i >= 0; i--) {
            commands[i].undo();
        }
    }

    @Override
    public String getDescription() {
        return description;
    }
}

// ========== 调用者 ==========

/**
 * 智能遥控器(调用者)
 * 持有命令对象的引用,调用命令的 execute 方法
 */
class SmartRemote {
    private SmartCommand[] slotCommands;  // 按钮槽位
    private SmartCommand[] slotUndos;     // 撤销槽位
    private Stack<SmartCommand> history;  // 命令历史(支持撤销)

    public SmartRemote(int slots) {
        this.slotCommands = new SmartCommand[slots];
        this.slotUndos = new SmartCommand[slots];
        this.history = new Stack<>();
        // 初始化为空命令
        for (int i = 0; i < slots; i++) {
            slotCommands[i] = new NoCommand();
            slotUndos[i] = new NoCommand();
        }
    }

    /**
     * 设置按钮对应的命令
     */
    public void setCommand(int slot, SmartCommand onCommand, SmartCommand offCommand) {
        slotCommands[slot] = onCommand;
        slotUndos[slot] = offCommand;
    }

    /**
     * 按下按钮(执行命令)
     */
    public void pressOnButton(int slot) {
        System.out.println("按下按钮 " + (slot + 1) + ": " + slotCommands[slot].getDescription());
        slotCommands[slot].execute();
        history.push(slotCommands[slot]);  // 记录到历史
    }

    /**
     * 按下关闭按钮
     */
    public void pressOffButton(int slot) {
        System.out.println("按下按钮 " + (slot + 1) + ": " + slotUndos[slot].getDescription());
        slotUndos[slot].execute();
        history.push(slotUndos[slot]);
    }

    /**
     * 撤销上一步操作
     */
    public void pressUndo() {
        if (!history.isEmpty()) {
            SmartCommand lastCommand = history.pop();
            System.out.println("撤销操作: " + lastCommand.getDescription());
            lastCommand.undo();
        } else {
            System.out.println("没有可撤销的操作!");
        }
    }
}

// ========== 客户端代码 ==========

public class SmartHomeDemo {
    public static void main(String[] args) {
        // 创建接收者
        Light livingRoomLight = new Light("客厅");
        Light bedroomLight = new Light("卧室");
        AirConditioner ac = new AirConditioner("客厅");
        Stereo stereo = new Stereo();
        Television tv = new Television();

        // 创建命令
        SmartCommand livingLightOn  = new LightOnCommand(livingRoomLight);
        SmartCommand livingLightOff = new LightOffCommand(livingRoomLight);
        SmartCommand bedroomLightOn  = new LightOnCommand(bedroomLight);
        SmartCommand bedroomLightOff = new LightOffCommand(bedroomLight);
        SmartCommand acOn  = new ACOnCommand(ac);
        SmartCommand stereoOn = new StereoOnCommand(stereo);
        SmartCommand tvChannel = new TVSetChannelCommand(tv, 5);

        // 创建遥控器
        SmartRemote remote = new SmartRemote(5);

        // 设置按钮
        remote.setCommand(0, livingLightOn, livingLightOff);
        remote.setCommand(1, bedroomLightOn, bedroomLightOff);
        remote.setCommand(2, acOn, new NoCommand());
        remote.setCommand(3, stereoOn, new NoCommand());
        remote.setCommand(4, tvChannel, new NoCommand());

        // 使用遥控器
        System.out.println("===== 操作1:打开客厅灯 =====");
        remote.pressOnButton(0);

        System.out.println("\n===== 操作2:打开卧室灯 =====");
        remote.pressOnButton(1);

        System.out.println("\n===== 操作3:打开空调 =====");
        remote.pressOnButton(2);

        System.out.println("\n===== 撤销上一步 =====");
        remote.pressUndo();

        System.out.println("\n===== 宏命令:回家模式 =====");
        SmartCommand homeMode = new MacroCommand("回家模式",
            livingLightOn, acOn, stereoOn);
        homeMode.execute();

        System.out.println("\n===== 撤销回家模式 =====");
        homeMode.undo();
    }
}

五、示例2:文本编辑器撤销/重做系统 ​

问题场景 ​

文本编辑器支持输入、删除操作,并支持撤销(undo)和重做(redo)。

java
import java.util.Stack;
import java.util.ArrayList;
import java.util.List;

// ========== 接收者 ==========

/**
 * 文本编辑器(接收者)
 */
class TextEditor {
    private StringBuilder content = new StringBuilder();

    public void insert(int position, String text) {
        content.insert(position, text);
        System.out.println("  [编辑器] 在位置 " + position + " 插入: \"" + text + "\"");
        System.out.println("  [当前内容] \"" + content + "\"");
    }

    public String delete(int position, int length) {
        String deleted = content.substring(position, position + length);
        content.delete(position, position + length);
        System.out.println("  [编辑器] 从位置 " + position + " 删除 " + length + " 个字符: \"" + deleted + "\"");
        System.out.println("  [当前内容] \"" + content + "\"");
        return deleted;
    }

    public String getContent() {
        return content.toString();
    }
}

// ========== 抽象命令 ==========

/**
 * 编辑命令(支持撤销和重做)
 */
interface EditCommand {
    void execute();
    void undo();
    void redo();
}

// ========== 具体命令 ==========

/**
 * 输入命令
 */
class InsertCommand implements EditCommand {
    private TextEditor editor;
    private int position;
    private String text;

    public InsertCommand(TextEditor editor, int position, String text) {
        this.editor = editor;
        this.position = position;
        this.text = text;
    }

    @Override
    public void execute() {
        editor.insert(position, text);
    }

    @Override
    public void undo() {
        editor.delete(position, text.length());
    }

    @Override
    public void redo() {
        editor.insert(position, text);
    }
}

/**
 * 删除命令
 */
class DeleteCommand implements EditCommand {
    private TextEditor editor;
    private int position;
    private int length;
    private String deletedText;  // 保存删除的文本,用于撤销

    public DeleteCommand(TextEditor editor, int position, int length) {
        this.editor = editor;
        this.position = position;
        this.length = length;
    }

    @Override
    public void execute() {
        deletedText = editor.delete(position, length);
    }

    @Override
    public void undo() {
        editor.insert(position, deletedText);
    }

    @Override
    public void redo() {
        editor.delete(position, length);
    }
}

// ========== 命令管理器(支持 undo/redo) ==========

/**
 * 命令管理器
 * 管理命令历史,支持撤销和重做
 */
class CommandHistory {
    private Stack<EditCommand> undoStack = new Stack<>();
    private Stack<EditCommand> redoStack = new Stack<>();

    /**
     * 执行命令并记录
     */
    public void executeCommand(EditCommand command) {
        command.execute();
        undoStack.push(command);
        redoStack.clear();  // 新命令清空重做栈
        System.out.println("  [历史] 已执行,可撤销 " + undoStack.size() + " 步");
    }

    /**
     * 撤销
     */
    public boolean undo() {
        if (undoStack.isEmpty()) {
            System.out.println("  [历史] 没有可撤销的操作!");
            return false;
        }
        EditCommand command = undoStack.pop();
        command.undo();
        redoStack.push(command);
        System.out.println("  [历史] 已撤销,可重做 " + redoStack.size() + " 步");
        return true;
    }

    /**
     * 重做
     */
    public boolean redo() {
        if (redoStack.isEmpty()) {
            System.out.println("  [历史] 没有可重做的操作!");
            return false;
        }
        EditCommand command = redoStack.pop();
        command.redo();
        undoStack.push(command);
        System.out.println("  [历史] 已重做,可撤销 " + undoStack.size() + " 步");
        return true;
    }

    /**
     * 显示历史
     */
    public void showHistory() {
        System.out.println("  [历史] 撤销栈: " + undoStack.size() + " 步");
        System.out.println("  [历史] 重做栈: " + redoStack.size() + " 步");
    }
}

// ========== 客户端代码 ==========

public class TextEditorDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        CommandHistory history = new CommandHistory();

        System.out.println("===== 输入文本 =====");
        history.executeCommand(new InsertCommand(editor, 0, "Hello"));
        history.executeCommand(new InsertCommand(editor, 5, " World"));
        history.executeCommand(new InsertCommand(editor, 11, " Java"));

        System.out.println("\n===== 撤销最后一步 =====");
        history.undo();

        System.out.println("\n===== 再撤销一步 =====");
        history.undo();

        System.out.println("\n===== 重做 =====");
        history.redo();

        System.out.println("\n===== 删除文本 =====");
        history.executeCommand(new DeleteCommand(editor, 5, 6));

        System.out.println("\n===== 撤销删除 =====");
        history.undo();

        System.out.println("\n===== 最终内容 =====");
        System.out.println("  编辑器内容: \"" + editor.getContent() + "\"");

        history.showHistory();
    }
}

六、UML 类图 ​

┌────────────┐     调用      ┌──────────────┐
│   Client    │─────────────▶│   Invoker     │
└────────────┘              │  (Remote)     │
                            │  +pressButton()│
                            └───────┬───────┘
                                    │ 持有
                                    ▼
                            ┌──────────────┐
                            │   Command     │ ◀── 抽象命令
                            │  +execute()   │
                            │  +undo()      │
                            └──────┬───────┘
                                   │ 实现
                    ┌──────────────┼──────────────┐
                    │              │              │
              ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐
              │Concrete    │ │Concrete    │ │Macro      │
              │Command1    │ │Command2    │ │Command    │
              │+execute()  │ │+execute()  │ │+execute() │
              └─────┬──────┘ └─────┬──────┘ └───────────┘
                    │              │
                    ▼              ▼
              ┌─────────────────────────┐
              │      Receiver            │
              │   (Light, TV, Stereo)    │
              └─────────────────────────┘

七、命令队列(异步执行) ​

java
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * 命令队列:支持异步执行命令
 */
class CommandQueue {
    private final BlockingQueue<SmartCommand> queue = new LinkedBlockingQueue<>();
    private volatile boolean running = true;

    public void start() {
        Thread worker = new Thread(() -> {
            while (running) {
                try {
                    SmartCommand command = queue.take();  // 阻塞等待命令
                    System.out.println("[命令队列] 执行命令: " + command.getDescription());
                    command.execute();
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }, "CommandWorker");
        worker.setDaemon(true);
        worker.start();
    }

    public void addCommand(SmartCommand command) {
        try {
            queue.put(command);
            System.out.println("[命令队列] 命令入队: " + command.getDescription()
                    + " (队列大小: " + queue.size() + ")");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    public void stop() {
        running = false;
    }
}

// ========== 客户端代码 ==========

public class CommandQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        CommandQueue queue = new CommandQueue();
        queue.start();

        // 添加命令到队列
        Light light = new Light("客厅");
        queue.addCommand(new LightOnCommand(light));
        queue.addCommand(new LightOffCommand(light));
        queue.addCommand(new LightOnCommand(light));

        Thread.sleep(1000);  // 等待命令执行完成
        queue.stop();
    }
}

八、实际应用场景 ​

java
/**
 * 1. Spring 事务管理中的命令模式
 *
 * @Transactional
 * public void transferMoney(Account from, Account to, double amount) {
 *     from.debit(amount);    // 命令1
 *     to.credit(amount);     // 命令2
 *     // 事务提交或回滚(undo)
 * }
 */

/**
 * 2. 数据库操作日志
 *
 * 所有数据库操作都封装为命令对象,
 * 可以记录到日志中,支持回放和审计
 */

/**
 * 3. 任务调度系统
 *
 * 定时任务 = 命令 + 触发器
 * Timer.schedule(command, delay, period)
 */

/**
 * 4. GUI 按钮和菜单
 *
 * 每个按钮绑定一个命令对象
 * button.addActionListener(e -> command.execute());
 */

九、优缺点分析 ​

优点 ​

  1. 解耦:调用者和接收者之间没有直接关系
  2. 可扩展:新增命令不需要修改现有代码(开闭原则)
  3. 支持撤销/重做:通过记录命令历史实现 undo/redo
  4. 支持命令队列:可以将命令放入队列中异步执行
  5. 支持宏命令:可以组合多个命令为一个复合命令
  6. 支持日志和事务:可以记录命令的执行历史

缺点 ​

  1. 类数量增加:每个命令都需要一个具体的命令类
  2. 复杂度增加:对于简单场景,引入命令模式反而增加了复杂度
  3. 过度设计:如果只是简单的请求-响应,不需要命令模式

十、使用场景 ​

  • GUI 按钮和菜单:将用户操作封装为命令
  • 撤销/重做功能:文本编辑器、图形编辑器
  • 任务队列:将任务封装为命令,异步执行
  • 事务管理:数据库操作的提交和回滚
  • 日志记录:记录所有操作,支持回放
  • 宏命令:将多个命令组合为一个复合命令

十一、与其他模式的关系 ​

  • 策略模式 vs 命令模式:策略模式关注算法的选择,命令模式关注请求的封装
  • 备忘录模式:命令模式常与备忘录模式结合,实现撤销功能
  • 组合模式:宏命令使用组合模式来组合多个命令
  • 观察者模式:命令模式可以与观察者模式结合,实现命令的监听

十二、最佳实践 ​

  1. 为每个命令实现 undo 方法:这是命令模式的核心价值之一
  2. 使用空命令模式:避免空指针检查(Null Object Pattern)
  3. 考虑命令队列:对于耗时操作,使用命令队列异步执行
  4. 合理设计命令粒度:命令太粗或太细都不好
  5. 使用 Lambda 简化:对于简单命令,可以使用 Lambda 表达式
java
// 使用 Lambda 简单命令
interface SimpleCommand {
    void execute();
}

// 使用 Lambda 创建命令
SimpleCommand lightOn = () -> System.out.println("灯已打开");
lightOn.execute();

总结:命令模式将请求封装为对象,使得请求的发送者和接收者解耦。它在撤销/重做、任务队列、事务管理等场景中非常有用。关键在于理解"命令"就是"将操作封装为对象",这样就可以像操作数据一样操作命令(存储、传递、排队、撤销等)。