设计模式(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:文本编辑器的撤销/重做系统

场景描述:

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

实现代码:

cpp 复制代码
#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范例

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 编译与运行方法

编译步骤:

bash 复制代码
# 1. 使用make编译
make

# 2. 或者直接使用g++编译
g++ -std=c++17 -Wall -Wextra -O2 -o command_demo main.cpp

# 3. 编译调试版本
make debug

运行方式:

bash 复制代码
# 运行程序
./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. 事务性命令模式

cpp 复制代码
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. 异步命令模式

cpp 复制代码
class AsyncCommand : public Command {
public:
    virtual std::future<void> executeAsync() = 0;
};

5.2 性能优化策略

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

6. 现代框架中的应用

6.1 Qt框架中的命令模式

cpp 复制代码
// 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框架、游戏引擎、事务系统等领域的应用证明了其持久的设计价值。


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

相关推荐
phdsky2 小时前
【设计模式】命令模式
设计模式·命令模式
青草地溪水旁2 小时前
设计模式(C++)详解——命令模式(2)
c++·设计模式·命令模式
new_daimond2 小时前
设计模式-原型模式详解
设计模式·原型模式
敲上瘾2 小时前
HTTP协议工作原理与生产环境服务器搭建实战
服务器·网络·c++·网络协议·http
Zfox_2 小时前
【C++项目】微服务即时通讯系统:服务端
数据库·c++·微服务·中间件·rpc·架构·即时通讯
清朝牢弟3 小时前
基于Win系统下PCL库入门到实践:IO模块之PCD文件的读写(附详细代码)
c++·pcl·pcd
爱和冰阔落3 小时前
【C++STL详解】带头双向循环结构 + 双向迭代器,核心接口 + 排序效率 + 避坑指南
开发语言·c++·经验分享
星星点点洲3 小时前
【Golang】数据设计模式
开发语言·设计模式·golang
步行cgn3 小时前
责任链设计模式详解
设计模式