1. 命令模式的核心思想与应用场景
命令模式(Command Pattern)是面向对象设计中最强大的行为型模式之一,它将请求封装为独立的对象,使你可以参数化客户端对象,将请求排队或记录请求日志,以及支持可撤销的操作。这种解耦方式在C++开发中尤为实用,特别是在需要实现操作队列、事务系统或宏命令的场景。
命令模式的核心在于将"做什么"(具体操作)与"谁来做"(调用者)分离。想象一下餐厅点餐的场景:顾客(Client)不需要知道厨师(Receiver)如何烹饪,只需通过服务员(Invoker)提交订单(Command)即可。这种间接性带来了极大的灵活性。
在C++中,命令模式通常包含以下角色:
- Command:声明执行操作的接口
- ConcreteCommand:将接收者对象绑定到动作
- Client:创建具体命令对象并设置接收者
- Invoker:要求命令执行请求
- Receiver:知道如何执行与请求相关的操作
提示:命令模式特别适合需要实现"撤销/重做"功能的场景,每个命令对象都可以保存状态以支持逆向操作。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. C++命令模式的标准实现
让我们通过一个完整的代码示例来理解命令模式在C++中的标准实现。假设我们要开发一个简单的文本编辑器,支持添加文本和撤销操作:
cpp复制#include <iostream>
#include <vector>
#include <memory>
#include <stack>
// 接收者类 - 知道如何执行操作
class TextEditor {
public:
void AddText(const std::string& text) {
content_ += text;
std::cout << "添加文本: " << text << "\n当前内容: " << content_ << std::endl;
}
void UndoAdd(size_t length) {
if (length <= content_.size()) {
content_.erase(content_.size() - length);
std::cout << "撤销添加\n当前内容: " << content_ << std::endl;
}
}
const std::string& GetContent() const { return content_; }
private:
std::string content_;
};
// 抽象命令接口
class Command {
public:
virtual ~Command() = default;
virtual void Execute() = 0;
virtual void Undo() = 0;
};
// 具体命令 - 添加文本
class AddTextCommand : public Command {
public:
AddTextCommand(TextEditor* editor, const std::string& text)
: editor_(editor), text_(text) {}
void Execute() override {
editor_->AddText(text_);
}
void Undo() override {
editor_->UndoAdd(text_.size());
}
private:
TextEditor* editor_;
std::string text_;
};
// 调用者 - 触发命令
class Invoker {
public:
void ExecuteCommand(std::unique_ptr<Command> cmd) {
cmd->Execute();
command_history_.push(std::move(cmd));
}
void Undo() {
if (!command_history_.empty()) {
command_history_.top()->Undo();
command_history_.pop();
}
}
private:
std::stack<std::unique_ptr<Command>> command_history_;
};
int main() {
TextEditor editor;
Invoker invoker;
// 执行命令
invoker.ExecuteCommand(std::make_unique<AddTextCommand>(&editor, "Hello, "));
invoker.ExecuteCommand(std::make_unique<AddTextCommand>(&editor, "Command Pattern!"));
// 撤销操作
invoker.Undo();
return 0;
}
这个实现展示了命令模式的几个关键优势:
- 解耦:调用者(Invoker)不知道接收者(TextEditor)的具体实现
- 可扩展:添加新命令只需创建新的ConcreteCommand类
- 可组合:可以轻松实现宏命令(组合多个命令)
- 可撤销:每个命令自带Undo操作支持
3. 命令模式的高级应用技巧
3.1 宏命令实现
宏命令是命令模式的强大扩展,它允许我们将多个命令组合成一个复合命令。这在实现批量操作或事务时特别有用:
cpp复制class MacroCommand : public Command {
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();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
// 使用示例
MacroCommand macro;
macro.AddCommand(std::make_unique<AddTextCommand>(&editor, "First "));
macro.AddCommand(std::make_unique<AddTextCommand>(&editor, "Second "));
macro.AddCommand(std::make_unique<AddTextCommand>(&editor, "Third"));
invoker.ExecuteCommand(std::make_unique<MacroCommand>(macro));
3.2 异步命令执行
命令模式天然支持异步操作,我们可以轻松实现命令队列:
cpp复制class AsyncInvoker {
public:
void QueueCommand(std::unique_ptr<Command> cmd) {
std::lock_guard<std::mutex> lock(queue_mutex_);
command_queue_.push(std::move(cmd));
queue_cv_.notify_one();
}
void StartProcessing() {
processor_thread_ = std::thread([this] {
while (true) {
std::unique_ptr<Command> cmd;
{
std::unique_lock<std::mutex> lock(queue_mutex_);
queue_cv_.wait(lock, [this] {
return !command_queue_.empty() || should_stop_;
});
if (should_stop_ && command_queue_.empty()) break;
cmd = std::move(command_queue_.front());
command_queue_.pop();
}
cmd->Execute();
}
});
}
void StopProcessing() {
{
std::lock_guard<std::mutex> lock(queue_mutex_);
should_stop_ = true;
}
queue_cv_.notify_all();
if (processor_thread_.joinable()) {
processor_thread_.join();
}
}
private:
std::queue<std::unique_ptr<Command>> command_queue_;
std::mutex queue_mutex_;
std::condition_variable queue_cv_;
std::thread processor_thread_;
bool should_stop_ = false;
};
3.3 命令模式与智能指针
在现代C++中,使用智能指针管理命令对象可以避免内存泄漏问题:
cpp复制class SmartCommandManager {
public:
template <typename T, typename... Args>
void CreateAndExecute(Args&&... args) {
static_assert(std::is_base_of_v<Command, T>,
"T must inherit from Command");
auto cmd = std::make_shared<T>(std::forward<Args>(args)...);
cmd->Execute();
active_commands_.push_back(cmd);
}
void UndoLast() {
if (!active_commands_.empty()) {
active_commands_.back()->Undo();
active_commands_.pop_back();
}
}
private:
std::vector<std::shared_ptr<Command>> active_commands_;
};
4. 命令模式在游戏开发中的实战应用
游戏开发是命令模式的典型应用场景。让我们以游戏输入处理为例,展示命令模式如何优雅地处理玩家输入:
cpp复制// 游戏角色(接收者)
class GameCharacter {
public:
void MoveForward(float distance) {
position_ += distance;
std::cout << "向前移动 " << distance << " 米\n";
}
void MoveBackward(float distance) {
position_ -= distance;
std::cout << "向后移动 " << distance << " 米\n";
}
void Jump(float height) {
std::cout << "跳跃 " << height << " 米\n";
}
float GetPosition() const { return position_; }
private:
float position_ = 0.0f;
};
// 游戏命令基类
class GameCommand {
public:
virtual ~GameCommand() = default;
virtual void Execute() = 0;
virtual void Undo() = 0;
};
// 具体游戏命令
class MoveCommand : public GameCommand {
public:
MoveCommand(GameCharacter* character, float distance)
: character_(character), distance_(distance) {}
void Execute() override {
character_->MoveForward(distance_);
}
void Undo() override {
character_->MoveBackward(distance_);
}
private:
GameCharacter* character_;
float distance_;
};
class JumpCommand : public GameCommand {
public:
JumpCommand(GameCharacter* character, float height)
: character_(character), height_(height) {}
void Execute() override {
character_->Jump(height_);
}
void Undo() override {
// 跳跃通常无法撤销,这里只是示例
std::cout << "撤销跳跃(无法真正撤销物理效果)\n";
}
private:
GameCharacter* character_;
float height_;
};
// 输入处理器
class InputHandler {
public:
GameCommand* HandleInput() {
if (IsKeyPressed(VK_UP)) {
return new MoveCommand(&character_, 1.0f);
}
if (IsKeyPressed(VK_SPACE)) {
return new JumpCommand(&character_, 2.0f);
}
return nullptr;
}
// 模拟按键检测
bool IsKeyPressed(int key) {
// 实际项目中会使用真正的输入系统
static std::map<int, bool> keyStates;
if (rand() % 5 == 0) { // 随机模拟按键
keyStates[key] = true;
return true;
}
return false;
}
private:
GameCharacter character_;
};
// 游戏循环示例
void GameLoop() {
InputHandler inputHandler;
std::vector<std::unique_ptr<GameCommand>> commands;
for (int i = 0; i < 10; ++i) {
if (auto cmd = inputHandler.HandleInput()) {
cmd->Execute();
commands.emplace_back(cmd);
}
// 模拟游戏帧
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
// 演示撤销
for (auto it = commands.rbegin(); it != commands.rend(); ++it) {
(*it)->Undo();
}
}
这种设计带来了几个显著优势:
- 输入重映射:可以轻松更改按键绑定,只需修改InputHandler
- 回放系统:记录命令序列即可实现游戏回放
- 网络同步:通过网络传输命令对象实现多人游戏同步
- AI控制:AI可以像玩家一样生成命令
5. 命令模式与C++现代特性的结合
现代C++提供了许多特性可以让命令模式实现得更优雅:
5.1 使用std::function实现轻量级命令
对于简单场景,我们可以用std::function替代完整的命令类层次:
cpp复制class FunctionCommand {
public:
using CommandFunc = std::function<void()>;
explicit FunctionCommand(CommandFunc execute, CommandFunc undo = {})
: execute_(std::move(execute)), undo_(std::move(undo)) {}
void Execute() { if (execute_) execute_(); }
void Undo() { if (undo_) undo_(); }
private:
CommandFunc execute_;
CommandFunc undo_;
};
// 使用示例
TextEditor editor;
FunctionCommand addHello(
[&] { editor.AddText("Hello"); },
[&] { editor.UndoAdd(5); }
);
addHello.Execute();
addHello.Undo();
5.2 使用lambda表达式创建即时命令
C++11的lambda表达式可以简化命令对象的创建:
cpp复制class LambdaInvoker {
public:
template <typename ExecuteFunc, typename UndoFunc = decltype([] {})>
void Execute(ExecuteFunc&& execute, UndoFunc&& undo = [] {}) {
command_history_.emplace(
std::make_unique<FunctionCommand>(
std::forward<ExecuteFunc>(execute),
std::forward<UndoFunc>(undo)
)
)->Execute();
}
void Undo() {
if (!command_history_.empty()) {
command_history_.top()->Undo();
command_history_.pop();
}
}
private:
std::stack<std::unique_ptr<FunctionCommand>> command_history_;
};
// 使用示例
LambdaInvoker invoker;
TextEditor editor;
invoker.Execute(
[&] { editor.AddText("Lambda"); },
[&] { editor.UndoAdd(6); }
);
invoker.Undo();
5.3 使用可变参数模板实现通用命令
结合可变参数模板,我们可以创建更通用的命令处理器:
cpp复制template <typename Receiver>
class GenericCommand {
public:
using ExecuteMethod = void (Receiver::*)();
using UndoMethod = void (Receiver::*)();
GenericCommand(Receiver* receiver, ExecuteMethod execute, UndoMethod undo)
: receiver_(receiver), execute_(execute), undo_(undo) {}
void Execute() { (receiver_->*execute_)(); }
void Undo() { (receiver_->*undo_)(); }
private:
Receiver* receiver_;
ExecuteMethod execute_;
UndoMethod undo_;
};
// 使用示例
TextEditor editor;
GenericCommand<TextEditor> genericCmd(
&editor,
&TextEditor::AddText,
&TextEditor::UndoAdd
);
6. 命令模式的性能考量与优化
虽然命令模式提供了优秀的设计解耦,但在性能敏感的场景中需要考虑一些优化策略:
6.1 命令对象池
频繁创建销毁命令对象可能导致内存分配成为瓶颈,使用对象池可以缓解这个问题:
cpp复制class CommandPool {
public:
template <typename T, typename... Args>
T* Acquire(Args&&... args) {
if constexpr (std::is_base_of_v<Command, T>) {
if (auto it = free_commands_.find(typeid(T)); it != free_commands_.end()) {
if (!it->second.empty()) {
auto cmd = static_cast<T*>(it->second.back());
it->second.pop_back();
new (cmd) T(std::forward<Args>(args)...); // 原地构造
return cmd;
}
}
return new T(std::forward<Args>(args)...);
}
return nullptr;
}
template <typename T>
void Release(T* cmd) {
if constexpr (std::is_base_of_v<Command, T>) {
cmd->~T(); // 显式析构
free_commands_[typeid(T)].push_back(cmd);
}
}
~CommandPool() {
for (auto& [type, commands] : free_commands_) {
for (auto cmd : commands) {
delete cmd;
}
}
}
private:
std::unordered_map<std::type_index, std::vector<Command*>> free_commands_;
};
6.2 内存布局优化
对于大量小型命令,可以考虑使用连续内存存储:
cpp复制class CommandBuffer {
public:
template <typename T, typename... Args>
T* EmplaceCommand(Args&&... args) {
static_assert(std::is_base_of_v<Command, T>, "Must be a Command type");
static_assert(std::is_trivially_destructible_v<T>,
"Command must be trivially destructible");
const size_t size = sizeof(T);
const size_t align = alignof(T);
// 对齐处理
buffer_offset_ = (buffer_offset_ + align - 1) & ~(align - 1);
if (buffer_offset_ + size > buffer_.size()) {
buffer_.resize(buffer_.size() * 2);
}
T* cmd = new (&buffer_[buffer_offset_]) T(std::forward<Args>(args)...);
buffer_offset_ += size;
return cmd;
}
void Reset() {
buffer_offset_ = 0;
}
private:
std::vector<uint8_t> buffer_{1024};
size_t buffer_offset_ = 0;
};
6.3 命令合并
对于高频小命令,可以合并执行以减少开销:
cpp复制class CompositeMoveCommand : public Command {
public:
void AddMove(GameCharacter* character, float distance) {
moves_.emplace_back(character, distance);
}
void Execute() override {
for (auto& [character, distance] : moves_) {
character->MoveForward(distance);
}
}
void Undo() override {
for (auto it = moves_.rbegin(); it != moves_.rend(); ++it) {
it->character->MoveBackward(it->distance);
}
}
private:
std::vector<std::pair<GameCharacter*, float>> moves_;
};
7. 命令模式与其他设计模式的协同
命令模式常与其他设计模式结合使用,产生更强大的设计效果:
7.1 命令模式 + 组合模式 = 宏命令
如前所示,组合模式可以让我们将多个命令组合成一个复合命令:
cpp复制class CompositeCommand : public Command {
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();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
7.2 命令模式 + 备忘录模式 = 完善的撤销系统
备忘录模式可以增强命令模式的撤销能力,特别是对于需要保存复杂状态的场景:
cpp复制// 备忘录类
class EditorMemento {
public:
explicit EditorMemento(std::string content) : content_(std::move(content)) {}
const std::string& GetContent() const { return content_; }
private:
std::string content_;
};
// 增强的文本编辑器
class AdvancedTextEditor {
public:
void AddText(const std::string& text) {
content_ += text;
}
std::unique_ptr<EditorMemento> CreateMemento() const {
return std::make_unique<EditorMemento>(content_);
}
void RestoreFromMemento(const EditorMemento* memento) {
content_ = memento->GetContent();
}
const std::string& GetContent() const { return content_; }
private:
std::string content_;
};
// 使用备忘录的命令
class AddTextCommandWithMemento : public Command {
public:
AddTextCommandWithMemento(AdvancedTextEditor* editor, const std::string& text)
: editor_(editor), text_(text) {}
void Execute() override {
memento_before_ = editor_->CreateMemento();
editor_->AddText(text_);
}
void Undo() override {
if (memento_before_) {
editor_->RestoreFromMemento(memento_before_.get());
}
}
private:
AdvancedTextEditor* editor_;
std::string text_;
std::unique_ptr<EditorMemento> memento_before_;
};
7.3 命令模式 + 原型模式 = 可克隆命令
当需要复制命令时,可以结合原型模式:
cpp复制class CloneableCommand : public Command {
public:
virtual std::unique_ptr<CloneableCommand> Clone() const = 0;
};
class ConcreteCloneableCommand : public CloneableCommand {
public:
explicit ConcreteCloneableCommand(int value) : value_(value) {}
void Execute() override {
std::cout << "执行命令,值: " << value_ << std::endl;
}
void Undo() override {
std::cout << "撤销命令,值: " << value_ << std::endl;
}
std::unique_ptr<CloneableCommand> Clone() const override {
return std::make_unique<ConcreteCloneableCommand>(*this);
}
private:
int value_;
};
8. 命令模式在实际项目中的最佳实践
根据多年C++项目经验,以下是命令模式在实际开发中的最佳实践:
-
保持命令轻量:命令对象应该只包含执行操作所需的最小状态,大数据应该存储在接收者中
-
区分瞬时命令和持久命令:
- 瞬时命令:执行后立即丢弃(如游戏输入)
- 持久命令:需要保留以支持撤销(如编辑器操作)
-
考虑命令的序列化:
- 如果需要保存命令历史或网络传输,设计可序列化的命令接口
- 使用类似这样的设计:
cpp复制class SerializableCommand : public Command { public: virtual std::string Serialize() const = 0; static std::unique_ptr<SerializableCommand> Deserialize(const std::string& data); };
-
命令命名规范:
- 使用动词+名词的命名方式(如AddTextCommand、DeleteItemCommand)
- 对于有相反操作的命令,使用对称命名(如Add/Remove、Insert/Delete)
-
错误处理策略:
- 命令执行应该是原子的 - 要么完全成功,要么完全失败
- 考虑添加TryExecute()方法,返回操作状态
-
性能敏感场景的优化:
- 使用自定义内存分配器
- 考虑命令的批量处理
- 避免在命令构造函数中进行昂贵操作
-
测试策略:
- 为每个命令类编写单元测试
- 特别注意测试撤销操作的正确性
- 测试命令的组合使用
注意:在大型项目中,命令模式可能导致类数量激增。这时可以考虑使用代码生成工具来自动生成简单的命令类,或者使用模板技术减少重复代码。
