rokevin
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
移动
前端
语言
  • 基础

    • Linux
    • 实施
    • 版本构建
  • 应用

    • WEB服务器
    • 数据库
  • 资讯

    • 工具
    • 部署
开放平台
产品设计
  • 人工智能
  • 云计算
计算机
其它
GitHub
  • 命令模式(Command Pattern)

  • 一、定义
  • 二、意图
  • 三、结构
  • 四、优点
  • 五、缺点
  • 六、类图(Mermaid)
  • 七、时序图(Mermaid)
  • 八、适用环境
  • 九、模式分析
  • 十、模式扩展
  • 十一、模式应用
  • 十二、Android 中的应用
  • 十三、代码实现(Java 版)
    • 1. 命令接口(Command)
    • 2. 接收者(TVReceiver)
    • 3. 具体命令(ConcreteCommand)
    • 4. 调用者(RemoteControlInvoker)
    • 5. 客户端(Client)
    • 6. 运行结果
  • 十四、总结

命令模式(Command Pattern)

命令模式是一种行为型设计模式,它将请求封装为对象,使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。这种模式的核心是 “将行为请求者与行为实现者解耦”,通过命令对象作为中间桥梁,实现二者的间接交互。

一、定义

命令模式(Command Pattern)的官方定义为: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. (将一个请求封装成一个对象,从而允许你用不同的请求对客户端进行参数化,对请求进行排队或记录日志,以及支持可撤销的操作。)

简单来说,命令模式把 “做什么”(请求)和 “谁去做”(实现)分离开,通过一个 “命令对象” 承载请求的细节,再由 “调用者” 触发命令,“接收者” 执行命令对应的操作。

二、意图

命令模式的核心意图可归纳为以下 3 点:

  1. 解耦请求者与接收者:请求者(如按钮、菜单)无需知道接收者(如具体业务逻辑类)的存在,也无需知道请求如何被执行,只需触发命令即可。
  2. 支持请求的灵活管理:可对命令进行排队(如任务队列)、记录日志(如操作审计)、缓存(如批量执行)。
  3. 支持可撤销 / 重做操作:通过保存命令的历史记录或反向操作,实现请求的撤销(Undo)和重做(Redo)。

三、结构

命令模式包含 5 个核心角色,各角色职责明确,协同完成 “请求封装与执行” 的流程:

角色名称核心职责
命令接口(Command)定义命令的统一接口,通常包含一个执行方法(如 execute())和可选的撤销方法(如 undo())。
具体命令(ConcreteCommand)实现命令接口,绑定 “接收者” 和 “具体操作”,在 execute() 中调用接收者的对应方法。
接收者(Receiver)负责执行命令对应的具体业务逻辑(如 “打开文件”“关闭窗口”),是命令的实际执行者。
调用者(Invoker)持有命令对象,负责触发命令的执行(如 “点击按钮” 触发命令),无需关心命令的具体实现。
客户端(Client)负责创建 “具体命令” 和 “接收者”,并将接收者注入命令,最终将命令交给调用者执行。

四、优点

  1. 解耦请求者与接收者:请求者只需触发命令,无需知道接收者的存在或操作的具体实现,降低模块间耦合。
  2. 易扩展新命令:新增命令只需实现 Command 接口,无需修改现有代码,符合开闭原则。
  3. 支持请求的灵活管理:可轻松实现命令排队(如线程池任务队列)、日志记录(如操作审计日志)、事务管理(如批量命令提交 / 回滚)。
  4. 支持撤销 / 重做:通过保存命令历史记录或在命令中定义反向操作(undo()),可实现操作的撤销与重做。
  5. 命令可复用:同一个具体命令对象可被多个调用者重复使用(如 “复制” 命令可被菜单、快捷键等多个调用者触发)。

五、缺点

  1. 类数量膨胀:每一个具体操作都需要对应一个具体命令类,若系统中存在大量命令,会导致类数量急剧增加,增加维护成本。
  2. 复杂度提升:相比直接调用方法,命令模式引入了多个角色(命令、调用者、接收者),增加了系统的抽象度和理解复杂度。
  3. 简单场景下冗余:对于无需撤销、排队、日志的简单请求,使用命令模式会显得过度设计,不如直接调用方法简洁。

六、类图(Mermaid)

以下类图展示命令模式的核心角色关系(以 “遥控器控制电视” 为例:遥控器是调用者,电视是接收者,“打开电视”“关闭电视” 是具体命令):

classDiagram
    direction TB
    class Command {
        <<interface>>
        +execute() : void
        +undo() : void
    }
    
    class ConcreteCommandOpen {
        -receiver: TVReceiver
        +ConcreteCommandOpen(receiver: TVReceiver)
        +execute() : void
        +undo() : void
    }
    
    class ConcreteCommandClose {
        -receiver: TVReceiver
        +ConcreteCommandClose(receiver: TVReceiver)
        +execute() : void
        +undo() : void
    }
    
    class TVReceiver {
        +turnOn() : void
        +turnOff() : void
    }
    
    class RemoteControlInvoker {
        -command: Command
        +setCommand(command: Command) : void
        +pressButton() : void
        +pressUndoButton() : void
    }
    
    class Client {
        +main() : void
    }
    
    Command <|.. ConcreteCommandOpen
    Command <|.. ConcreteCommandClose
    ConcreteCommandOpen o-- TVReceiver : "持有"
    ConcreteCommandClose o-- TVReceiver : "持有"
    RemoteControlInvoker o-- Command : "持有"
    Client ..> RemoteControlInvoker : "创建并设置命令"
    Client ..> TVReceiver : "创建接收者"
    Client ..> ConcreteCommandOpen : "创建具体命令"
    Client ..> ConcreteCommandClose : "创建具体命令"

七、时序图(Mermaid)

以下时序图展示 “客户端通过遥控器打开电视” 的完整流程(包含命令执行和撤销操作):

sequenceDiagram
    participant Client
    participant RemoteControlInvoker as 遥控器(调用者)
    participant ConcreteCommandOpen as 打开命令
    participant TVReceiver as 电视(接收者)
    
    %% 1. 客户端初始化:创建接收者、具体命令、调用者,并绑定关系
    Client->>TVReceiver: 1. new TVReceiver() (创建接收者)
    Client->>ConcreteCommandOpen: 2. new ConcreteCommandOpen(TVReceiver) (绑定接收者到命令)
    Client->>RemoteControlInvoker: 3. new RemoteControlInvoker() (创建调用者)
    Client->>RemoteControlInvoker: 4. setCommand(ConcreteCommandOpen) (调用者绑定命令)
    
    %% 2. 触发命令执行(按下“打开”按钮)
    Client->>RemoteControlInvoker: 5. 按下“打开”按钮 → pressButton()
    RemoteControlInvoker->>ConcreteCommandOpen: 6. 调用 command.execute()
    ConcreteCommandOpen->>TVReceiver: 7. 调用 receiver.turnOn()
    TVReceiver-->>ConcreteCommandOpen: 8. 返回:电视已打开
    ConcreteCommandOpen-->>RemoteControlInvoker: 9. 返回:命令执行完成
    RemoteControlInvoker-->>Client: 10. 返回:操作成功
    
    %% 3. 触发命令撤销(按下“撤销”按钮)
    Client->>RemoteControlInvoker: 11. 按下“撤销”按钮 → pressUndoButton()
    RemoteControlInvoker->>ConcreteCommandOpen: 12. 调用 command.undo()
    ConcreteCommandOpen->>TVReceiver: 13. 调用 receiver.turnOff() (反向操作)
    TVReceiver-->>ConcreteCommandOpen: 14. 返回:电视已关闭
    ConcreteCommandOpen-->>RemoteControlInvoker: 15. 返回:撤销完成
    RemoteControlInvoker-->>Client: 16. 返回:撤销成功

八、适用环境

当系统满足以下任一场景时,适合使用命令模式:

  1. 需要解耦请求者与接收者:如 GUI 界面中,按钮(请求者)无需知道点击后具体执行什么业务逻辑(接收者)。
  2. 需要支持请求的排队 / 日志 / 事务:如订单系统的 “提交订单”“支付”“发货” 命令需排队执行;操作日志需记录每一步命令的执行情况;事务需保证多个命令要么全部执行,要么全部回滚。
  3. 需要支持撤销 / 重做操作:如文本编辑器的 “撤销输入”“重做输入”、绘图软件的 “撤销画笔”“重做画笔”。
  4. 需要动态切换命令:如遥控器可切换 “打开电视”“打开空调” 等不同命令,只需更换命令对象即可。

九、模式分析

  1. 核心逻辑:命令模式的本质是 “将请求对象化”—— 每个请求不再是直接的方法调用,而是一个包含请求信息的对象。这个对象可以被传递、存储、修改,从而实现灵活的请求管理。
  2. 依赖关系:调用者(Invoker)依赖命令接口(Command),具体命令(ConcreteCommand)依赖接收者(Receiver),客户端(Client)负责组装三者的关系,符合 “依赖倒置原则”(依赖抽象,不依赖具体)。
  3. 撤销机制的实现:
    • 方式 1:在具体命令中保存执行前的状态,undo() 方法恢复该状态(如文本编辑器保存修改前的文本内容)。
    • 方式 2:undo() 方法执行 execute() 的反向操作(如 “打开电视” 的反向是 “关闭电视”)。

十、模式扩展

命令模式可结合其他设计模式进行扩展,满足更复杂的需求:

  1. 结合备忘录模式(Memento):实现更灵活的撤销 / 重做。备忘录模式保存命令执行前的状态,命令模式的 undo() 方法通过备忘录恢复状态(适合状态复杂的场景,如文档编辑)。
  2. 结合组合模式(Composite):实现 “宏命令”(Macro Command)。将多个命令组合成一个复合命令,调用者触发一次复合命令,即可执行多个子命令(如 “关机” 命令包含 “保存文档”“关闭窗口”“切断电源” 三个子命令)。
  3. 结合原型模式(Prototype):快速复制命令对象。若需重复执行相同参数的命令,可通过原型模式复制命令对象,避免重复创建。
  4. 结合观察者模式(Observer):命令执行后通知相关对象。如命令执行成功后,通知日志系统记录日志、通知 UI 更新状态。

十一、模式应用

命令模式在各类系统中应用广泛,典型场景包括:

  1. GUI 框架:如 Swing/AWT 中的 Action 接口(本质是命令模式),按钮、菜单绑定 Action 对象,点击时触发 actionPerformed()(类似 execute())。
  2. 文本编辑器:“复制”“粘贴”“撤销”“重做” 等操作均封装为命令,通过命令历史记录实现撤销 / 重做。
  3. 遥控器 / 智能设备控制:遥控器(调用者)通过不同命令(如 “打开”“关闭”“调节音量”)控制电视、空调(接收者)。
  4. 任务调度系统:如 Quartz 任务调度框架,将定时任务封装为命令,调度器(调用者)按时间触发命令执行。
  5. 事务管理:如数据库事务,“提交”“回滚” 操作封装为命令,事务管理器(调用者)根据执行结果触发 “提交” 或 “回滚” 命令。

十二、Android 中的应用

在 Android 开发中,命令模式的应用场景非常普遍,以下是典型案例:

  1. Runnable 与线程池:
    • Runnable 接口本质是命令模式的简化版(无 undo() 方法),代表一个 “可执行的命令”。
    • 线程池(ThreadPoolExecutor)是调用者,负责触发 Runnable.run()(类似 execute());具体业务逻辑(如网络请求、数据处理)是接收者。
  2. View.OnClickListener:
    • 虽然 OnClickListener 是回调接口,但逻辑上符合命令模式:View(如 Button)是调用者,onClick(View v) 是命令执行方法,Activity/Fragment 中的具体业务逻辑是接收者。
    • 扩展:若需支持 “点击撤销”(如按钮点击后触发弹窗,撤销时关闭弹窗),可将 onClick 逻辑封装为完整的命令类(含 execute() 和 undo())。
  3. WorkManager 任务调度:
    • WorkRequest(如 OneTimeWorkRequest)是具体命令,封装了后台任务(如数据同步、文件清理);WorkManager 是调用者,负责触发任务执行;后台任务的具体逻辑是接收者。
  4. 撤销 / 重做场景:
    • 如 Android 绘图应用(如 SketchBook)、文本编辑应用(如 WPS),通过命令模式保存用户操作的历史记录,实现 “撤销画笔”“重做输入” 等功能。

十三、代码实现(Java 版)

以 “遥控器控制电视” 为例,实现命令模式的完整代码,包含 “打开电视”“关闭电视” 命令及撤销功能:

1. 命令接口(Command)

定义命令的统一方法(执行 + 撤销):

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

2. 接收者(TVReceiver)

负责执行具体业务逻辑(打开 / 关闭电视):

// 接收者:电视
public class TVReceiver {
    // 打开电视
    public void turnOn() {
        System.out.println("电视已打开,正在播放节目...");
    }

    // 关闭电视
    public void turnOff() {
        System.out.println("电视已关闭,进入待机模式...");
    }
}

3. 具体命令(ConcreteCommand)

实现命令接口,绑定接收者与具体操作:

// 具体命令1:打开电视
public class ConcreteCommandOpen implements Command {
    // 持有接收者引用
    private TVReceiver tvReceiver;

    // 构造器注入接收者
    public ConcreteCommandOpen(TVReceiver tvReceiver) {
        this.tvReceiver = tvReceiver;
    }

    @Override
    public void execute() {
        // 调用接收者的“打开”方法
        tvReceiver.turnOn();
    }

    @Override
    public void undo() {
        // 撤销:调用接收者的“关闭”方法(反向操作)
        tvReceiver.turnOff();
    }
}

// 具体命令2:关闭电视
public class ConcreteCommandClose implements Command {
    private TVReceiver tvReceiver;

    public ConcreteCommandClose(TVReceiver tvReceiver) {
        this.tvReceiver = tvReceiver;
    }

    @Override
    public void execute() {
        tvReceiver.turnOff();
    }

    @Override
    public void undo() {
        // 撤销:调用接收者的“打开”方法(反向操作)
        tvReceiver.turnOn();
    }
}

4. 调用者(RemoteControlInvoker)

持有命令对象,触发命令执行与撤销:

// 调用者:遥控器
public class RemoteControlInvoker {
    // 当前命令(可扩展为命令列表,支持多命令)
    private Command currentCommand;
    // 命令历史记录(用于多步撤销/重做)
    private Stack<Command> commandHistory = new Stack<>();

    // 设置当前命令
    public void setCommand(Command command) {
        this.currentCommand = command;
    }

    // 按下“执行”按钮:触发命令执行,并记录历史
    public void pressExecuteButton() {
        if (currentCommand != null) {
            currentCommand.execute();
            commandHistory.push(currentCommand); // 记录已执行的命令
        } else {
            System.out.println("未设置命令,请先绑定命令!");
        }
    }

    // 按下“撤销”按钮:触发上一次命令的撤销
    public void pressUndoButton() {
        if (!commandHistory.isEmpty()) {
            Command lastCommand = commandHistory.pop();
            lastCommand.undo();
        } else {
            System.out.println("无历史命令可撤销!");
        }
    }
}

5. 客户端(Client)

组装角色关系,测试命令模式:

// 客户端:测试命令模式
public class CommandPatternClient {
    public static void main(String[] args) {
        // 1. 创建接收者(电视)
        TVReceiver tv = new TVReceiver();

        // 2. 创建具体命令(打开、关闭),并绑定接收者
        Command openCommand = new ConcreteCommandOpen(tv);
        Command closeCommand = new ConcreteCommandClose(tv);

        // 3. 创建调用者(遥控器)
        RemoteControlInvoker remote = new RemoteControlInvoker();

        // 4. 测试“打开电视”命令
        System.out.println("=== 测试打开电视 ===");
        remote.setCommand(openCommand);
        remote.pressExecuteButton(); // 执行:打开电视

        // 5. 测试“撤销打开”
        System.out.println("\n=== 测试撤销打开 ===");
        remote.pressUndoButton(); // 撤销:关闭电视

        // 6. 测试“关闭电视”命令
        System.out.println("\n=== 测试关闭电视 ===");
        remote.setCommand(closeCommand);
        remote.pressExecuteButton(); // 执行:关闭电视

        // 7. 测试“撤销关闭”
        System.out.println("\n=== 测试撤销关闭 ===");
        remote.pressUndoButton(); // 撤销:打开电视
    }
}

6. 运行结果

=== 测试打开电视 ===
电视已打开,正在播放节目...

=== 测试撤销打开 ===
电视已关闭,进入待机模式...

=== 测试关闭电视 ===
电视已关闭,进入待机模式...

=== 测试撤销关闭 ===
电视已打开,正在播放节目...

十四、总结

命令模式通过 “将请求封装为对象”,实现了请求者与接收者的解耦,核心价值在于灵活管理请求(排队、日志、撤销)和易扩展新请求(符合开闭原则)。

  • 核心优势:解耦、可扩展、支持撤销 / 排队 / 日志,适用于复杂的请求管理场景。
  • 主要局限:类数量膨胀、简单场景下过度设计,需根据实际需求权衡使用。
  • Android 实践建议:在需支持撤销(如编辑功能)、任务调度(如后台任务)、解耦 UI 与业务逻辑(如按钮点击)的场景中,可优先考虑命令模式,或借鉴其 “请求对象化” 的思想(如用 Runnable 封装后台任务)。

命令模式是设计模式中 “封装思想” 的典型体现,理解它能帮助开发者更好地设计低耦合、高灵活的系统架构。

最近更新:: 2025/10/22 15:36
Contributors: 罗凯文, luokaiwen