user2478832
user2478832

Reputation: 561

Pros and cons of a centralized event dispatch

I'm considering different approaches to implementing events in a C++ application. There's a suggestion to implement a centralized event dispatch, via a notification center. An alternative would be for sources and targets of the events to communicate directly. I'm having reservations about the notification center approach, however. I'll outline both approaches as I see them (I could well be misunderstanding something about them, I've never implemented event handling before).

a) Direct communication. Events are a part of their source's interface. Objects interested in the event must somehow get a hold of an instance of the source class and subscribe to its event(s):

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*/ }
};

(From what I understand, boost::signals, Qt signals/slots and .NET events all work like this, but I could be wrong.)

b) Notification center. Events aren't visible in their source's interface. All events go to some notification center, probably implemented as a singleton (any advice on avoiding this would be appreciated), as they are fired. Target objects don't have to know anything about the sources; they subscribe to certain event types by accessing the notification center. Once the notification center receives a new event, it notifies all its subscribers interested in that particular event.

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 {/**/}
};

(I took the term "Notification center" from Poco, which, as far as I understand, implements both approaches).

I can see some pros to this approach; it will be easier for the targets to create their subscriptions because they wouldn't need access to the sources. Also, there wouldn't be any lifetime management problems: unlike sources, the notification center will always outlive the targets, so they targets always unsubscribe in their destructors without worrying whether the source still exists (that's a major con I can see in the direct communication). However, I am afraid that this approach could lead to unmaintainable code, because:

  1. All sorts of events, probably completely unrelated to each other, would go to this one big sink.

  2. The most obvious way of implementing the notification center is as a singleton, so it will be hard to track who and when modifies the subscribers' list.

  3. Events aren't visible in any interface, so there is no way to see whether a particular event belongs to any source at all.

As a result of these cons, I'm afraid that as the application grows it would become very difficult to track connections between objects (I'm imagining problems trying to understand why some particular event doesn't fire, for example).

I'm looking for advice regarding the pros and cons of the "notification center" approach. Is it maintainable? Does it fit every sort of applications? Maybe there are ways to improve the implementation? Comparison between the two approaches I described, as well as any other event handling suggestions, are most welcome.

Upvotes: 5

Views: 1736

Answers (3)

Daniele Pallastrelli
Daniele Pallastrelli

Reputation: 2552

I will focus on pros and cons of the "NotificationCenter" solution as required by the question (though I'd call it DataBus, Dispatcher or Publisher/Subscriber, instead).

Pros:

  • High decoupling of the classes
  • The Subscriber of an event doesn't need to know the source of the event
  • Concurrency management is simplified if you use an asynchronous NotificationCenter (you can avoid multithreading by using the NotificationCenter as a scheduler of cpu time).
  • An event driven architecture is higly testable / observable

Cons:

  • The interface is implicit (i.e. the interface of a class does not expose the events)
  • The partitioning of the application is critical, in order to avoid duplication of the state in the classes
  • Reusability in another application becomes harder (when you want to reuse a class in another application, it brings with itself the NotificationCenter)
  • It's difficult to insert a new elaboration in the flow of events from input to output (i.e.: if class A emits event E and class B registers for notifications of E, you cannot insert a class C "between" A and B to change the content of E let's say to do some kind of elaboration on it)
  • The NotificationCenter must manage client's errors (i.e. the NotificationCenter should provide an exception handler to catch the exeptions thrown in the event handlers).

In addition, I'd like to point out that it's not indispensable that the NotificationCenter be a singleton. In fact you can have multiple instances of NotificationCenter, each managing different category of events (e.g. in an embedded system you can have a NotificationCenter for the low level hardware events and another for the high level logic).

Upvotes: 2

ZijingWu
ZijingWu

Reputation: 3490

We should limited the NotificationCenter design, it has a lot of cons for code maintain.

  1. It hidden the object relation ship into the implementation, you cannot get clue from caller code, who actually will explicit bind the observer and target object in Direct communication.
  2. You can not see the even interface from the header, you need to look into implementation.
  3. You have bind your business logical with the notification center, you cannot easily unit-test the business component and reuse it in other project.

The benifit of NotificationCenter is that you doesn't need to know the event source, so you should only use NotificationCenter when there is multiple event source for same event, or even source number will change.

For example, in Windows platform you want to listen all windows size changed event, but at the same time the new window can be created.

Upvotes: 1

Krzysztof Kosiński
Krzysztof Kosiński

Reputation: 4325

These approaches are orthogonal. Direct communication should be used when the events are exchanged between specific objects. The notification center approach should only be used for broadcast events, e.g. when you want to process all events of a given type regardless of their source, or when you want to send an event to some set of objects which you don't know in advance.

To avoid singletons, reuse the code for direct communication and subscribe the notification center object to all events you want to process in this way. To keep things manageable, you would do this in the emitting objects.

The lifetime-related problems with direct communication are usually solved by requiring that every class that subscribes to any events must be derived from a specific base class; in Boost.Signals, this class is called trackable. The equivalent of your CreateDelegate function stores the information about subscriptions for the given object in data member within trackable. On destruction of trackable, all subscriptions are cancelled by calling the matching Unsubscribe functions. Note that this is not thread-safe, because the destructor of trackable is called only after the derived class destructor finishes - there is a period where a partially destroyed object can still receive events.

Upvotes: 4

Related Questions