Introduction
The following tip explains the basic structure of the behavioral design pattern to effectively handle the problem of multiple observers on multiple observables.
Background
While there are many examples of a subject/observable being observed by many observers, I could not find a design solution for multiple OBSERVABLES/SUBJECTS, which are again observed by multiple OBSERVERS. I had thought about it and immediately arrived at a design solution which uses chain of responsibility and observer patterns.
Using the Code
I have the following problem:
- There are numerable subjects either related or unrelated which had to be observed for an event.
- Each Observable or subject has numerable observers, which can again be either related or unrelated
- I should be able to add new subjects delete some old subjects as part of the software enhancement for , say, every release.
- I should be able to add /remove observers.
So the problem is that I cannot have multiple individual instantiations of the subjects in any class and I should provide a basic framework wherein the developer should create new subject, attach new observers.
Solution
The subjects are contained by SubjectContainer
and they are linked to by a linked list. Individual instantiations of the subjects in the container is avoided but the pointer to the head of the linked list is stored as a pointer to the interface of the subject. The SubjectContainer
class does not know the implementation types of the subjects. The subjects can be added to the linked chain of responsibility.
Second is the AttachObserver()
template method, which takes the Implementation type. Note that the implementation type is the explicit type we need to know. This is the actual observable. Like, if it is a connection notifier, we need to know the exact type of the implementation. To us, it is more of the actual event than "implementation". This will be understood by going through the client (main) code.
Third is the TriggerSubject()
, a test method to trigger the event,
Legends
Class ISubject is abstract
. Three implementations SubjectImpl_1, SubjectImpl_2 and, SubjectImpl_3 are defined for ISubject.
Class IObserver is abstract
. Three implementations ObserverImpl_1
, ObserverImpl_2 and , ObserverImpl_3 are defined for IObserver
#ifndef __SUBJECTCONTAINER__
#define __SUBJECTCONTAINER__
#include "ISubject.h"
#include "SubjectImpl_1.h"
#include "SubjectImpl_2.h"
class SubjectContainer
{
private:
ISubject *m_pSub;
public:
SubjectContainer()
{
m_pSub = NULL;
}
int AttachSubjects( ISubject *sub )
{
if ( !sub )
return 1;
if ( !m_pSub )
m_pSub = sub;
else if ( m_pSub && m_pSub != sub )
return m_pSub->ExtendChain( sub );
return 0;
}
template <class ActualEvent>
int AttachObserver( IObserver *obs )
{
const type_info& actual_event_type = typeid(ActualEvent);
return (m_pSub->AttachObserver( obs, actual_event_type) );
}
template <class ActualEvent>
void TriggerSubject()
{
const type_info& actual_event_type = typeid(ActualEvent);
m_pSub->Trigger( actual_event_type );
}
};
#endif
#ifndef __ISUBJECT__
#define __ISUBJECT__
#include "IObserver.h"
class ISubject {
protected :
ISubject *m_next;
public:
ISubject()
{
m_next = NULL;
}
virtual int ExtendChain( ISubject *sub )
{
if ( m_next )
return m_next->ExtendChain( sub );
else
m_next = sub;
return 0;
}
virtual int AttachObserver( IObserver *obs, const type_info& classInfo ) = 0;
virtual void NotifyObservers() = 0;
virtual void Trigger( const type_info&) = 0;
};
#endif
#ifndef __SUBJECTIMPL_1__
#define __SUBJECTIMPL_1__
#include <vector>
#include "ISubject.h"
class SubjectImpl_1 : public ISubject
{
std::vector<IObserver*> m_obsList;
public:
SubjectImpl_1()
{
m_obsList.reserve(10);
}
int AttachObserver( IObserver *obs, const type_info& classInfo )
{
if ( typeid(*this) == classInfo )
m_obsList.push_back( obs );
else if ( m_next )
m_next->AttachObserver( obs, classInfo );
else
return 1;
return 0;
}
void NotifyObservers()
{
void *data = NULL;
for ( int ii=0; ii<m_obsList.size(); ii++ )
m_obsList[ii]->UpdateAction(data);
}
void Trigger( const type_info& class_type_info )
{
if ( typeid(*this) == class_type_info )
NotifyObservers();
else if ( m_next )
m_next->Trigger( class_type_info );
else
return;
}
};
#endif
#ifndef __SUBJECTIMPL_2__
#define __SUBJECTIMPL_2__
#include <vector>
#include "ISubject.h"
class SubjectImpl_2 : public ISubject
{
std::vector<IObserver*> m_obsList;
public:
SubjectImpl_2()
{
m_obsList.reserve(10);
}
int AttachObserver( IObserver *obs, const type_info& classInfo )
{
if ( typeid(*this) == classInfo )
m_obsList.push_back( obs );
else if ( m_next )
m_next->AttachObserver( obs, classInfo );
else
return 1;
return 0;
}
void NotifyObservers()
{
void *data = NULL;
for ( int ii=0; ii<m_obsList.size(); ii++ )
m_obsList[ii]->UpdateAction(data);
}
void Trigger( const type_info& class_type_info )
{
if ( typeid(*this) == class_type_info )
NotifyObservers();
else if ( m_next )
m_next->Trigger( class_type_info );
else
return;
}
};
#endif
#ifndef __SUBJECTIMPL_3__
#define __SUBJECTIMPL_3__
#include <vector>
#include "ISubject.h"
class SubjectImpl_3 : public ISubject
{
std::vector<IObserver*> m_obsList;
public:
SubjectImpl_3()
{
m_obsList.reserve(10);
}
int AttachObserver( IObserver *obs, const type_info& classInfo )
{
if ( typeid(*this) == classInfo )
m_obsList.push_back( obs );
else if ( m_next )
m_next->AttachObserver( obs, classInfo );
else
return 1;
return 0;
}
void NotifyObservers()
{
void *data = NULL;
for ( int ii=0; ii<m_obsList.size(); ii++ )
m_obsList[ii]->UpdateAction(data);
}
void Trigger( const type_info& class_type_info )
{
if ( typeid(*this) == class_type_info )
NotifyObservers();
else if ( m_next )
m_next->Trigger( class_type_info );
else
return;
}
};
#endif
#ifndef __IOBSERVER__
#define __IOBSERVER__
class IObserver
{
public:
virtual int UpdateAction( void* data ) = 0;
};
#endif
#ifndef __OBSERVERIMPL_1__
#define __OBSERVERIMPL_1__
#include "IObserver.h"
#include <iostream>
class ObserverImpl_1:public IObserver
{
public:
int UpdateAction( void* data ) { std::cout<<
"Obserer impl1 notified"<<std::endl; return 0; }
};
#endif
#ifndef __OBSERVERIMPL_2__
#define __OBSERVERIMPL_2__
#include "IObserver.h"
#include <iostream>
class ObserverImpl_2:public IObserver
{
public:
int UpdateAction( void* data ) { std::cout<<"Obserer impl2 notified"<<std::endl; return 0;}
};
#endif
#ifndef __OBSERVERIMPL_3__
#define __OBSERVERIMPL_3__
#include "IObserver.h"
#include <iostream>
class ObserverImpl_3:public IObserver
{
public:
int UpdateAction( void* data ) { std::cout<<"Obserer impl3 notified"<<std::endl; return 0;}
};
#endif
#include "stdafx.h"
#include "ObserverImpl_1.h"
#include "ObserverImpl_2.h"
#include "ObserverImpl_3.h"
#include "SubjectContainer.h"
#include "SubjectImpl_1.h"
#include "SubjectImpl_2.h"
#include "SubjectImpl_3.h"
int _tmain(int argc, _TCHAR* argv[])
{
IObserver *obs_1 = new ObserverImpl_1;
IObserver *obs_2 = new ObserverImpl_2;
IObserver *obs_3 = new ObserverImpl_3;
ISubject *sub1 = new SubjectImpl_1;
ISubject *sub2 = new SubjectImpl_2;
ISubject *sub3 = new SubjectImpl_3;
SubjectContainer subContain;
subContain.AttachSubjects(sub1);
subContain.AttachSubjects(sub2);
subContain.AttachSubjects(sub3);
subContain.AttachObserver<SubjectImpl_1>( obs_1 );
subContain.AttachObserver<SubjectImpl_2>( obs_2 );
subContain.AttachObserver<SubjectImpl_3>( obs_3 );
subContain.TriggerSubject<SubjectImpl_3>();
return 0;
}
Points of Interest
Debatable point: Usage of type_info
: If not used, the attachObserver
call has to be made using a type string
. Management of string
s or for that matter any other data to find out the responsible implementation would lead to the developer error when by mistake a data which does not have any info regarding the implementation is passed. This is avoided by safe typechecking using templates. If the actual implementation class is not known apriori, compiler error will be generated.
I also feel that this is just a basic design. I am interested to know a better implementation for this kind of pattern where policy template classes could be implemented for both: chain of responsibility and observers. I look forward to have improvements to my design by gurus.