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(具体命令) | 将接收者绑定到动作 | CopyCommand 、PasteCommand |
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框架、游戏引擎、事务系统等领域的应用证明了其持久的设计价值。
注:本文详细解析了命令模式的各个方面,实际应用时请根据具体场景选择合适的实现方式。命令模式虽强大,但也要避免在不必要的场景中过度设计。