1. 命令模式基础回顾
在深入探讨C++中的命令模式变体之前,我们需要先理解经典命令模式的核心思想。命令模式是一种行为设计模式,它将请求封装为对象,从而使你可以参数化客户端对象,将请求排队或记录请求日志,以及支持可撤销的操作。
命令模式通常包含以下几个关键角色:
- Command(命令接口):声明执行操作的接口
- ConcreteCommand(具体命令):将一个接收者对象绑定于一个动作,调用接收者相应的操作
- Client(客户端):创建具体命令对象并设置其接收者
- Invoker(调用者):要求命令执行请求
- Receiver(接收者):知道如何实施与执行一个请求相关的操作
在C++中,一个典型的命令模式实现可能如下所示:
cpp复制// 命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
// 接收者类
class Receiver {
public:
void action() {
std::cout << "Receiver performing action" << std::endl;
}
};
// 具体命令
class ConcreteCommand : public Command {
public:
ConcreteCommand(Receiver* receiver) : receiver_(receiver) {}
void execute() override {
receiver_->action();
}
private:
Receiver* receiver_;
};
// 调用者
class Invoker {
public:
void setCommand(Command* command) {
command_ = command;
}
void executeCommand() {
if (command_) {
command_->execute();
}
}
private:
Command* command_ = nullptr;
};
这种基础实现虽然简单,但在实际工程中往往需要根据具体场景进行各种变体和优化。接下来我们将探讨几种常见的C++命令模式变体及其适用场景。
2. 带撤销功能的命令模式
在实际应用中,命令模式最强大的特性之一就是支持撤销操作。要实现撤销功能,我们需要扩展基本的命令接口:
cpp复制class UndoableCommand : public Command {
public:
virtual void undo() = 0;
virtual bool canUndo() const = 0;
};
2.1 简单状态撤销实现
对于可以简单通过状态恢复的命令,我们可以这样实现:
cpp复制class ToggleLightCommand : public UndoableCommand {
public:
explicit ToggleLightCommand(bool& lightState)
: lightState_(lightState), previousState_(lightState) {}
void execute() override {
previousState_ = lightState_;
lightState_ = !lightState_;
std::cout << "Light is now " << (lightState_ ? "ON" : "OFF") << std::endl;
}
void undo() override {
lightState_ = previousState_;
std::cout << "Undo: Light reverted to " << (lightState_ ? "ON" : "OFF") << std::endl;
}
bool canUndo() const override { return true; }
private:
bool& lightState_;
bool previousState_;
};
2.2 复杂操作的撤销实现
对于更复杂的操作,可能需要采用备忘录模式(Memento)来保存状态:
cpp复制class Document {
public:
void write(const std::string& text) {
content_ += text;
}
std::string getContent() const { return content_; }
class Memento {
public:
explicit Memento(const Document& doc) : savedState_(doc.content_) {}
private:
std::string savedState_;
friend class Document;
};
Memento createMemento() const {
return Memento(*this);
}
void restoreFromMemento(const Memento& memento) {
content_ = memento.savedState_;
}
private:
std::string content_;
};
class DocumentCommand : public UndoableCommand {
public:
explicit DocumentCommand(Document& doc, const std::string& text)
: document_(doc), text_(text) {}
void execute() override {
memento_ = document_.createMemento();
document_.write(text_);
}
void undo() override {
document_.restoreFromMemento(memento_);
}
bool canUndo() const override { return true; }
private:
Document& document_;
std::string text_;
Document::Memento memento_;
};
提示:在设计撤销功能时,需要考虑内存消耗问题。对于频繁执行的命令,可能需要实现增量式状态保存或设置撤销栈深度限制。
3. 组合命令模式(宏命令)
组合命令(也称为宏命令)允许将多个命令组合成一个命令,这在实现批处理操作时非常有用。
3.1 基本组合命令实现
cpp复制class CompositeCommand : public Command {
public:
void addCommand(std::unique_ptr<Command> command) {
commands_.push_back(std::move(command));
}
void execute() override {
for (auto& cmd : commands_) {
cmd->execute();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
3.2 支持撤销的组合命令
cpp复制class UndoableCompositeCommand : public UndoableCommand {
public:
void addCommand(std::unique_ptr<UndoableCommand> command) {
commands_.push_back(std::move(command));
}
void execute() override {
for (auto& cmd : commands_) {
cmd->execute();
}
}
void undo() override {
// 注意要反向执行undo
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
if ((*it)->canUndo()) {
(*it)->undo();
}
}
}
bool canUndo() const override {
return std::all_of(commands_.begin(), commands_.end(),
[](const auto& cmd) { return cmd->canUndo(); });
}
private:
std::vector<std::unique_ptr<UndoableCommand>> commands_;
};
在实际应用中,组合命令可以用于实现复杂的用户操作序列,如图形编辑器中的"组合/取消组合"功能,或者游戏中的"宏"技能系统。
4. 异步命令模式
在现代C++应用中,我们经常需要处理异步操作。传统的命令模式可以扩展为支持异步执行。
4.1 基于future/promise的实现
cpp复制class AsyncCommand : public Command {
public:
virtual std::future<void> executeAsync() = 0;
void execute() override {
executeAsync().wait();
}
};
class ConcreteAsyncCommand : public AsyncCommand {
public:
std::future<void> executeAsync() override {
std::promise<void> promise;
auto future = promise.get_future();
std::thread([this, promise = std::move(promise)]() mutable {
try {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Async operation completed" << std::endl;
promise.set_value();
} catch (...) {
promise.set_exception(std::current_exception());
}
}).detach();
return future;
}
};
4.2 基于回调的异步命令
cpp复制class CallbackCommand : public Command {
public:
using Callback = std::function<void(bool)>;
virtual void executeWithCallback(Callback cb) = 0;
void execute() override {
executeWithCallback([](bool){});
}
};
class NetworkRequestCommand : public CallbackCommand {
public:
void executeWithCallback(Callback cb) override {
std::thread([this, cb = std::move(cb)]() {
// 模拟网络请求
std::this_thread::sleep_for(std::chrono::milliseconds(500));
bool success = (rand() % 2) == 0;
if (success) {
std::cout << "Request succeeded" << std::endl;
} else {
std::cout << "Request failed" << std::endl;
}
cb(success);
}).detach();
}
};
异步命令模式在GUI应用、网络服务和游戏开发中特别有用,可以避免阻塞主线程,提高应用的响应性。
5. 基于模板的命令模式
C++的模板特性允许我们创建更灵活的命令实现。下面介绍几种基于模板的命令模式变体。
5.1 通用命令包装器
cpp复制template <typename Receiver, typename Action>
class GenericCommand : public Command {
public:
GenericCommand(Receiver* receiver, Action action)
: receiver_(receiver), action_(action) {}
void execute() override {
action_(*receiver_);
}
private:
Receiver* receiver_;
Action action_;
};
// 使用示例
class Light {
public:
void turnOn() { std::cout << "Light on" << std::endl; }
void turnOff() { std::cout << "Light off" << std::endl; }
};
Light light;
auto onCmd = GenericCommand(&light, &Light::turnOn);
auto offCmd = GenericCommand(&light, &Light::turnOff);
5.2 带参数的模板命令
cpp复制template <typename Receiver, typename Action, typename... Args>
class ParametricCommand : public Command {
public:
ParametricCommand(Receiver* receiver, Action action, Args... args)
: receiver_(receiver), action_(action), args_(std::forward<Args>(args)...) {}
void execute() override {
std::apply([this](auto&&... args) {
(receiver_->*action_)(std::forward<decltype(args)>(args)...);
}, args_);
}
private:
Receiver* receiver_;
Action action_;
std::tuple<Args...> args_;
};
// 使用示例
class Calculator {
public:
void add(int a, int b) {
std::cout << a << " + " << b << " = " << (a + b) << std::endl;
}
};
Calculator calc;
auto addCmd = ParametricCommand(&calc, &Calculator::add, 5, 3);
模板化的命令实现可以减少代码重复,提高类型安全性,但可能会增加编译时间和代码体积,需要根据项目需求权衡使用。
6. 命令模式与C++现代特性的结合
现代C++提供了许多新特性,可以与命令模式结合使用,创建更强大、更安全的实现。
6.1 使用智能指针管理命令生命周期
cpp复制class CommandManager {
public:
template <typename Cmd, typename... Args>
void createAndExecute(Args&&... args) {
auto cmd = std::make_unique<Cmd>(std::forward<Args>(args)...);
cmd->execute();
history_.push_back(std::move(cmd));
}
void undoLast() {
if (!history_.empty() && history_.back()->canUndo()) {
history_.back()->undo();
history_.pop_back();
}
}
private:
std::vector<std::unique_ptr<UndoableCommand>> history_;
};
6.2 使用lambda表达式创建即时命令
cpp复制class LambdaCommand : public Command {
public:
template <typename F>
explicit LambdaCommand(F&& f) : action_(std::forward<F>(f)) {}
void execute() override { action_(); }
private:
std::function<void()> action_;
};
// 使用示例
auto lambdaCmd = LambdaCommand([]() {
std::cout << "Executing lambda command" << std::endl;
});
6.3 使用移动语义优化命令参数传递
cpp复制class StringProcessingCommand : public Command {
public:
explicit StringProcessingCommand(std::string data)
: data_(std::move(data)) {}
void execute() override {
std::cout << "Processing: " << data_ << std::endl;
// 处理数据...
}
private:
std::string data_;
};
// 使用示例
std::string largeData = "Very large string data...";
auto cmd = StringProcessingCommand(std::move(largeData));
// largeData现在已被移动,不应再使用
这些现代C++特性可以使命令模式的实现更加简洁、高效和安全,特别是在资源管理和性能关键的场景中。
7. 命令模式在游戏开发中的应用实例
游戏开发是命令模式应用最广泛的领域之一。下面我们通过一个简单的游戏示例来展示命令模式的实际应用。
7.1 游戏角色控制
cpp复制class GameCharacter {
public:
void move(int dx, int dy) {
x_ += dx;
y_ += dy;
std::cout << "Moved to (" << x_ << ", " << y_ << ")" << std::endl;
}
void jump() {
std::cout << "Character jumped!" << std::endl;
}
void attack() {
std::cout << "Character attacked!" << std::endl;
}
private:
int x_ = 0;
int y_ = 0;
};
class CharacterCommand : public UndoableCommand {
public:
virtual ~CharacterCommand() = default;
protected:
static inline GameCharacter* character_ = nullptr;
struct CharacterState {
int x, y;
};
CharacterState saveState() const {
return {0, 0}; // 简化示例,实际需要保存相关状态
}
void restoreState(const CharacterState& state) {
// 恢复状态实现
}
};
class MoveCommand : public CharacterCommand {
public:
MoveCommand(int dx, int dy) : dx_(dx), dy_(dy) {}
void execute() override {
savedState_ = saveState();
character_->move(dx_, dy_);
}
void undo() override {
restoreState(savedState_);
}
bool canUndo() const override { return true; }
private:
int dx_, dy_;
CharacterState savedState_;
};
// 初始化角色
GameCharacter player;
CharacterCommand::character_ = &player;
7.2 游戏命令队列和重放系统
cpp复制class CommandQueue {
public:
void addCommand(std::unique_ptr<Command> cmd) {
queue_.push(std::move(cmd));
}
void processQueue() {
while (!queue_.empty()) {
auto cmd = std::move(queue_.front());
queue_.pop();
cmd->execute();
if (auto undoable = dynamic_cast<UndoableCommand*>(cmd.get())) {
history_.push_back(std::move(cmd));
}
}
}
void undoLast() {
if (!history_.empty()) {
history_.back()->undo();
history_.pop_back();
}
}
private:
std::queue<std::unique_ptr<Command>> queue_;
std::vector<std::unique_ptr<UndoableCommand>> history_;
};
// 使用示例
CommandQueue queue;
queue.addCommand(std::make_unique<MoveCommand>(1, 0));
queue.addCommand(std::make_unique<MoveCommand>(0, 1));
queue.processQueue();
queue.undoLast();
这种实现可以用于游戏中的回放系统、AI行为控制,以及网络游戏中的命令同步等场景。
8. 命令模式的性能考量与优化
虽然命令模式提供了良好的设计结构,但在性能敏感的场景中需要考虑一些优化策略。
8.1 命令对象池
频繁创建和销毁命令对象可能导致内存分配成为瓶颈。对象池模式可以缓解这个问题:
cpp复制template <typename CommandType>
class CommandPool {
public:
template <typename... Args>
CommandType* acquire(Args&&... args) {
if (pool_.empty()) {
pool_.push_back(std::make_unique<CommandType>(std::forward<Args>(args)...));
}
auto cmd = pool_.back().get();
cmd->reset(std::forward<Args>(args)...); // 假设CommandType有reset方法
pool_.pop_back();
return cmd;
}
void release(CommandType* cmd) {
pool_.push_back(std::unique_ptr<CommandType>(cmd));
}
private:
std::vector<std::unique_ptr<CommandType>> pool_;
};
8.2 小型命令优化
对于非常简单的命令,可以使用小型缓冲区优化(SBO)来避免堆分配:
cpp复制class LightweightCommand {
public:
template <typename F>
explicit LightweightCommand(F&& f) {
static_assert(sizeof(F) <= sizeof(buffer_), "Functor too large");
new (buffer_) F(std::forward<F>(f));
executor_ = [](void* buf) {
(*reinterpret_cast<F*>(buf))();
};
deleter_ = [](void* buf) {
reinterpret_cast<F*>(buf)->~F();
};
}
~LightweightCommand() {
if (deleter_) deleter_(buffer_);
}
void execute() {
executor_(buffer_);
}
private:
alignas(16) char buffer_[32]; // 足够存储小型函数对象
void (*executor_)(void*) = nullptr;
void (*deleter_)(void*) = nullptr;
};
8.3 命令批量处理
对于大量小命令,可以考虑批量处理来减少函数调用开销:
cpp复制class BatchProcessor {
public:
void addCommand(Command* cmd) {
currentBatch_.push_back(cmd);
}
void processBatch() {
for (auto cmd : currentBatch_) {
cmd->execute();
}
currentBatch_.clear();
}
private:
std::vector<Command*> currentBatch_;
};
在实际项目中,应该根据性能分析结果来决定是否需要这些优化。过早优化往往是浪费时间的根源,但对于已确定的性能热点,这些技术可以显著提高命令模式的执行效率。
