行为型设计模式的一种,它利用就是命令模式(Command)将请求封装为对象,使请求的发送者与接收者解耦,同时帮助请求的参数化、队列化、日志记录和撤销操作。此种模式将"做什么"与"谁去做"分离,提高了系统的灵活性和可扩展性。

一、核心思想与角色

命令模式的核心是“封装请求为对象”,通过命令对象介导发送者与接收者的交互。其核心角色如下:

角色名称核心职责
抽象命令(Command)定义命令的接口,声明执行命令的方法(如execute())和撤销方法(如undo())。
具体命令(ConcreteCommand)实现抽象命令接口,持有接收者对象,在execute()中调用接收者的具体方法,完成请求。
接收者(Receiver)执行命令的实际对象,包含具体的业务逻辑(如开灯、关灯的具体完成)。
调用者(Invoker)持有命令对象,负责调用命令的execute()方法,不关心命令如何执行。
客户端(Client)创建具体命令对象,指定其接收者,并将命令对象交给调用者执行。

核心思想:将请求封装为命令对象,使发送者(调用者)无需知道接收者的具体信息,只需经过命令对象即可触发请求;同时,命令对象可被存储、传递和复用,承受麻烦的操作管理(如撤销、重试)。

二、实现示例(智能家电控制系统)

假设大家需要设计一个智能遥控器(调用者),可控制多种家电(接收者,如灯光、电视)的开关操作,并支持撤销特性。运用命令模式可灵活扩展设备类型和操作:

#include <iostream>
  #include <string>
    #include <vector>
      // 2. 接收者1:灯光
      class Light {
      private:
      std::string location; // 位置(如客厅、卧室)
      public:
      Light(const std::string& loc) : location(loc) {}
      // 具体业务逻辑:开灯
      void on() {
      std::cout << location << "灯光已打开" << std::endl;
      }
      // 具体业务逻辑:关灯
      void off() {
      std::cout << location << "灯光已关闭" << std::endl;
      }
      };
      // 2. 接收者2:电视
      class TV {
      private:
      std::string location;
      int channel; // 频道
      public:
      TV(const std::string& loc) : location(loc), channel(0) {}
      void on() {
      std::cout << location << "电视已打开" << std::endl;
      }
      void off() {
      std::cout << location << "电视已关闭" << std::endl;
      }
      void setChannel(int ch) {
      channel = ch;
      std::cout << location << "电视已切换到频道" << channel << std::endl;
      }
      int getChannel() const { return channel; }
      };
      // 1. 抽象命令
      class Command {
      public:
      virtual void execute() = 0;   // 执行命令
      virtual void undo() = 0;      // 撤销命令
      virtual ~Command() = default;
      };
      // 3. 具体命令1:开灯命令
      class LightOnCommand : public Command {
      private:
      Light* light; // 持有接收者(灯光)
      public:
      LightOnCommand(Light* l) : light(l) {}
      // 执行:调用接收者的on()
      void execute() override {
      light->on();
      }
      // 撤销:调用接收者的off()(与execute相反)
      void undo() override {
      light->off();
      }
      };
      // 3. 具体命令2:关灯命令
      class LightOffCommand : public Command {
      private:
      Light* light;
      public:
      LightOffCommand(Light* l) : light(l) {}
      void execute() override {
      light->off();
      }
      void undo() override {
      light->on();
      }
      };
      // 3. 具体命令3:开电视并切换频道命令
      class TVOnWithChannelCommand : public Command {
      private:
      TV* tv;
      int prevChannel; // 记录之前的频道(用于撤销)
      public:
      TVOnWithChannelCommand(TV* t, int channel) : tv(t), prevChannel(0) {
      this->prevChannel = tv->getChannel(); // 保存当前频道
      }
      void execute() override {
      prevChannel = tv->getChannel(); // 执行前再次保存当前频道
      tv->on();
      tv->setChannel(5); // 切换到5频道
      }
      void undo() override {
      tv->setChannel(prevChannel); // 恢复到之前的频道
      tv->off(); // 关闭电视
      }
      };
      // 3. 空命令(用于初始化遥控器按钮,避免空指针)
      class NoCommand : public Command {
      public:
      void execute() override {} // 什么也不做
      void undo() override {}    // 什么也不做
      };
      // 4. 调用者:智能遥控器
      class RemoteControl {
      private:
      std::vector<Command*> onCommands;  // 存储"开"命令
        std::vector<Command*> offCommands; // 存储"关"命令
          Command* lastCommand; // 记录最后执行的命令(用于撤销)
          public:
          // 构造函数:初始化按钮为无命令
          RemoteControl(int buttonCount) {
          Command* noCommand = new NoCommand();
          for (int i = 0; i < buttonCount; ++i) {
          onCommands.push_back(noCommand);
          offCommands.push_back(noCommand);
          }
          lastCommand = noCommand;
          }
          // 设置按钮对应的命令
          void setCommand(int slot, Command* onCmd, Command* offCmd) {
          if (slot >= 0 && slot < onCommands.size()) {
          onCommands[slot] = onCmd;
          offCommands[slot] = offCmd;
          }
          }
          // 按下"开"按钮
          void pressOnButton(int slot) {
          if (slot >= 0 && slot < onCommands.size()) {
          onCommands[slot]->execute();
          lastCommand = onCommands[slot]; // 记录最后执行的命令
          }
          }
          // 按下"关"按钮
          void pressOffButton(int slot) {
          if (slot >= 0 && slot < offCommands.size()) {
          offCommands[slot]->execute();
          lastCommand = offCommands[slot]; // 记录最后执行的命令
          }
          }
          // 按下撤销按钮
          void pressUndoButton() {
          std::cout << "撤销操作:";
          lastCommand->undo();
          }
          // 析构函数:释放命令对象
          ~RemoteControl() {
          // 释放所有命令(注意:NoCommand可能被多个按钮共享,这里简化处理)
          for (auto cmd : onCommands) {
          delete cmd;
          }
          // offCommands中的命令可能与onCommands重复,避免重复释放
          lastCommand = nullptr;
          }
          };
          // 客户端代码:配置遥控器并使用
          int main() {
          // 创建接收者
          Light* livingRoomLight = new Light("客厅");
          TV* livingRoomTV = new TV("客厅");
          // 创建具体命令(绑定接收者)
          Command* lightOn = new LightOnCommand(livingRoomLight);
          Command* lightOff = new LightOffCommand(livingRoomLight);
          Command* tvOn = new TVOnWithChannelCommand(livingRoomTV, 5);
          // 创建调用者(遥控器,2个按钮)
          RemoteControl* remote = new RemoteControl(2);
          // 配置按钮:按钮0控制灯光,按钮1控制电视
          remote->setCommand(0, lightOn, lightOff);
          remote->setCommand(1, tvOn, new LightOffCommand(livingRoomLight)); // 简化:电视关闭复用灯光关闭命令
          // 操作遥控器
          std::cout << "=== 按下客厅灯光开按钮 ===" << std::endl;
          remote->pressOnButton(0);
          std::cout << "\n=== 按下客厅电视开按钮 ===" << std::endl;
          remote->pressOnButton(1);
          std::cout << "\n=== 按下撤销按钮 ===" << std::endl;
          remote->pressUndoButton();
          std::cout << "\n=== 按下客厅灯光关按钮 ===" << std::endl;
          remote->pressOffButton(0);
          std::cout << "\n=== 按下撤销按钮 ===" << std::endl;
          remote->pressUndoButton();
          // 释放资源
          delete remote;
          delete livingRoomTV;
          delete livingRoomLight;
          return 0;
          }

三、代码解析

  1. 接收者(Receiver)

    • LightTV是具体的家电,包含实际的业务逻辑(on()off()等),它们不知道命令的存在,只负责执行具体操作。
  2. 抽象命令(Command)
    定义了execute()(执行)和undo()(撤销)接口,所有具体命令都需实现这两个方法。

  3. 具体命令(ConcreteCommand)

    • 每个命令绑定一个接收者(如LightOnCommand绑定Light),在execute()中调用接收者的对应方法(如light->on())。
    • undo()方法实现与execute()相反的操作(如开灯的撤销是关灯),对于复杂命令(如TVOnWithChannelCommand),需要记录执行前的状态(如之前的频道)用于恢复。
  4. 调用者(Invoker)
    RemoteControl(遥控器)持有多个命令对象,提供按钮操作接口(pressOnButton()pressUndoButton()),通过调用命令的execute()undo()完成操作,无需知道具体的接收者和执行细节。

  5. 空命令(NoCommand)
    作为默认命令初始化遥控器按钮,避免空指针异常,体现了“null对象模式”的思想。

  6. 客户端使用
    客户端负责创建接收者、命令和调用者,将命令绑定到调用者的按钮上,最终凭借调用者触发命令执行。

四、核心优势与适用场景

优势
  1. 解耦发送者与接收者:调用者无需知道接收者的具体类型和操控细节,只需通过命令对象交互。
  2. 支持命令队列和日志:命令对象可被存储在队列中批量执行,或记录到日志中实现故障恢复(如数据库事务日志)。
  3. 支撑撤销/重做:通过undo()redo()方法,可实现操作的撤销和重复执行(需记录命令历史)。
  4. 易于扩展:新增命令只需实现Command接口,无需修改现有调用者和接收者(符合开闭原则)。
  5. 参数化操作:可通过命令对象传递参数(如TVOnWithChannelCommand中的频道),使操作更灵活。
适用场景
  1. 需要抽象出操作并参数化:如GUI中的菜单操作、遥控器按钮、数据库事务。
  2. 得支持撤销/重做:如文本编辑器的撤销功能、绘图软件的执行回退。
  3. 需要将操作队列化或日志化:如任务调度系统、命令批处理、操作日志记录。
  4. 应该解耦请求发送者和接收者:如分布式系统中的命令分发、中间件的事件处理。

五、与其他模式的区别

模式核心差异点
命令模式将请求封装为对象,支持队列、日志、撤销,强调“请求的封装与管理”。
职责链模式请求沿链传递,由第一个能处理的对象处理,强调“请求的分发与传递”。
策略模式封装算法家族,使算法可动态替换,强调“算法的选择与切换”。
观察者模式一个对象改变时通知多个观察者,强调“一对多的依赖关系与事件通知”。

六、实践建议

  1. 设计完善的撤销机制:对于需要撤销的命令,确保undo()方法能准确恢复到执行前的状态,必要时在execute()中记录历史状态。
  2. 采用空命令避免空指针:如示例中的NoCommand,简化调用者的空判断逻辑。
  3. 合理管理命令生命周期:命令对象可能被长期存储(如日志),需注意内存管理,避免资源泄漏。
  4. 结合备忘录模式:对于复杂状态的撤销,可在命令中使用备忘录模式存储对象状态,简化undo()实现。

命令模式的核心价值在于“将请求标准化、对象化”,通过命令对象建立了请求发送与执行的解耦,同时为艰难操作管理(如队列、日志、撤销)提供了灵活的解决方案。在需要对运行进行抽象、扩展和精细化管理的场景中,命令模式是一种非常有效的设计选择。

posted on 2025-10-20 14:53  ycfenxi  阅读(1)  评论(0)    收藏  举报