Friday, January 23, 2009

Delegation pattern in C++. My recipe.

Look into example code:
#include "DelegationPattern.h"
#include <iostream>
using namespace std;


class Notifier : public INotifier
{
public:
 void NotifyAll(int j)
 {
  std::vector<IDelegate*>::iterator i = _delegates.begin(), iEnd = _delegates.end();
  for (; i != iEnd; i++)
  {
   (*i)->Invoke(j++);
  }
 }
};


class A : ISubscriber<A>
{
public:
 void SubscribeAll(INotifier* notifier) { Subscribe(notifier, &A::ma); Subscribe(notifier, &A::mb);};
 void UnsubscribeAll(INotifier* notifier) { Unsubscribe(notifier, &A::ma); Unsubscribe(notifier, &A::mb);};
 void UnsubscribeSecond(INotifier* notifier) { Unsubscribe(notifier, &A::mb);};
 void SubscribeFirst(INotifier* notifier) { Subscribe(notifier, &A::ma);};
protected:
 void ma(int i) { cout << "A::ma(" << i << ") has been called" << endl;};
 void mb(int i) { cout << "A::mb(" << i << ") has been called" << endl;};
};

class B : ISubscriber<B>
{
 int _id;
public:
 B(int id) : _id(id) {}; 
 void SubscribeAll(INotifier* notifier) { Subscribe(notifier, &B::ma);};
 void UnsubscribeAll(INotifier* notifier) { Unsubscribe(notifier, &B::ma);};
 void ma(int i) { cout << "B(" << _id << ")::ma(" << i << ") has been called" << endl;};
};


int main()
{
 Notifier notifier;
 A a;
 
 {
  B b1(1);
  B b2(2);

  a.SubscribeAll(¬ifier);
  notifier.NotifyAll(3);
  b1.SubscribeAll(¬ifier);
  notifier.NotifyAll(10);
  a.SubscribeFirst(¬ifier);
  a.UnsubscribeSecond(¬ifier);
  a.UnsubscribeSecond(¬ifier);
  b2.SubscribeAll(¬ifier);
  notifier.NotifyAll(21);
 }
 notifier.NotifyAll(30);
}



Notifier is events source. It must be single instance and process all events.
Suppose you have 2 independent classes A and B, which want to receive some events from notifier. To achieve this goal they inherit ISubscriber interface and define inside themselves simple subscribe/unsubscribe methods. That's all. Program results are:
A::ma(3) has been called
A::mb(4) has been called
A::ma(10) has been called
A::mb(11) has been called
B(1)::ma(12) has been called
A::ma(21) has been called
B(1)::ma(22) has been called
B(2)::ma(23) has been called
A::ma(30) has been called


Here is the code that covers presented interfaces (DelegationPattern.h):
#ifndef _DELEGATION_PATTERN_INTERFACES_SVOLKOV_
#define _DELEGATION_PATTERN_INTERFACES_SVOLKOV_


#include <vector>
#include <algorithm>
class IDelegate;


///Simple notifier interface.
class INotifier
{
public:
 void Subscribe(IDelegate* delegate) { _delegates.push_back(delegate);};
 void Unsubscribe(IDelegate* delegate) { _delegates.erase(std::find(_delegates.begin(), _delegates.end(), delegate));};

protected:
 std::vector<IDelegate*> _delegates;
};


/** Subscriber interface.
@remarks 
Any class can become subscriber to register own methods as callbacks.
*/
template <class T>
class ISubscriber
{
public:
 ///Destructor.
 virtual ~ISubscriber() = 0;
 ///Accepted arguments list for target callback method.
 typedef void (T::*CallbackMethod) (int);

 /** Register new delegate.
 @remarks Actually any other arguments can be added here to distinguish event type to subscribe etc.
 */
 void Subscribe(INotifier* notifier, typename ISubscriber<T>::CallbackMethod method);
 /** Unregister delegate.
 */
 void Unsubscribe(INotifier* notifier, typename ISubscriber<T>::CallbackMethod method);

protected:
 std::vector<std::pair<INotifier*, IDelegate*> > _delegates;
};


/** Delegate object interface.
*/
class IDelegate
{
public:
 ///Call.
 virtual void Invoke(int) = 0;
};


/** Delegate object with callback method pointer.
*/
template <class T>
class MethodDelegate : public IDelegate
{
 template <typename U> friend class ISubscriber;
protected:
 T* _object;
 typename ISubscriber<T>::CallbackMethod _method;
public:
 ///Constructor.
 MethodDelegate(T* object, typename ISubscriber<T>::CallbackMethod method)
  : _object(object), _method(method) {};
 ///Call.
 void Invoke(int);
};


template<class T>
void MethodDelegate<T>::Invoke(int i)
{
 (_object->*_method)(i);
}


template <class T>
ISubscriber<T>::~ISubscriber<T>()
{
 std::vector<std::pair<INotifier*, IDelegate*> >::iterator i = _delegates.begin(), iEnd = _delegates.end();
 for (; i != iEnd; i++)
 {
  i->first->Unsubscribe(i->second);
  delete(i->second);
 }
}


template <class T>
void ISubscriber<T>::Subscribe(INotifier* notifier, typename ISubscriber<T>::CallbackMethod method)
{
 std::vector<std::pair<INotifier*, IDelegate*> >::iterator i = _delegates.begin(), iEnd = _delegates.end();
 for (; i != iEnd; i++)
 {
  if (i->first == notifier && ((MethodDelegate<T>*)i->second)->_method == method)
   return;//already subscribed
 }
 IDelegate* delegate = new MethodDelegate<T>((T*)this, method);
 _delegates.push_back(std::make_pair(notifier, delegate));
 notifier->Subscribe(delegate);
}


template <class T>
void ISubscriber<T>::Unsubscribe(INotifier* notifier, typename ISubscriber<T>::CallbackMethod method)
{
 std::vector<std::pair<INotifier*, IDelegate*> >::iterator i = _delegates.begin(), iEnd = _delegates.end();
 for (; i != iEnd; i++)
 {
  if (i->first == notifier && ((MethodDelegate<T>*)i->second)->_method == method)
  {
   i->first->Unsubscribe(i->second);
   delete i->second;
   _delegates.erase(i);
   return;
  }
 }
}


#endif



Make attention that it's only possible to define callback method with fixed arguments here (void method(int)), however it must be enough. In my program I used one (void method(object*)). 
Also note that it's single-threaded solution. I'm going to post multi-threaded version later.

No comments:

Post a Comment