如何改进使用单例模式的消息传递系统?
How can I improve a messaging system that utilizes the singleton pattern?
我正在开发一个由一系列 "modules" 构建的软件。模块可以连接在一起以形成完整的应用程序(一个模块可能会转到另一个模块,有点像隐含的状态机)。每个模块都可以呈现到屏幕上,从其他模块获取更新和访问状态。请注意,模块仍在同一进程中,因此无需将 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);
第二个参数是可选的。
使用 "master authority" 进行消息传递有一个缺点:如果不使用某种序列化或类型擦除(从图片中删除类型安全并影响性能),事件本身不能发送不同的参数。
但它也有 strict/strong 不需要耦合的好处,这意味着在任何给定时间,不同的模块可以负责发送特定事件,而无需更改接收模块。
备选方案似乎不使用单例,而是每个模块接收一个可用于订阅的对象。这可能会变得混乱,尤其是当您到处传递它们时,这将很快意味着函数开始采用样板参数。
在这样的系统中传递消息的好的设计是什么?如何对其进行改进并使其易于管理?类型安全和 open/close 原则在这里很重要。我认为跨模块具有直接依赖关系是可以的,只要它们可以被模拟(用于单元测试)并且在模块更改时可以轻松换出而不会对整个系统造成严重影响(但这是 open/close 原则的一部分)。
第一:我不喜欢单身人士。我接受的唯一单例是单例管理器(某种中央实例分发器),它按定义的顺序处理所有 "singletons" 的定义初始化和取消初始化。
但回到你的问题:
你的标题已经有了解决方案:定义一个消息接口。如果你想 type-safety 定义一个具有公共属性的 IMessage
。
然后定义 IMessage
的特化,然后由您的回调使用。
棘手的部分是:你将需要 RTTI,这在 c++ 中很奇怪,我知道但可能是值得的,如果你仅限于 gcc 或 visual studio,你可以利用这些类型,或者在 IMessage
本身中实现一些简单的 RTTI 以避免 dynamic_cast
.
为了避免在回调中检查和转换 IMessage
的样板代码,我会提供一个实用函数(伪代码、调整指针、引用、智能指针、const 正确性等)
T SafeCast<T>(IMessage message);
根据编译器的实现,您应该将 T
的限制添加为 IMessage
的子类型,以及当转换失败时应该发生什么(例外,nullptr
等)。
或者:检查其他人是如何解决这个问题的(也许是 Qt 的 Signals&Slots 或 Boost 中的某些东西)
我会让子模块依赖父模块 class(在你的例子中是单例)。然后你可以沿线传递这个对象的引用,以在模块中使用。
Module(Handler& h) : _h(h) { }
void do_stuff(){
_h.RegisterEvent("TheEventName", [](std::string const& data)
{ /* the callback */ })
然后我会将您的模块 class 本身或另一个 class 注册为一个事件,在处理程序方面,我将以一种您可以获得多个的方式正式化消息传递回调而不仅仅是一个。虽然您必须正式化您的消息,但您将拥有类型安全而不是传递字符串。
例如处理程序,在解析消息时,他会调用:
_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();
您的模块需要实现这些。
我正在开发一个由一系列 "modules" 构建的软件。模块可以连接在一起以形成完整的应用程序(一个模块可能会转到另一个模块,有点像隐含的状态机)。每个模块都可以呈现到屏幕上,从其他模块获取更新和访问状态。请注意,模块仍在同一进程中,因此无需将 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);
第二个参数是可选的。
使用 "master authority" 进行消息传递有一个缺点:如果不使用某种序列化或类型擦除(从图片中删除类型安全并影响性能),事件本身不能发送不同的参数。
但它也有 strict/strong 不需要耦合的好处,这意味着在任何给定时间,不同的模块可以负责发送特定事件,而无需更改接收模块。
备选方案似乎不使用单例,而是每个模块接收一个可用于订阅的对象。这可能会变得混乱,尤其是当您到处传递它们时,这将很快意味着函数开始采用样板参数。
在这样的系统中传递消息的好的设计是什么?如何对其进行改进并使其易于管理?类型安全和 open/close 原则在这里很重要。我认为跨模块具有直接依赖关系是可以的,只要它们可以被模拟(用于单元测试)并且在模块更改时可以轻松换出而不会对整个系统造成严重影响(但这是 open/close 原则的一部分)。
第一:我不喜欢单身人士。我接受的唯一单例是单例管理器(某种中央实例分发器),它按定义的顺序处理所有 "singletons" 的定义初始化和取消初始化。
但回到你的问题:
你的标题已经有了解决方案:定义一个消息接口。如果你想 type-safety 定义一个具有公共属性的 IMessage
。
然后定义 IMessage
的特化,然后由您的回调使用。
棘手的部分是:你将需要 RTTI,这在 c++ 中很奇怪,我知道但可能是值得的,如果你仅限于 gcc 或 visual studio,你可以利用这些类型,或者在 IMessage
本身中实现一些简单的 RTTI 以避免 dynamic_cast
.
为了避免在回调中检查和转换 IMessage
的样板代码,我会提供一个实用函数(伪代码、调整指针、引用、智能指针、const 正确性等)
T SafeCast<T>(IMessage message);
根据编译器的实现,您应该将 T
的限制添加为 IMessage
的子类型,以及当转换失败时应该发生什么(例外,nullptr
等)。
或者:检查其他人是如何解决这个问题的(也许是 Qt 的 Signals&Slots 或 Boost 中的某些东西)
我会让子模块依赖父模块 class(在你的例子中是单例)。然后你可以沿线传递这个对象的引用,以在模块中使用。
Module(Handler& h) : _h(h) { }
void do_stuff(){
_h.RegisterEvent("TheEventName", [](std::string const& data)
{ /* the callback */ })
然后我会将您的模块 class 本身或另一个 class 注册为一个事件,在处理程序方面,我将以一种您可以获得多个的方式正式化消息传递回调而不仅仅是一个。虽然您必须正式化您的消息,但您将拥有类型安全而不是传递字符串。 例如处理程序,在解析消息时,他会调用:
_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();
您的模块需要实现这些。