命令模式:将请求封装为对象

FreeGuideOnline 最新 2026-06-18

什么是命令模式

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

通俗地讲,命令模式把“发出请求的对象”和“如何执行请求的对象”完全解耦。你不再需要直接调用某个方法,而是创建一个代表该请求的命令对象,然后由调度者来调用。这样做的好处是:请求可以延迟执行、可以排队、可以记录日志,还很容易实现撤销和重做。

为什么需要命令模式

在日常开发中,我们经常遇到这样的场景:按钮点击、菜单选择、快捷键操作,这些行为通常直接耦合在界面组件里。这样会带来几个问题:

  • 界面代码与业务逻辑紧密耦合,难以复用和测试。
  • 难以实现操作的撤销、重做、宏命令等高级功能。
  • 当需要扩展新的操作时,必须修改已有类,违背开闭原则。

命令模式通过引入命令对象,将操作本身参数化,完美解决了上述问题。

命令模式的核心角色

  • Command(命令接口):声明执行操作的接口,通常包含 execute() 方法,有的还会包含 undo() 方法。
  • ConcreteCommand(具体命令):绑定一个接收者对象,调用接收者的相应操作,实现 execute()
  • Invoker(调用者/触发者):要求命令对象执行请求,它可以持有命令对象,并在合适的时机调用其 execute()
  • Receiver(接收者):真正执行具体业务逻辑的对象,任何类都可能充当接收者。
  • Client(客户端):创建具体命令对象并设置它的接收者,同时将命令对象交给调用者。

命令模式的类图结构

+----------------+          +-------------------+
|    Invoker     |          |    <<interface>>  |
|----------------|   uses   |     Command       |
| - command      |<---------|-------------------|
| + setCommand() |          | + execute()       |
| + invoke()     |          | + undo()          |
+----------------+          +-------------------+
                                     ^
                                     |
                         +-----------+-----------+
                         |                       |
                  +------------------+  +------------------+
                  |ConcreteCommandA  |  |ConcreteCommandB  |
                  |------------------|  |------------------|
                  | - receiver       |  | - receiver       |
                  | - params         |  | - params         |
                  | + execute()      |  | + execute()      |
                  | + undo()         |  | + undo()         |
                  +--------+---------+  +--------+---------+
                           |                     |
                           v                     v
                  +------------------+  +------------------+
                  |    Receiver      |  |    Receiver      |
                  |------------------|  |------------------|
                  | + action()       |  | + action()       |
                  +------------------+  +------------------+

一个简单易懂的代码示例

假设我们正在开发一个智能家居遥控器,遥控器的每个按钮可以执行不同设备的操作。这里使用命令模式来实现解耦。

1. 定义接收者:电灯与音响

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

// 接收者:音响
class Stereo {
    public void on() {
        System.out.println("音响已打开");
    }
    public void off() {
        System.out.println("音响已关闭");
    }
    public void setVolume(int level) {
        System.out.println("音响音量设置为:" + level);
    }
}

2. 定义命令接口及具体命令

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

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

// 关灯命令
class LightOffCommand implements Command {
    private Light light;
    
    public LightOffCommand(Light light) {
        this.light = light;
    }
    
    @Override
    public void execute() {
        light.off();
    }
    
    @Override
    public void undo() {
        light.on();
    }
}

// 音响开并设置音量命令(组合操作)
class StereoOnWithVolumeCommand implements Command {
    private Stereo stereo;
    
    public StereoOnWithVolumeCommand(Stereo stereo) {
        this.stereo = stereo;
    }
    
    @Override
    public void execute() {
        stereo.on();
        stereo.setVolume(11);
    }
    
    @Override
    public void undo() {
        stereo.off();
    }
}

3. 定义调用者:遥控器

class RemoteControl {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
    
    public void pressUndo() {
        command.undo();
    }
}

4. 客户端使用

public class Client {
    public static void main(String[] args) {
        // 创建接收者
        Light livingRoomLight = new Light();
        Stereo stereo = new Stereo();
        
        // 创建命令对象
        Command lightOn = new LightOnCommand(livingRoomLight);
        Command lightOff = new LightOffCommand(livingRoomLight);
        Command stereoOn = new StereoOnWithVolumeCommand(stereo);
        
        // 创建遥控器
        RemoteControl remote = new RemoteControl();
        
        // 绑定命令
        remote.setCommand(lightOn);
        remote.pressButton();   // 输出:电灯已打开
        remote.pressUndo();     // 输出:电灯已关闭
        
        remote.setCommand(stereoOn);
        remote.pressButton();   // 输出:音响已打开 \n 音响音量设置为:11
    }
}

通过这个例子可以看到,遥控器只需知道命令接口,完全不用关心具体是哪个设备、执行什么操作。新增设备时,我们只需新增对应的命令类,无需修改遥控器代码,符合开闭原则。同时,撤销功能也自然地集成在了命令内部。

命令模式的高级应用

宏命令(组合命令)

宏命令本身也是一个命令,但它内部可以包含一组命令。调用宏命令的 execute() 会依次执行内部所有命令。

class MacroCommand implements Command {
    private Command[] commands;
    
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }
    
    @Override
    public void execute() {
        for (Command cmd : commands) {
            cmd.execute();
        }
    }
    
    @Override
    public void undo() {
        // 撤销时一般逆序执行
        for (int i = commands.length - 1; i >= 0; i--) {
            commands[i].undo();
        }
    }
}

宏命令非常适合实现“一键执行多个操作”的批量处理场景,比如智能家居的“回家模式”(开灯、开空调、播放音乐)。

命令队列与日志请求

命令对象可以被存入队列中,由工作线程逐一取出执行,从而实现异步任务处理。此外,可以将命令序列化到磁盘,形成操作日志,在系统崩溃后可以重新加载并执行日志中的命令来恢复状态。

撤销/重做(Undo/Redo)

通过维护一个命令历史栈,可以轻松实现多步撤销和重做。执行命令时将其压入历史栈;撤销时弹出并调用 undo();还可以将撤销的命令暂存到另一个重做栈中,支持 redo()

命令模式的优缺点

优点

  • 解耦请求者和执行者:调用者无需了解接收者的具体实现,只需操作命令接口。
  • 易扩展:新增命令只需实现接口,对已有系统无影响,符合开闭原则。
  • 支持撤销/重做:命令对象内部可以封装状态,实现回滚操作。
  • 可将命令组合:支持宏命令,构建复杂操作。
  • 可对命令进行排队、记录日志:命令对象可持久化,用于日志记录、事务等。

缺点

  • 类数量可能膨胀:每个具体操作都需要一个命令类,系统复杂度会略微增加。
  • 增加了一层间接性:对于简单调用场景,可能会显得过度设计。

适用场景

  • 需要将操作参数化,例如菜单项、按钮的回调操作。
  • 需要支持撤销、重做、事务回滚等需求。
  • 需要记录操作日志,以便在系统崩溃后恢复。
  • 需要将请求放入队列中异步执行,或者在不同时间执行请求。
  • 需要将一组操作组合成宏命令执行。

小结

命令模式的核心思想是将请求封装为对象,从而获得对请求的完全控制权。它让代码结构更加清晰,解耦了调用者和实现者,是许多框架和工具的基础设计模式(如GUI事件处理、线程池任务调度、撤销功能等)。掌握命令模式,能够帮助你在合适的场景下写出更灵活、更易维护的代码。