我正在考虑在C ++应用程序中实现事件的不同方法。有建议通过通知中心实施集中的事件调度。另一种方法是将事件的来源和目标直接通信。但是,我对通知中心方法有所保留。我会在我看到它们时勾勒出两种方法(我很可能误解了它们,我以前从未实现过事件处理)。
a)直接沟通。事件是其源界面的一部分。对事件感兴趣的对象必须以某种方式获取源类的实例并订阅其事件:
struct Source
{
Event</*some_args_here*/> InterestingEventA;
Event</*some_other_args_here*/> InterestingEventB;
};
class Target
{
public:
void Subscribe(Source& s)
{
s.InterestingEventA += CreateDelegate(&MyHandlerFunction, this);
}
private:
void MyHandlerFunction(/*args*/) { /*whatever*/ }
};
(根据我的理解,boost :: signals,Qt signal / slots和.NET事件都是这样的,但我可能错了。)
b)通知中心。事件在其来源界面中不可见。所有事件都发送到一些通知中心,可能是作为单身人员实施的(任何关于避免这种情况的建议都会受到赞赏),因为他们被解雇了。目标对象不必知道有关源的任何信息;他们通过访问通知中心订阅某些事件类型。通知中心收到新事件后,会通知所有对该特定事件感兴趣的订户。
class NotificationCenter
{
public:
NotificationCenter& Instance();
void Subscribe(IEvent& event, IEventTarget& target);
void Unsubscribe(IEvent& event, IEventTarget& target);
void FireEvent(IEvent& event);
};
class Source
{
void SomePrivateFunc()
{
// ...
InterestingEventA event(/* some args here*/);
NotificationCenter::Instance().FireEvent(event);
// ...
}
};
class Target : public IEventTarget
{
public:
Target()
{
NotificationCenter::Instance().Subscribe(InterestingEventA(), *this);
}
void OnEvent(IEvent& event) override {/**/}
};
(我接受了Poco这个术语&#34;通知中心&#34;据我所知,它实现了这两种方法。)
我可以看到这种方法的一些优点;目标创建订阅会更容易,因为他们不需要访问来源。此外,不存在任何终生管理问题:与来源不同,通知中心将始终比目标更长,因此他们的目标总是取消订阅他们的析构函数,而不必担心来源是否仍然存在(这是一个主要的骗局)可以在直接沟通中看到)。但是,我担心这种方法可能导致无法维护的代码,因为:
各种各样的事件,可能完全无关,会发生在这一大水槽上。
实施通知中心最明显的方式是单身,因此很难跟踪修改订阅者的人和修改时间。列表
事件在任何界面都不可见,因此无法查看特定事件是否属于任何来源。
由于这些缺点,我担心随着应用程序的增长,跟踪对象之间的连接将变得非常困难(我想象一下试图理解为什么某些特定事件不会出现问题的问题。例如,火。
我正在寻找关于&#34;通知中心&#34;的利弊的建议。做法。它可维护吗?它适合各种应用吗?也许有办法改进实施?我所描述的两种方法之间的比较以及任何其他事件处理建议都是最受欢迎的。
答案 0 :(得分:4)
这些方法是正交的。在特定对象之间交换事件时,应使用直接通信。通知中心方法仅应用于广播事件,例如当你想要处理给定类型的所有事件而不管它们的来源,或者你想要将事件发送到你事先不知道的某些对象集时。
要避免单例,请重复使用代码进行直接通信,并将通知中心对象订阅到您希望以此方式处理的所有事件。为了使事情易于管理,您可以在发射对象中执行此操作。
直接通信与生命周期相关的问题通常通过要求订阅任何事件的每个类必须从特定基类派生来解决;在Boost.Signals中,此类称为trackable
。等效于CreateDelegate
函数存储trackable
内数据成员中给定对象的订阅信息。在销毁trackable
时,通过调用匹配的Unsubscribe
函数取消所有订阅。请注意,这不是线程安全的,因为只有在派生类析构函数完成后才会调用trackable
的析构函数 - 有一段时间内部分销毁的对象仍然可以接收事件。
答案 1 :(得分:2)
我会根据问题的要求关注“NotificationCenter
”解决方案的优缺点(虽然我称之为DataBus
,Dispatcher
或Publisher/Subscriber
,代替)。
<强>赞成强>:
NotificationCenter
,则可以简化并发管理(可以使用NotificationCenter
作为cpu时间的调度程序来避免多线程。)<强>缺点强>:
NotificationCenter
)A
发出事件E
而类B
注册{{1}的通知}},你不能在“E
和C
之间插入一个类A
”来改变B
的内容,让我们对它进行一些详细阐述)E
必须管理客户端的错误(即NotificationCenter
应该提供一个异常处理程序来捕获事件处理程序中抛出的异常值。)此外,我想指出,NotificationCenter是一个单身人士并不是必不可少的。实际上,您可以拥有多个NotificationCenter实例,每个实例管理不同类别的事件(例如,在嵌入式系统中,您可以为低级硬件事件提供NotificationCenter,为高级逻辑提供另一个NotificationCenter)。
答案 2 :(得分:1)
我们应该限制NotificationCenter设计,它对代码维护有很多缺点。
NotificationCenter的好处是您不需要知道事件源,因此当同一事件有多个事件源时,您应该只使用NotificationCenter,甚至源数也会改变。
例如,在Windows平台中,您希望监听所有窗口大小已更改的事件,但同时可以创建新窗口。