插件式框架代码分析(3)
CommandBus这是什么啊
CommandBus是项目里的一个 C++ 类,可以理解为命令中转站:
发送方给出命令编号(unsigned int commandId)
它找到对应的处理器(CommandHandler * handler)并执行。
你的程序已经在使用它加载插件:
ApplicationHost │ 发送命令 1000,附带插件目录 ▼ CommandBus │ 查到:1000 对应 PluginLoader ▼ PluginLoader 扫描目录、加载 DLL发送命令1000
// 以可执行文件所在目录定位插件,避免受 Qt Creator 工作目录设置影响。 QByteArray path = QDir(QCoreApplication::applicationDirPath()) .filePath("plugins") .toUtf8(); // 1000 是本学习版约定的“加载插件”命令,CommandBindings 将它绑定到 PluginLoader。 // 分发为同步调用,path 在调用期间有效;处理器不能保存 data 指针供稍后使用。 if (!ServiceRegistry::instance()->commandBus()->dispatchCommand( 1000, path.data(), path.size())) qWarning() << "Some plugins failed to load; see previous messages";// 命令分发 int CommandBus::dispatchCommand(unsigned int commandId, char * data /*= 0L*/, int length/* = 0*/) { CommandHandler * handler = findHandler(commandId); if (handler == 0L) { return 0; } // 虚函数调用进入具体处理器;总线不需要知道它是不是插件加载器。 return handler->dispatchCommand(commandId, data, length); }bool PluginLoader::dispatchCommand(unsigned int id, char *data, int length) { // 命令入口只验证载荷并转换编码,具体加载步骤交给下层函数。 if (id != 1000 || !data || length <= 0) return false; return loadDirectory(QString::fromUtf8(data, length)); }| 类 | 职责 | 在当前项目中的作用 |
|---|---|---|
CommandHandler | 抽象接口:规定处理器必须提供什么方法 | 定义dispatchCommand、dispatchMessage、dispose |
PluginLoader | 具体处理器:执行插件加载 | 继承CommandHandler,处理命令1000 |
注册:
| 类 | 职责 | 在当前项目中的作用 |
|---|---|---|
CommandBus | 分发器:保存编号与处理器的映射 | 收到1000后,找到对应的PluginLoader并调用它 |
// 注册指定命令处理器 int CommandBus::registerHandler(unsigned int commandId, CommandHandler * handler) { if (findHandler(commandId) != NULL) { // 同一CmdID不能重复注册 return -1; } // 同一个处理器可以绑定多个命令,每个成功绑定都占一份引用。 commandHandlers[commandId] = handler; retainHandler(handler); return 0; }哪里调用了它?
| 类 | 职责 | 在当前项目中的作用 |
|---|---|---|
CommandBindings | 绑定管理:构造时注册,析构时注销 | 创建PluginLoader,把它绑定到命令1000 |
CommandBus只认识CommandHandler接口。它不需要知道处理器是插件加载器(PluginLoader),还是将来新增的文件保存器、设备控制器。
CommandBindings::CommandBindings(WidgetFactory *factory) { // 注册成功后,分发器通过引用计数管理这个堆对象;不能再手动 delete。 auto *loader = new PluginLoader(factory); registered = ServiceRegistry::instance()->commandBus()->registerHandler(1000, loader) == 0; if (!registered) // 注册被拒绝时总线没有接管引用,申请者负责释放尚未使用的加载器。 delete loader; }对应的核心调用是:
// 注册:以后收到命令 1000,就交给这个处理器。 commandBus->registerHandler(1000, loader); // 发送:让命令 1000 的处理器执行操作。 commandBus->dispatchCommand(1000, data, length);发送方只需要知道“发什么命令、带什么数据”,不用直接调用PluginLoader。
本质上,CommandBus管理了一组<id,CommandHandler>,根据id--->CommandHandler--->CommandHandler的子类
对,你理解得很准确。更精确地说,它管理的是<id, CommandHandler*>映射:
map<unsigned int, CommandHandler*> commandHandlers;执行过程是:
id → 查找 CommandHandler* → 调用虚函数 dispatchCommand() → 执行该指针所指向的子类实现这里并没有“先找到基类,再查找子类”的过程。指针从一开始就指向PluginLoader对象,只是以CommandHandler*类型保存;调用时由虚函数机制选择具体实现。
是的,这个软件还算蛮充分地利用虚函数实现重载和多态
是的,这个框架确实充分利用了虚函数实现多态。不过这里准确的术语是重写(override),而不是重载(overload):
- 重载:同名函数,参数列表不同,例如
registerHandler(id, handler)和registerHandler(firstId, lastId, handler)。 - 重写:子类实现基类的虚函数,例如
PluginLoader::dispatchCommand()。 - 运行时多态:通过
CommandHandler*调用虚函数,实际执行PluginLoader的实现。
这套设计的价值在于:总线的分发代码保持稳定,新增功能通过实现新的处理器并注册编号来扩展。插件的Plugin、WidgetProvider接口也是类似思路。