设计模式(C++)详解——命令模式(1) - 指南

1. 背景与核心概念

1.1 起源与发展历程

命令模式最早由Gamma、Helm、Johnson和Vlissides在1994年的经典著作《设计模式:可复用面向对象软件的基础》中提出。该模式源于对GUI系统中菜单项和按钮操作的抽象需求,逐渐发展成为处理操作请求的通用解决方案。

发展时间线:

  • 1994年:GoF首次系统化描述命令模式
  • 2000年代:广泛应用于GUI框架、游戏开发、事务系统
  • 2010年代至今:在微服务架构、事件溯源系统中焕发新生

1.2 核心概念解析

命令模式的核心在于将"请求"封装为对象,其主要参与者包括:

Client
+createCommand()
Invoker
-command: Command
+setCommand()
+executeCommand()
«interface»
Command
+execute()
+undo()
ConcreteCommand
-receiver: Receiver
-state: State
+execute()
+undo()
Receiver
+action()

关键术语说明:

角色职责实例
Command(命令)声明执行操作的接口Action接口
ConcreteCommand(具体命令)将接收者绑定到动作CopyCommandPasteCommand
Client(客户端)创建具体命令对象应用程序代码
Invoker(调用者)要求命令执行请求按钮、菜单项
Receiver(接收者)知道如何实施操作文档、编辑器

2. 设计意图与考量

2.1 核心设计目标

解耦请求发送者与接收者

  • 发送者无需知道接收者的具体接口
  • 接收者变化不影响发送者代码
  • 支持请求的排队、日志记录、撤销等高级功能

设计权衡分析:

优势代价
✅ 降低系统耦合度❌ 增加类的数量
✅ 支持撤销/重做❌ 可能引入性能开销
✅ 易于扩展新命令❌ 设计复杂度提高
✅ 支持宏命令组合❌ 需要额外内存存储命令状态

2.2 架构设计考量

客户端
创建命令
设置接收者
绑定到调用者
执行命令
命令调用接收者
完成操作
存入历史
支持撤销

3. 实例与应用场景

3.1 案例1:文本编辑器的撤销/重做系统

场景描述:
现代文本编辑器需要支持复杂的编辑操作撤销功能,用户可能需要在多次编辑后回退到之前的状态。

实现代码:

#include <iostream>
  #include <vector>
    #include <stack>
      #include <string>
        #include <memory>
          /**
          * @brief 文档类 - 命令的接收者
          *
          * 负责实际执行文本操作,如插入、删除文本等。
          * 包含文档内容和光标位置状态。
          */
          class Document
          {
          private:
          std::string content_;
          size_t cursorPosition_;
          public:
          Document() : content_(""), cursorPosition_(0) {
          }
          /**
          * @brief 在光标位置插入文本
          *
          * 输入变量说明:
          * - text: 要插入的文本内容
          *
          * 输出变量说明:
          * - content_: 更新后的文档内容
          * - cursorPosition_: 移动后的光标位置
          */
          void insertText(const std::string& text) {
          content_.insert(cursorPosition_, text);
          cursorPosition_ += text.length();
          std::cout <<
          "插入文本: \"" << text <<
          "\",当前内容: \"" << content_ <<
          "\"" << std::endl;
          }
          /**
          * @brief 删除指定长度的文本
          *
          * 输入变量说明:
          * - length: 要删除的字符数
          *
          * 返回值说明:
          * 返回被删除的文本内容,用于撤销操作
          */
          std::string deleteText(size_t length) {
          if (cursorPosition_ < length) {
          length = cursorPosition_;
          }
          size_t startPos = cursorPosition_ - length;
          std::string deleted = content_.substr(startPos, length);
          content_.erase(startPos, length);
          cursorPosition_ = startPos;
          std::cout <<
          "删除文本: \"" << deleted <<
          "\",当前内容: \"" << content_ <<
          "\"" << std::endl;
          return deleted;
          }
          /**
          * @brief 移动光标位置
          *
          * 输入变量说明:
          * - position: 新的光标位置
          */
          void setCursorPosition(size_t position) {
          if (position > content_.length()) {
          position = content_.length();
          }
          cursorPosition_ = position;
          }
          std::string getContent() const {
          return content_;
          }
          size_t getCursorPosition() const {
          return cursorPosition_;
          }
          };
          /**
          * @brief 命令接口 - 声明执行和撤销操作
          *
          * 所有具体命令类的基类,定义命令的统一接口。
          */
          class Command
          {
          public:
          virtual ~Command() = default;
          /**
          * @brief 执行命令操作
          */
          virtual void execute() = 0;
          /**
          * @brief 撤销命令操作
          */
          virtual void undo() = 0;
          /**
          * @brief 获取命令描述(用于显示)
          */
          virtual std::string getDescription() const = 0;
          };
          /**
          * @brief 插入文本命令 - 具体命令实现
          *
          * 封装插入文本操作,保存操作状态以支持撤销。
          */
          class InsertTextCommand
          : public Command {
          private:
          Document& document_;
          std::string text_;
          size_t position_;
          public:
          /**
          * @brief 构造函数
          *
          * 输入变量说明:
          * - doc: 目标文档引用
          * - text: 要插入的文本
          */
          InsertTextCommand(Document& doc, const std::string& text)
          : document_(doc), text_(text), position_(doc.getCursorPosition()) {
          }
          void execute() override {
          document_.setCursorPosition(position_);
          document_.insertText(text_);
          }
          void undo() override {
          document_.setCursorPosition(position_);
          document_.deleteText(text_.length());
          document_.setCursorPosition(position_);
          // 恢复光标位置
          }
          std::string getDescription() const override {
          return "插入文本: \"" + text_ + "\"";
          }
          };
          /**
          * @brief 删除文本命令 - 具体命令实现
          *
          * 封装删除文本操作,保存被删除内容以支持撤销。
          */
          class DeleteTextCommand
          : public Command {
          private:
          Document& document_;
          size_t length_;
          std::string deletedText_;
          size_t position_;
          public:
          /**
          * @brief 构造函数
          *
          * 输入变量说明:
          * - doc: 目标文档引用
          * - length: 要删除的字符数
          */
          DeleteTextCommand(Document& doc, size_t length)
          : document_(doc), length_(length), position_(doc.getCursorPosition()) {
          }
          void execute() override {
          document_.setCursorPosition(position_);
          deletedText_ = document_.deleteText(length_);
          }
          void undo() override {
          document_.setCursorPosition(position_);
          document_.insertText(deletedText_);
          }
          std::string getDescription() const override {
          return "删除文本: \"" + deletedText_ + "\"";
          }
          };
          /**
          * @brief 命令历史管理器 - 支持撤销/重做功能
          *
          * 维护命令执行历史,提供撤销和重做操作接口。
          */
          class CommandHistory
          {
          private:
          std::stack<std::unique_ptr<Command>> undoStack_;
            std::stack<std::unique_ptr<Command>> redoStack_;
              public:
              /**
              * @brief 执行新命令并添加到历史记录
              *
              * 输入变量说明:
              * - command: 要执行的命令对象
              *
              * 输出变量说明:
              * - undoStack_: 命令被压入撤销栈
              * - redoStack_: 重做栈被清空
              */
              void executeCommand(std::unique_ptr<Command> command) {
                command->
                execute();
                undoStack_.push(std::move(command));
                // 执行新命令时清空重做栈
                while (!redoStack_.empty()) {
                redoStack_.pop();
                }
                std::cout <<
                "命令已执行" << std::endl;
                }
                /**
                * @brief 撤销最近执行的命令
                *
                * 返回值说明:
                * 成功撤销返回true,无命令可撤销返回false
                */
                bool undo() {
                if (undoStack_.empty()) {
                std::cout <<
                "无可撤销的命令" << std::endl;
                return false;
                }
                auto command = std::move(undoStack_.top());
                undoStack_.pop();
                command->
                undo();
                redoStack_.push(std::move(command));
                std::cout <<
                "命令已撤销" << std::endl;
                return true;
                }
                /**
                * @brief 重做最近撤销的命令
                *
                * 返回值说明:
                * 成功重做返回true,无命令可重做返回false
                */
                bool redo() {
                if (redoStack_.empty()) {
                std::cout <<
                "无可重做的命令" << std::endl;
                return false;
                }
                auto command = std::move(redoStack_.top());
                redoStack_.pop();
                command->
                execute();
                undoStack_.push(std::move(command));
                std::cout <<
                "命令已重做" << std::endl;
                return true;
                }
                /**
                * @brief 显示命令历史状态
                */
                void showHistory() const {
                std::cout <<
                "=== 命令历史 ===" << std::endl;
                std::cout <<
                "撤销栈大小: " << undoStack_.size() << std::endl;
                std::cout <<
                "重做栈大小: " << redoStack_.size() << std::endl;
                }
                };
                // 测试代码
                int main() {
                Document doc;
                CommandHistory history;
                std::cout <<
                "=== 文本编辑器命令模式演示 ===" << std::endl;
                // 执行一系列编辑操作
                history.executeCommand(std::make_unique<InsertTextCommand>(doc, "Hello"));
                  history.executeCommand(std::make_unique<InsertTextCommand>(doc, " World"));
                    history.executeCommand(std::make_unique<InsertTextCommand>(doc, "!"));
                      std::cout <<
                      "\n当前文档内容: \"" << doc.getContent() <<
                      "\"" << std::endl;
                      history.showHistory();
                      // 测试撤销功能
                      std::cout <<
                      "\n=== 测试撤销功能 ===" << std::endl;
                      history.undo();
                      std::cout <<
                      "撤销后内容: \"" << doc.getContent() <<
                      "\"" << std::endl;
                      history.undo();
                      std::cout <<
                      "再次撤销后内容: \"" << doc.getContent() <<
                      "\"" << std::endl;
                      // 测试重做功能
                      std::cout <<
                      "\n=== 测试重做功能 ===" << std::endl;
                      history.redo();
                      std::cout <<
                      "重做后内容: \"" << doc.getContent() <<
                      "\"" << std::endl;
                      history.showHistory();
                      return 0;
                      }

3.2 案例2:智能家居控制系统

场景描述:
智能家居系统需要统一控制多种设备(灯光、空调、窗帘等),支持情景模式(如"回家模式"、“影院模式”)和定时任务。

实现时序图:

用户遥控器(Invoker)命令对象灯光(Receiver)空调(Receiver)按下"回家模式"执行宏命令turnOn()灯光渐亮设置暖色调完成setTemperature(24)空调启动设置24度完成执行完成模式已激活用户遥控器(Invoker)命令对象灯光(Receiver)空调(Receiver)

4. 编译与运行说明

4.1 Makefile范例

# 编译器设置
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -O2
TARGET := command_pattern_demo
SOURCES := main.cpp
# 默认目标
$(TARGET): $(SOURCES)
$(CXX) $(CXXFLAGS) -o $(TARGET) $(SOURCES)
# 调试版本
debug: CXXFLAGS += -g -DDEBUG
debug: $(TARGET)
# 清理构建文件
clean:
rm -f $(TARGET) *.o
# 安装到系统目录(示例)
install: $(TARGET)
sudo cp $(TARGET) /usr/local/bin/
# 运行测试
test: $(TARGET)
./$(TARGET)
.PHONY: clean install test debug

4.2 编译与运行方法

编译步骤:

# 1. 使用make编译
make
# 2. 或者直接使用g++编译
g++ -std=c++17 -Wall -Wextra -O2 -o command_demo main.cpp
# 3. 编译调试版本
make debug

运行方式:

# 运行程序
./command_pattern_demo
# 运行并测试
make test

预期输出结果:

=== 文本编辑器命令模式演示 ===
插入文本: "Hello",当前内容: "Hello"
命令已执行
插入文本: " World",当前内容: "Hello World"
命令已执行
插入文本: "!",当前内容: "Hello World!"
命令已执行
当前文档内容: "Hello World!"
=== 命令历史 ===
撤销栈大小: 3
重做栈大小: 0
=== 测试撤销功能 ===
删除文本: "!",当前内容: "Hello World"
命令已撤销
撤销后内容: "Hello World"
删除文本: " World",当前内容: "Hello"
命令已撤销
再次撤销后内容: "Hello"
=== 测试重做功能 ===
插入文本: " World",当前内容: "Hello World"
命令已重做
重做后内容: "Hello World"
=== 命令历史 ===
撤销栈大小: 2
重做栈大小: 1

5. 高级应用与最佳实践

5.1 命令模式的变体与扩展

1. 事务性命令模式

class TransactionalCommand
: public Command {
private:
std::vector<std::unique_ptr<Command>> commands_;
  public:
  void addCommand(std::unique_ptr<Command> cmd) {
    commands_.push_back(std::move(cmd));
    }
    void execute() override {
    for (auto& cmd : commands_) {
    cmd->
    execute();
    }
    }
    void undo() override {
    for (auto it = commands_.rbegin(); it != commands_.rend();
    ++it) {
    (*it)->
    undo();
    }
    }
    };

2. 异步命令模式

class AsyncCommand
: public Command {
public:
virtual std::future<
void>
executeAsync() = 0;
};

5.2 性能优化策略

优化技术适用场景实现方式
命令对象池高频命令创建对象池模式复用命令实例
懒加载状态大状态命令只在撤销时加载必要状态
增量式撤销大数据量操作只存储变化差异

6. 现代框架中的应用

6.1 Qt框架中的命令模式

// QUndoCommand是Qt中命令模式的典型实现
class CustomCommand
: public QUndoCommand {
public:
CustomCommand(Editor* editor, const QString& oldText, const QString& newText)
: editor_(editor), oldText_(oldText), newText_(newText) {
}
void undo() override {
editor_->
setText(oldText_);
}
void redo() override {
editor_->
setText(newText_);
}
private:
Editor* editor_;
QString oldText_;
QString newText_;
};

总结

命令模式通过将操作请求封装为对象,实现了请求发送者与接收者的解耦,为复杂系统提供了强大的灵活性和可扩展性。该模式特别适合于需要支持撤销/重做、事务处理、宏命令等高级功能的场景。

核心价值体现:

  • 解耦设计:彻底分离调用者与实现者
  • 撤销重做:天然支持操作历史管理
  • 组合扩展:易于实现宏命令和事务
  • 日志审计:便于操作记录和追踪

命令模式在现代软件架构中仍然具有重要地位,特别是在GUI框架、游戏引擎、事务系统等领域的应用证明了其持久的设计价值。


注:本文详细解析了命令模式的各个方面,实际应用时请根据具体场景选择合适的实现方式。命令模式虽强大,但也要避免在不必要的场景中过度设计。

posted on 2025-10-22 17:38  slgkaifa  阅读(6)  评论(0)    收藏  举报

导航