Luke
Luke

Reputation: 560

Best design pattern for event listeners

Using C++ & MFC I've created a class which makes it easier to add drag/drop functionality to a CWnd object. There is nothing special about it really. Currently it is used like so:

  1. Create CDropListener object
  2. Call a method on the CDropListener object saying which file extension you want it to react to and a function pointer of what to call when a file is dropped
  3. Register this with the CWnd object
  4. Delete the CDropListener object when the window is destroyed
  5. Repeat all of the above steps if you want different file extensions for a different CWnd

It is a bit cumbersome having to create a class member variable for each listener, and I was just wondering which design pattern would be more suitable for this. I only need the member objects so that I can delete them at the end. I thought I could just use an array to store them in and it would simplify it a bit, but I also thought there might be a better way where you can just call a static function similar to DropListener::RegisterListener(CWnd* wnd, CString& extension, void(*callback) callback) and it handles all of the creating / registering / deleting for you.

Upvotes: 1

Views: 5140

Answers (1)

Dennis
Dennis

Reputation: 3731

I'm not familiar with MFC but from an OO point of view your design can be improved.

First identify what aspects of your requirements are most likely to change and then identify what the interfaces needed to isolate those changes are:

Changes:

  • The thing you want to listen for (the event)
  • The action you want to take (the callback)

Interfaces:

  • The mechanism of adding a callback related to an event to the event notifier
  • The mechanism of calling the callback from the notifier

So you need an Event interface, a Callback interface, and a Notifier interface.

In C++ there is a handy thing called std::function<T> where T is any callable type (a pointer to a function, a functor, a lambda). So you should probably use that for encapsulating your callbacks to give your user more freedom.

Then how may event types do you want to support? This will tell you whether you need to support different Event objects as well as how your registration will look:

// For example if you support just `Drop` events:
void addDropListener(std::function<T> callback);

// If you support many events:
void addListener(Event::Type evType, std::function<T> callback);

Once you have answered that you need to decide what a "callback" looks like (T in the above examples). This could return a value (if you want success verification) or throw a particular exception type (make sure to document the contract). Then ask if you want a copy of the event that was fired (usually you would). Assuming you are happy to only be notified of errors via exceptions then you can typedef the expected std::function like this:

typedef std::function<void (const Event&)> EventCallback;

I recommend then that your Notifier implementer use a std::vector<EventCallback> or std::map<Event::Type, std:vector<EventCallback>. The first one is useful if you want to only support one event type or to call all listeners for all events. The second is handy when you want to only notify listeners of certain types of event.

In any case if you are finding that you need to change your class code to accommodate minor changes to behaviour then you need some refactoring.

Hope that helped. :)

Upvotes: 5

Related Questions