5

我正在考虑在 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 信号/插槽和 .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 取了“通知中心”这个词,据我所知,它实现了这两种方法)。

我可以看到这种方法的一些优点;目标创建订阅会更容易,因为他们不需要访问源。此外,不会有任何生命周期管理问题:与源不同,通知中心总是比目标更长寿,因此它们的目标总是在其析构函数中取消订阅,而不用担心源是否仍然存在(这是我可以直接看到的一个主要问题沟通)。但是,我担心这种方法可能会导致无法维护的代码,因为:

  1. 各种各样的事件,可能彼此完全无关,都会进入这个大水槽。

  2. 实现通知中心最明显的方式是作为单例,因此很难跟踪谁以及何时修改了订阅者列表。

  3. 事件在任何界面中都不可见,因此根本无法查看特定事件是否属于任何源。

由于这些缺点,我担心随着应用程序的增长,跟踪对象之间的连接将变得非常困难(例如,我正在想象试图理解为什么某些特定事件不会触发的问题)。

我正在寻找有关“通知中心”方法优缺点的建议。是否可维护?它适合各种应用吗?也许有办法改进实施?我所描述的两种方法之间的比较,以及任何其他事件处理建议,都是非常受欢迎的。

4

3 回答 3

4

这些方法是正交的。在特定对象之间交换事件时应使用直接通信。通知中心方法应该只用于广播事件,例如当你想处理给定类型的所有事件而不管它们的来源,或者当你想将一个事件发送到你事先不知道的一组对象时.

为避免单例,请重用代码进行直接通信,并将通知中心对象订阅到您要以这种方式处理的所有事件。为了使事情易于管理,您将在发射对象中执行此操作。

与直接通信的生命周期相关的问题通常通过要求订阅任何事件的每个类都必须从特定的基类派生来解决;在 Boost.Signals 中,这个类被称为trackable. 您的函数的等价物CreateDelegate将有关给定对象的订阅信息存储在 内的数据成员中trackable。在销毁 时trackable,通过调用匹配Unsubscribe函数取消所有订阅。请注意,这不是线程安全的,因为trackable只有在派生类析构函数完成后才会调用析构函数——在一段时间内,部分销毁的对象仍然可以接收事件。

于 2013-09-01T18:34:14.110 回答
2

我将根据问题的要求关注“”解决方案的优缺点NotificationCenter(尽管我将其称为DataBus, Dispatcheror Publisher/Subscriber)。

优点

  • 类的高度解耦
  • 事件的订阅者不需要知道事件的来源
  • 如果您使用异步,并发管理会被简化(您可以通过使用作为 cpu 时间的调度程序NotificationCenter来避免多线程)。NotificationCenter
  • 事件驱动的架构是高度可测试/可观察的

缺点

  • 接口是隐式的(即类的接口不暴露事件)
  • 应用程序的分区很关键,以避免在类中重复状态
  • 在另一个应用程序中的可重用性变得更加困难(当您想在另一个应用程序中重用一个类时,它会自带NotificationCenter.
  • 很难在从输入到输出的事件流中插入新的详细说明(即:如果类发出事件A并且类E注册B以通知详细说明)ECABE
  • 必须管理客户端的NotificationCenter错误(即NotificationCenter应该提供一个异常处理程序来捕获事件处理程序中抛出的异常)。

另外,我想指出的是,NotificationCenter 不是必须是单例的。事实上,您可以有多个 NotificationCenter 实例,每个实例管理不同类别的事件(例如,在嵌入式系统中,您可以有一个 NotificationCenter 用于低级硬件事件,另一个用于高级逻辑)。

于 2013-09-03T07:23:56.293 回答
1

我们应该限制 NotificationCenter 的设计,它有很多代码维护的缺点。

  1. 它将对象关系隐藏到实现中,您无法从调用者代码中获得线索,调用者实际上将在直接通信中显式绑定观察者和目标对象。
  2. 您无法从标题中看到偶数接口,您需要查看实现。
  3. 您已将业务逻辑与通知中心绑定,您无法轻松地对业务组件进行单元测试并在其他项目中重用它。

NotificationCenter的好处是不需要知道事件源,所以只有在同一事件有多个事件源时才使用NotificationCenter,否则源数会发生变化。

例如,在 Windows 平台上,您想监听所有窗口大小更改事件,但同时可以创建新窗口。

于 2013-09-02T04:59:02.687 回答