1. 命令模式基础回顾
在深入探讨C++中的命令模式变体之前,我们需要先理解命令模式的基本概念。命令模式是一种行为设计模式,它将请求封装为对象,从而使你可以参数化客户端对象,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式的核心思想是将"做什么"(请求)与"谁来做"(执行者)解耦。在C++中,典型的命令模式实现通常包含以下几个关键组件:
- Command(命令接口):声明执行操作的接口
- ConcreteCommand(具体命令):实现命令接口,绑定接收者与动作
- Invoker(调用者):要求命令执行请求
- Receiver(接收者):知道如何执行与请求相关的操作
下面是一个最基本的C++命令模式实现示例:
cpp复制#include <iostream>
#include <memory>
// 接收者类
class Receiver {
public:
void Action() {
std::cout << "Receiver: 执行操作" << std::endl;
}
};
// 命令接口
class Command {
public:
virtual ~Command() = default;
virtual void Execute() = 0;
};
// 具体命令
class ConcreteCommand : public Command {
public:
explicit 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;
};
int main() {
Receiver receiver;
ConcreteCommand command(&receiver);
Invoker invoker;
invoker.SetCommand(&command);
invoker.ExecuteCommand();
return 0;
}
这个基础实现展示了命令模式的核心结构,但在实际开发中,我们往往需要根据具体场景对命令模式进行各种变体和扩展。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 命令模式的常见变体
2.1 支持撤销/重做的命令模式
在实际应用中,命令模式最常见的变体是支持撤销(Undo)和重做(Redo)功能。这种变体通过在命令对象中保存执行前的状态或反向操作来实现。
cpp复制class UndoableCommand : public Command {
public:
virtual void Undo() = 0;
};
class ConcreteUndoableCommand : public UndoableCommand {
public:
explicit ConcreteUndoableCommand(Receiver* receiver)
: receiver_(receiver), state_before_(0) {}
void Execute() override {
state_before_ = receiver_->GetState();
receiver_->Action();
}
void Undo() override {
receiver_->SetState(state_before_);
}
private:
Receiver* receiver_;
int state_before_;
};
// 命令历史记录类
class CommandHistory {
public:
void Push(std::unique_ptr<UndoableCommand> cmd) {
history_.push_back(std::move(cmd));
}
std::unique_ptr<UndoableCommand> Pop() {
if (history_.empty()) return nullptr;
auto cmd = std::move(history_.back());
history_.pop_back();
return cmd;
}
private:
std::vector<std::unique_ptr<UndoableCommand>> history_;
};
这种实现方式允许我们维护一个命令历史栈,通过遍历栈来执行撤销和重做操作。在实际应用中,我们还需要考虑内存管理、命令合并等高级功能。
2.2 宏命令(组合命令)
宏命令是另一种常见的命令模式变体,它将多个命令组合成一个命令,实现批量执行的效果。这在需要执行一系列相关操作的场景中非常有用。
cpp复制class MacroCommand : public Command {
public:
void AddCommand(std::unique_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
void Execute() override {
for (const auto& cmd : commands_) {
cmd->Execute();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
// 使用示例
auto macro = std::make_unique<MacroCommand>();
macro->AddCommand(std::make_unique<ConcreteCommand>(&receiver1));
macro->AddCommand(std::make_unique<ConcreteCommand>(&receiver2));
Invoker invoker;
invoker.SetCommand(macro.get());
invoker.ExecuteCommand();
宏命令特别适合需要原子性执行多个操作的场景,比如事务处理、批量配置等。
2.3 异步命令模式
在现代C++开发中,我们经常需要处理异步操作。传统的命令模式可以扩展为支持异步执行:
cpp复制#include <future>
#include <thread>
class AsyncCommand : public Command {
public:
void Execute() override {
future_ = std::async(std::launch::async, [this] {
this->ExecuteAsync();
});
}
virtual void ExecuteAsync() = 0;
void Wait() {
if (future_.valid()) {
future_.wait();
}
}
private:
std::future<void> future_;
};
class ConcreteAsyncCommand : public AsyncCommand {
public:
explicit ConcreteAsyncCommand(Receiver* receiver) : receiver_(receiver) {}
void ExecuteAsync() override {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(1));
receiver_->Action();
}
private:
Receiver* receiver_;
};
这种变体在GUI编程、网络请求处理等场景中非常有用,可以避免阻塞主线程。
3. 基于现代C++特性的命令模式实现
3.1 使用std::function的命令模式
现代C++提供了std::function和lambda表达式,我们可以利用这些特性简化命令模式的实现:
cpp复制#include <functional>
#include <vector>
class FunctionCommand {
public:
using CommandFunc = std::function<void()>;
explicit FunctionCommand(CommandFunc func) : func_(std::move(func)) {}
void Execute() {
if (func_) {
func_();
}
}
private:
CommandFunc func_;
};
// 使用示例
Receiver receiver;
FunctionCommand cmd([&receiver] {
receiver.Action();
});
cmd.Execute();
这种实现方式更加灵活,可以轻松绑定任何可调用对象作为命令。结合std::bind,我们可以创建更加复杂的命令:
cpp复制class ComplexReceiver {
public:
void Action(int param, const std::string& message) {
std::cout << "ComplexReceiver: " << param << ", " << message << std::endl;
}
};
// 使用示例
ComplexReceiver complexReceiver;
auto boundFunc = std::bind(&ComplexReceiver::Action, &complexReceiver,
42, "Hello Command");
FunctionCommand complexCmd(boundFunc);
complexCmd.Execute();
3.2 使用可变参数模板的命令模式
C++11引入的可变参数模板可以让我们创建更加通用的命令实现:
cpp复制template <typename Receiver, typename... Args>
class GenericCommand {
public:
using ActionType = void (Receiver::*)(Args...);
GenericCommand(Receiver* receiver, ActionType action, Args... args)
: receiver_(receiver), action_(action), args_(std::forward<Args>(args)...) {}
void Execute() {
std::apply([this](auto&&... args) {
(receiver_->*action_)(std::forward<decltype(args)>(args)...);
}, args_);
}
private:
Receiver* receiver_;
ActionType action_;
std::tuple<Args...> args_;
};
// 使用示例
ComplexReceiver receiver;
GenericCommand cmd(&receiver, &ComplexReceiver::Action, 100, "Template Command");
cmd.Execute();
这种实现方式虽然复杂,但提供了极大的灵活性,可以适应各种不同的成员函数签名。
4. 命令模式在C++项目中的实际应用
4.1 GUI系统中的命令模式
在图形用户界面(GUI)开发中,命令模式被广泛应用。例如,菜单项、工具栏按钮等用户界面元素通常都是命令的调用者:
cpp复制// 假设我们有一个简单的绘图应用
class Document {
public:
void AddShape(const std::string& shape) {
shapes_.push_back(shape);
std::cout << "添加形状: " << shape << std::endl;
}
void RemoveLastShape() {
if (!shapes_.empty()) {
std::cout << "移除形状: " << shapes_.back() << std::endl;
shapes_.pop_back();
}
}
const auto& GetShapes() const { return shapes_; }
private:
std::vector<std::string> shapes_;
};
class AddShapeCommand : public UndoableCommand {
public:
AddShapeCommand(Document* doc, const std::string& shape)
: doc_(doc), shape_(shape) {}
void Execute() override {
doc_->AddShape(shape_);
}
void Undo() override {
doc_->RemoveLastShape();
}
private:
Document* doc_;
std::string shape_;
};
// 使用示例
Document doc;
CommandHistory history;
auto cmd1 = std::make_unique<AddShapeCommand>(&doc, "圆形");
cmd1->Execute();
history.Push(std::move(cmd1));
auto cmd2 = std::make_unique<AddShapeCommand>(&doc, "方形");
cmd2->Execute();
history.Push(std::move(cmd2));
// 撤销上一个操作
if (auto cmd = history.Pop()) {
cmd->Undo();
}
这种模式使得实现撤销/重做功能变得非常简单,同时也便于将用户操作记录到日志中。
4.2 游戏开发中的命令模式
在游戏开发中,命令模式常用于实现输入处理、AI行为队列等:
cpp复制class GameCharacter {
public:
void Move(int dx, int dy) {
x_ += dx;
y_ += dy;
std::cout << "移动到: (" << x_ << ", " << y_ << ")" << std::endl;
}
void Attack() {
std::cout << "攻击!" << std::endl;
}
void Jump() {
std::cout << "跳跃!" << std::endl;
}
private:
int x_ = 0;
int y_ = 0;
};
class GameCommand {
public:
virtual ~GameCommand() = default;
virtual void Execute(GameCharacter& character) = 0;
};
class MoveCommand : public GameCommand {
public:
MoveCommand(int dx, int dy) : dx_(dx), dy_(dy) {}
void Execute(GameCharacter& character) override {
character.Move(dx_, dy_);
}
private:
int dx_;
int dy_;
};
class InputHandler {
public:
GameCommand* HandleInput(char input) {
switch (input) {
case 'w': return &moveUp_;
case 's': return &moveDown_;
case 'a': return &moveLeft_;
case 'd': return &moveRight_;
case ' ': return &jump_;
case 'f': return &attack_;
default: return nullptr;
}
}
private:
MoveCommand moveUp_{0, 1};
MoveCommand moveDown_{0, -1};
MoveCommand moveLeft_{-1, 0};
MoveCommand moveRight_{1, 0};
struct JumpCommand : public GameCommand {
void Execute(GameCharacter& character) override {
character.Jump();
}
} jump_;
struct AttackCommand : public GameCommand {
void Execute(GameCharacter& character) override {
character.Attack();
}
} attack_;
};
// 使用示例
GameCharacter player;
InputHandler inputHandler;
char userInput = 'w'; // 假设来自用户输入
if (auto* cmd = inputHandler.HandleInput(userInput)) {
cmd->Execute(player);
}
这种设计使得游戏输入处理非常灵活,可以轻松实现按键重映射、宏命令、回放等功能。
4.3 网络请求处理中的命令模式
在网络编程中,命令模式可以用于封装不同类型的请求:
cpp复制#include <map>
#include <string>
class NetworkRequest {
public:
virtual ~NetworkRequest() = default;
virtual void Execute() = 0;
virtual std::string GetResponse() const = 0;
};
class HttpGetRequest : public NetworkRequest {
public:
explicit HttpGetRequest(const std::string& url) : url_(url) {}
void Execute() override {
// 模拟HTTP GET请求
std::cout << "执行GET请求: " << url_ << std::endl;
response_ = "来自 " + url_ + " 的响应数据";
}
std::string GetResponse() const override {
return response_;
}
private:
std::string url_;
std::string response_;
};
class RequestScheduler {
public:
void AddRequest(const std::string& id, std::unique_ptr<NetworkRequest> request) {
pending_requests_[id] = std::move(request);
}
void ExecuteAll() {
for (auto& [id, request] : pending_requests_) {
request->Execute();
completed_requests_[id] = request->GetResponse();
}
pending_requests_.clear();
}
std::string GetResponse(const std::string& id) const {
auto it = completed_requests_.find(id);
return it != completed_requests_.end() ? it->second : "";
}
private:
std::map<std::string, std::unique_ptr<NetworkRequest>> pending_requests_;
std::map<std::string, std::string> completed_requests_;
};
// 使用示例
RequestScheduler scheduler;
scheduler.AddRequest("homepage", std::make_unique<HttpGetRequest>("http://example.com"));
scheduler.AddRequest("api", std::make_unique<HttpGetRequest>("http://api.example.com/data"));
scheduler.ExecuteAll();
std::cout << "API响应: " << scheduler.GetResponse("api") << std::endl;
这种模式使得网络请求的管理和执行更加结构化,便于实现请求队列、优先级调度等高级功能。
5. 命令模式的性能考量与优化
5.1 内存管理优化
在C++中实现命令模式时,内存管理是一个重要考量。频繁创建和销毁命令对象可能导致性能问题。我们可以使用对象池模式来优化:
cpp复制#include <stack>
#include <memory>
template <typename T>
class ObjectPool {
public:
template <typename... Args>
std::unique_ptr<T, std::function<void(T*)>> Acquire(Args&&... args) {
if (pool_.empty()) {
return {new T(std::forward<Args>(args)...), [this](T* obj) {
pool_.push(std::unique_ptr<T>(obj));
}};
}
auto obj = std::move(pool_.top());
pool_.pop();
*obj = T(std::forward<Args>(args)...);
return {obj.release(), [this](T* obj) {
pool_.push(std::unique_ptr<T>(obj));
}};
}
private:
std::stack<std::unique_ptr<T>> pool_;
};
// 使用示例
ObjectPool<ConcreteCommand> commandPool;
{
auto cmd = commandPool.Acquire(&receiver);
cmd->Execute();
} // 命令对象自动返回对象池
这种技术特别适用于需要频繁创建和销毁相似命令对象的场景,如游戏中的输入处理。
5.2 命令的轻量级实现
对于简单的命令,我们可以使用更轻量的实现方式,减少虚函数调用的开销:
cpp复制class LightweightCommand {
public:
template <typename F>
explicit LightweightCommand(F&& f)
: execute_(std::forward<F>(f)) {}
void Execute() {
execute_();
}
private:
std::function<void()> execute_;
};
// 使用示例
Receiver receiver;
LightweightCommand cmd([&receiver] {
receiver.Action();
});
cmd.Execute();
这种实现避免了虚函数调用,性能更好,但灵活性稍逊于传统的命令模式实现。
5.3 命令的批量处理
当需要处理大量命令时,批量处理可以显著提高性能:
cpp复制class CommandBatch {
public:
void AddCommand(std::function<void()> cmd) {
commands_.push_back(std::move(cmd));
}
void Execute() {
for (const auto& cmd : commands_) {
cmd();
}
}
void Clear() {
commands_.clear();
}
private:
std::vector<std::function<void()>> commands_;
};
// 使用示例
CommandBatch batch;
Receiver receiver1, receiver2, receiver3;
batch.AddCommand([&receiver1] { receiver1.Action(); });
batch.AddCommand([&receiver2] { receiver2.Action(); });
batch.AddCommand([&receiver3] { receiver3.Action(); });
batch.Execute();
批量处理减少了函数调用的开销,特别适合在游戏循环、事件处理等场景中使用。
6. 命令模式与其他设计模式的结合
6.1 命令模式与责任链模式结合
将命令模式与责任链模式结合,可以创建灵活的命令处理管道:
cpp复制class CommandHandler {
public:
virtual ~CommandHandler() = default;
void SetNext(std::shared_ptr<CommandHandler> next) {
next_ = next;
}
virtual void Handle(Command* command) {
if (next_) {
next_->Handle(command);
}
}
protected:
std::shared_ptr<CommandHandler> next_;
};
class LoggingHandler : public CommandHandler {
public:
void Handle(Command* command) override {
std::cout << "记录命令执行前状态" << std::endl;
CommandHandler::Handle(command);
std::cout << "记录命令执行后状态" << std::endl;
}
};
class ValidationHandler : public CommandHandler {
public:
void Handle(Command* command) override {
std::cout << "验证命令参数" << std::endl;
CommandHandler::Handle(command);
}
};
// 使用示例
auto handlerChain = std::make_shared<LoggingHandler>();
handlerChain->SetNext(std::make_shared<ValidationHandler>());
ConcreteCommand cmd(&receiver);
handlerChain->Handle(&cmd);
这种组合模式使得我们可以灵活地添加各种中间处理逻辑,如日志记录、验证、权限检查等。
6.2 命令模式与备忘录模式结合
备忘录模式可以帮助我们更好地实现命令的撤销功能:
cpp复制class Memento {
public:
virtual ~Memento() = default;
};
class DocumentMemento : public Memento {
public:
explicit DocumentMemento(const std::vector<std::string>& shapes)
: shapes_(shapes) {}
const std::vector<std::string>& GetState() const {
return shapes_;
}
private:
std::vector<std::string> shapes_;
};
class Document {
public:
std::unique_ptr<Memento> CreateMemento() const {
return std::make_unique<DocumentMemento>(shapes_);
}
void RestoreFromMemento(const Memento* memento) {
if (const auto* docMemento = dynamic_cast<const DocumentMemento*>(memento)) {
shapes_ = docMemento->GetState();
}
}
// 其他成员函数同前...
private:
std::vector<std::string> shapes_;
};
class DocumentCommand : public UndoableCommand {
public:
explicit DocumentCommand(Document* doc) : doc_(doc) {}
void Execute() override {
memento_ = doc_->CreateMemento();
PerformAction();
}
void Undo() override {
if (memento_) {
doc_->RestoreFromMemento(memento_.get());
}
}
virtual void PerformAction() = 0;
protected:
Document* doc_;
std::unique_ptr<Memento> memento_;
};
这种实现方式提供了更完善的撤销机制,可以保存和恢复对象的完整状态。
6.3 命令模式与观察者模式结合
将命令模式与观察者模式结合,可以实现命令执行的事件通知:
cpp复制#include <set>
class CommandObserver {
public:
virtual ~CommandObserver() = default;
virtual void OnCommandExecuted(Command* command) = 0;
};
class ObservableCommand : public Command {
public:
void AddObserver(CommandObserver* observer) {
observers_.insert(observer);
}
void RemoveObserver(CommandObserver* observer) {
observers_.erase(observer);
}
void Execute() override {
DoExecute();
NotifyObservers();
}
virtual void DoExecute() = 0;
private:
void NotifyObservers() {
for (auto* observer : observers_) {
observer->OnCommandExecuted(this);
}
}
std::set<CommandObserver*> observers_;
};
class LoggingObserver : public CommandObserver {
public:
void OnCommandExecuted(Command* command) override {
std::cout << "命令已执行: " << typeid(*command).name() << std::endl;
}
};
// 使用示例
ConcreteObservableCommand cmd(&receiver);
LoggingObserver observer;
cmd.AddObserver(&observer);
cmd.Execute();
这种组合模式在需要监控命令执行情况或实现命令执行后处理的场景中非常有用。
7. C++20/23新特性在命令模式中的应用
7.1 使用协程实现异步命令
C++20引入了协程支持,我们可以利用它来实现更优雅的异步命令:
cpp复制#include <coroutine>
#include <exception>
struct AsyncResult {
struct promise_type {
AsyncResult get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
class CoroutineCommand : public Command {
public:
virtual AsyncResult ExecuteAsync() = 0;
void Execute() override {
ExecuteAsync();
}
};
class NetworkFetchCommand : public CoroutineCommand {
public:
AsyncResult ExecuteAsync() override {
// 模拟网络请求
co_await std::suspend_always{};
std::cout << "网络请求完成" << std::endl;
}
};
协程使得异步代码的编写更加直观,避免了回调地狱的问题。
7.2 使用概念(Concepts)约束命令类型
C++20的概念(Concepts)特性可以让我们更好地约束命令类型:
cpp复制template <typename T>
concept CommandType = requires(T cmd) {
{ cmd.Execute() } -> std::same_as<void>;
};
template <CommandType Cmd>
void ExecuteCommand(Cmd& cmd) {
cmd.Execute();
}
// 使用示例
ConcreteCommand cmd(&receiver);
ExecuteCommand(cmd); // 编译通过
struct NotACommand {};
NotACommand notCmd;
// ExecuteCommand(notCmd); // 编译错误
这种技术可以在编译期确保类型符合命令接口的约束,提高代码的安全性。
7.3 使用span和ranges处理命令集合
C++20的std::span和ranges库可以简化命令集合的处理:
cpp复制#include <span>
#include <ranges>
void ExecuteCommands(std::span<Command*> commands) {
for (auto* cmd : commands) {
cmd->Execute();
}
}
void ExecuteSelectedCommands(std::span<Command*> commands,
std::predicate<Command*> auto&& pred) {
for (auto* cmd : commands | std::views::filter(pred)) {
cmd->Execute();
}
}
// 使用示例
Command* commands[] = {&cmd1, &cmd2, &cmd3};
ExecuteCommands(commands);
// 只执行满足条件的命令
ExecuteSelectedCommands(commands, [](Command* cmd) {
return dynamic_cast<UndoableCommand*>(cmd) != nullptr;
});
这些新特性使得命令的批量处理更加方便和安全。
