我正在研究一系列由一系列"模块构建的软件。模块可以连接在一起形成完整的应用程序(一个模块可能转到另一个模块,一种隐含的状态机)。每个模块都可以呈现到屏幕,从其他模块获取更新和访问状态。请注意,模块仍然在同一个过程中,因此不需要设计IPC。
但是,这些模块并不直接相互依赖。有一个单例对象,其唯一目的是管理模块之间的消息传递。当您想要从任何模块注册活动时:
CPostMaster::Instance().RegisterEvent("TheEventName", [](std::string const& data) { /* the callback */ });
data
变量是序列化数据。可以是任何东西,但通常是XML或JSON。要发送您要执行的活动:
std::string serialized_data = /* serialized data, do this before calling */;
CPostMaster::Instance().SendEvent("TheEventName", serialized_data);
第二个参数是可选的。
拥有"主管当局"对于消息传递有一个缺点:事件本身不能使用某种序列化或类型擦除发送变化的参数(从图像中删除类型安全性并影响性能)。
但它也具有不需要严格/强耦合的好处,这意味着在任何给定时间,不同模块可以负责发送特定事件而不需要更改接收模块。
替代方案似乎没有使用单例,而是每个模块都接收一个可以用来订阅的对象。这可能会变得混乱,特别是当你在任何地方传递它们时,很快意味着函数开始采用样板参数。
在像这样的系统中传递消息的好设计是什么?如何改进并使其易于管理?类型安全和开/关原则在这里很重要。我认为只要它们可以被模拟(用于单元测试)并且在模块改变而不会严重影响整个系统的情况下轻松换出模块,就可以直接依赖模块(但这是开/关原则的一部分) )。
答案 0 :(得分:0)
首先:我不喜欢单身人士。我接受的唯一单例是一个单独的管理器(某种中央实例分发器),它处理所有"单例"的定义的init和deinit。按照规定的顺序。
但回到你的问题:
您的标题已经有解决方案:定义消息界面。如果您希望type-safety定义具有共同属性的IMessage
。
然后定义IMessage
的特化,然后由回调消耗。
棘手的部分是:你将需要RTTI,这在c ++中是奇怪的,我知道但是可能值得的好处,如果你被限制在gcc或visual studio,你可以利用这些类型,或者实现IMessage
本身中的一些简单RTTI可以避免dynamic_cast
。
为了避免在IMessage
周围检查和转换的回调中的样板代码,我将提供一个实用函数(伪代码,调整指针,引用,智能ptrs,const正确性等)。
T SafeCast<T>(IMessage message);
取决于编译器的实现,您应该将T
的限制添加为IMessage
的子类型以及当转换失败时应该发生什么(例外,nullptr
等)。
或者:检查其他人是如何解决这个问题的(可能是Qt的信号和插槽或其他东西)
答案 1 :(得分:0)
我会让子模块依赖于父类(在你的情况下是单例)。然后,您可以沿着该行传递此对象的引用,以便在模块中使用。
Module(Handler& h) : _h(h) { }
void do_stuff(){
_h.RegisterEvent("TheEventName", [](std::string const& data)
{ /* the callback */ })
然后我将您的Module类本身或其他类注册为Event,而在Handler方面,我将以一种您将获得多个回调而不是一个回调的方式正式化消息传递。你必须正式化你的消息,但你有类型安全而不是传递字符串。 例如,处理程序在解析消息时,他会调用:
_callback.start(); //signals the start of a message
_callback.signalParam1(1); //calls Module.signalParam(int);
_callback.signalParam2("test"); //calls Module.signalParam2(const char*);
_callback.end();
您的模块需要实施这些。