5_软件重构_模块间通讯方式_事件

发布于:2025-09-03 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、模块(Module)间通讯方式简介       

最近一大段的时间任务是做模块(Module)间通讯方案设计,未编码。通过调研,做法如下:

       观察者模式

              实现思路:维护一个回调函数列表(std::function 或函数指针),广播时循环调用。

优点:简单直接,性能高。

缺点:调用链耦合,容易出现回调地狱,模块之间绑定紧密。

       信号槽模式(Qt / boost::signals2)

实现思路:维护一个消息队列,广播时把消息写入多个队列,每个订阅模块消费自己的队列。

优点:解耦,异步处理,支持不同消费速度。

缺点:消息需要复制,内存开销大;实现稍复杂。

       事件总线()

实现思路:设计一个统一的 事件总线,模块之间只和总线交互。

事件总线可以支持 同步调用(直接回调)或 异步调用(队列/线程池)。

优点:解耦、扩展性好、支持广播与点对点。

缺点:需要设计完整的调度机制。

常见实现思路:

Apollo CyberRT / ROS2 的进程内 PubSub

一些开源库:

| 库名称                       | 语言版本     | 特点与优势                   | 适用场景                 |

| ------------------------- | -------- | ----------------------- | -------------------- |

| **EventBus (gelldur)**    | C++11    | 轻量(37 KB)、快速、类型安全       | 简单事件广播模块轻量集成         |

| **eventbus (DevPaul123)** | C++17 支持 | header-only、RAII注销、线程安全 | 模块解耦、现代 C++ 项目       |

| **eventpp**               | C++11+   | 同步/异步、线程安全、高性能、灵活       | 复杂事件处理、性能敏感的框架或工具类项目 |

| **PubSubQueue (IPC)**     | C++      | 共享内存、高效 IPC (跨进程)       | 高实时要求的进程间通信          |

| **D-Bus**                 | C/C++    | 系统级消息总线、语言绑定丰富、成熟       | 系统服务间通信、跨语言、跨进程的场景   |

建议选择指南

如果你追求极简低耦合且无需异步复杂逻辑:推荐 gelldur/EventBus

若你希望让事件系统更加灵活、安全并融入现代 C++ 机制:推荐 DevPaul123/eventbus

对性能、线程安全、复杂调度要求高:推荐 eventpp

将来可能扩展到跨进程,关注延迟与速度,可考虑 PubSubQueue

二、实战演练

#includeclass Event{public:   Event(int e, std::string e_msg): event_(e), event_msg(e_msg) {}   private:    int event_;    std::string event_msg;}; class ModuleBase{public:    virtual ~ModuleBase() {}  // 必须要有虚析构函数    virtual void init()  = 0;    virtual void start() = 0;    virtual std::string name() = 0;    virtual std::string version() = 0;     virtual void processEvent() { std::cout << "event ModuleBase" << std::endl; };}; class EventDispatch{public:    void SendEvent(ModuleBase *receiver, Event *event) {        receiver->processEvent();    }}; class ModuleA : public ModuleBase{    void init()    override { std::cout << "init ModuleA" << std::endl; };    void start()   override { std::cout << "start ModuleA" << std::endl; };    std::string name()    override { return "ModuleA"; };    std::string version() override { return "1.0.0"; };     void processEvent() { std::cout << "event ModuleA" << std::endl; };}; int main(void){    std::cout << "enter main" << std::endl;     ModuleBase *module_ = new ModuleA();    EventDispatch *eventDispatch = new EventDispatch();    Event *e = new Event(10000, "test msg");    eventDispatch->SendEvent(module_, e);     return 0;}

       整了个gitee仓库,来记录这个软件结构,慢慢逐渐完善。

       同时把C++语法沉淀一下,还有一些现代C++思想也要学习,如执行器。

       https://gitee.com/ideals_and_love/modularization

       Module间通讯,很大程度上无法避免接口调用;对接口调用进一步提炼,是数据的收发,高频数据用回调函数的方式来解决,不过此时仍有一定耦合但也能接受,直接开一个线程高频舒心是不被接受的。其他数据收发,如机械臂运动目标点位的下发,这块直接调用接口也能接受,但是如果不想包含对方的头文件呢?如果模块间交互数据改为buf+size,就很C风格,那结构体就是绕不开的,底层核心问题无法避免,最终还是取舍问题。

       模块间通讯再加上这次事件的点对点方式,基本可以覆盖进程内模块间通讯需求。

欢迎关注,欢迎留言交流。

往期重构相关:

4_软件重构_二进制兼容接口设计

3_软件重构_组件化开发实例方法论

2_软件重构_一种组件化开发方式

1_C++进程内模块之间调用函数的方式

1_啥是AimRT


网站公告

今日签到

点亮在社区的每一天
去签到