命令模式的核心是将请求的接收者、执行逻辑、请求参数封装成独立的命令对象,让请求的调用者不需要关心具体执行细节。回调函数则是把一段可执行的逻辑作为参数传递给其他函数,在合适的时机触发执行。二者结合可以兼顾命令模式的封装优势和回调函数的灵活特性,在事件处理、异步任务等场景中非常实用。

命令模式与回调函数结合的核心优势
二者的结合可以解决单独使用命令模式时命令对象逻辑固化的问题,也能避免回调函数分散管理带来的维护成本,主要优势有以下几点:
- 解耦更彻底:调用者只需要持有命令对象,不需要知道具体的回调逻辑和接收者,接收者也不需要依赖调用者的具体实现
- 逻辑更灵活:命令对象内部可以动态绑定不同的回调函数,同一个命令模板可以对应多种不同的执行逻辑
- 扩展更方便:新增功能时只需要新增回调函数或者新增命令子类,不需要修改已有的调用逻辑
基础实现示例
首先我们定义命令模式的抽象基类,内部预留回调函数的绑定接口:
#include <iostream>
#include <functional>
#include <memory>
// 抽象命令基类
class Command {
public:
// 定义回调函数类型,参数为命令携带的参数,无返回值
using Callback = std::function<void(const std::string&)>;
virtual ~Command() = default;
// 执行命令的接口
virtual void execute() = 0;
// 设置回调函数的接口
void setCallback(Callback cb) {
callback = cb;
}
protected:
Callback callback; // 存储绑定的回调函数
};
具体命令实现
接下来实现一个具体的打印命令,执行时会触发绑定的回调函数:
// 具体打印命令类
class PrintCommand : public Command {
public:
PrintCommand(std::string content) : content(std::move(content)) {}
void execute() override {
// 执行命令时先处理自身逻辑,再触发回调
std::cout << "执行打印命令,内容:" << content << std::endl;
if (callback) {
// 调用回调函数,传递命令内容作为参数
callback(content);
}
}
private:
std::string content; // 命令携带的参数
};
回调函数与调用逻辑
定义具体的回调函数,再由调用者组装命令对象和回调函数:
// 具体的回调函数1:记录日志
void logCallback(const std::string& content) {
std::cout << "日志回调:已处理内容 " << content << std::endl;
}
// 具体的回调函数2:统计数据
void countCallback(const std::string& content) {
std::cout << "统计回调:内容长度为 " << content.size() << std::endl;
}
int main() {
// 创建命令对象
auto cmd = std::make_shared<PrintCommand>("Hello Command Pattern");
// 绑定第一个回调函数
cmd->setCallback(logCallback);
cmd->execute();
std::cout << "----- 分割线 -----" << std::endl;
// 重新绑定第二个回调函数,同一个命令对象可以切换不同回调
cmd->setCallback(countCallback);
cmd->execute();
return 0;
}
上述代码的执行结果如下:
执行打印命令,内容:Hello Command Pattern 日志回调:已处理内容 Hello Command Pattern ----- 分割线 ----- 执行打印命令,内容:Hello Command Pattern 统计回调:内容长度为 20
进阶应用:结合命令队列实现异步回调
在实际开发中,我们常需要将命令放入队列异步执行,结合回调函数可以在命令执行完成后通知调用者,示例代码如下:
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
// 命令队列类
class CommandQueue {
public:
void push(std::shared_ptr<Command> cmd) {
std::lock_guard<std::mutex> lock(mtx);
cmdQueue.push(cmd);
cv.notify_one(); // 通知等待的线程处理命令
}
std::shared_ptr<Command> pop() {
std::unique_lock<std::mutex> lock(mtx);
// 等待队列中有命令
cv.wait(lock, [this]() { return !cmdQueue.empty(); });
auto cmd = cmdQueue.front();
cmdQueue.pop();
return cmd;
}
private:
std::queue<std::shared_ptr<Command>> cmdQueue;
std::mutex mtx;
std::condition_variable cv;
};
// 异步处理线程函数
void processCommands(CommandQueue& queue) {
while (true) {
auto cmd = queue.pop();
cmd->execute(); // 执行命令,会触发绑定的回调
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟处理耗时
}
}
int main() {
CommandQueue queue;
// 启动异步处理线程
std::thread worker(processCommands, std::ref(queue));
// 创建命令并绑定回调
auto cmd1 = std::make_shared<PrintCommand>("异步任务1");
cmd1->setCallback([](const std::string& content) {
std::cout << "异步回调1:任务 " << content << " 执行完成" << std::endl;
});
auto cmd2 = std::make_shared<PrintCommand>("异步任务2");
cmd2->setCallback([](const std::string& content) {
std::cout << "异步回调2:任务 " << content << " 执行完成" << std::endl;
});
// 将命令加入队列
queue.push(cmd1);
queue.push(cmd2);
// 主线程等待,避免程序退出
worker.join();
return 0;
}
开发注意事项
在使用这种结合方式时,需要注意以下几点:
- 回调函数的生命周期管理:如果回调函数绑定了局部对象的成员函数,需要确保对象生命周期长于命令执行的时间,避免悬空引用
- 回调函数的异常处理:如果回调函数可能抛出异常,需要在命令的execute方法中做好异常捕获,避免影响整个命令队列的执行
- 回调参数的设计:回调函数的参数应该尽量通用,避免和具体命令强耦合,方便后续扩展不同的命令类型
这种结合方式在GUI事件处理、网络请求回调、任务调度系统中都有广泛应用,合理设计可以大幅提升代码的灵活性和可维护性。