Click here to Skip to main content
15,867,594 members
Articles / Programming Languages / C++
Tip/Trick

Design of Multiple Observables/Subjects with Multiple Observers

Rate me:
Please Sign up or sign in to vote.
4.67/5 (2 votes)
28 Jul 2013CPOL2 min read 17.7K   5   1
Mutliple observables / subjects with multiple observers in C++

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:

  1. There are numerable subjects either related or unrelated which had to be observed for an event.
  2. Each Observable or subject has numerable observers, which can again be either related or unrelated
  3. I should be able to add new subjects delete some old subjects as part of the software enhancement for , say, every release.
  4. 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

C++
#ifndef __SUBJECTCONTAINER__
#define __SUBJECTCONTAINER__

#include "ISubject.h"
#include "SubjectImpl_1.h"
#include "SubjectImpl_2.h"

class SubjectContainer
{
private:
	ISubject *m_pSub;
public:
	SubjectContainer()
C++
	{
		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   
C++
#ifndef __ISUBJECT__
#define __ISUBJECT__

#include "IObserver.h"
class ISubject // Abstract Subject class
{
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;

	// Test method 
	virtual void Trigger( const type_info&) = 0;
};

#endif
C++
#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
C++
#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
C++
#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
C++
// IObserver interface
#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 
C++
#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
C++
#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
C++
// CLIENT CODE
// crtp.cpp : Defines the entry point for the console application.
//

#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[])
{
	// Create observers. The client holds all the observers 
	// in this case
	IObserver *obs_1 = new ObserverImpl_1;
	IObserver *obs_2 = new ObserverImpl_2;
	IObserver *obs_3 = new ObserverImpl_3;
	
	// Create the subjects. This design of client
	// having the subject is not final. This is
	// just to demonstrate.
	ISubject *sub1 = new SubjectImpl_1;
	ISubject *sub2 = new SubjectImpl_2;
	ISubject *sub3 = new SubjectImpl_3;

	// The Subject container is abstracted from the
	// type of the individual subjects. The container
	// simply has the pointer to the first subject.
	SubjectContainer subContain;

	// Append the subjects to the container. The AttachSubjects()
	// method in the container is responsible to create the chain
	// of responsibility.
	// Please also note that the subject container  does 
	// not in any way refer to the actual implementation.
	subContain.AttachSubjects(sub1);
	subContain.AttachSubjects(sub2);
	subContain.AttachSubjects(sub3);

	// The client is the only responsible scope where the
	// actual events are known: SubjectImpl_1, SubjectImpl_2 etc.,
	// The Subject and the subject container does not have 
	// any idea of what the actual observer is and thus abstracted.
	subContain.AttachObserver<SubjectImpl_1>( obs_1 );
	subContain.AttachObserver<SubjectImpl_2>( obs_2 );
	subContain.AttachObserver<SubjectImpl_3>( obs_3 );

	// Test code to trigger SubjectImpl_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 strings 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
SuggestionAlternative Pin
osy1-Aug-13 23:44
osy1-Aug-13 23:44 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.