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