Introduction
This article introduces a reusable C++ generic component which can be used in place of observer design pattern. It has the following benefits:
- Eliminates the repeated task of pattern's skeleton implementation.
- Even novice developers, unfamiliar with the observer patterns structure, can use it very easily.
- Code reusability in-turn results in reduced bugs.
- No need for observer classes to be inherited from an
abstract
observer class. - Event subscription can be done in C# style modern syntax.
[Concept Courtesy : From Patterns to Components -Doctoral thesis by Karine Arnout]
Background
Observer design pattern is one of the most used design pattern among the 23 popular design patterns identified by Gang of Four (GoF). The traditional implementation of Observer pattern can be found here.
GoF provides only the design concept of the pattern. In C++, every time you have to make your own implementation of the Subject
& Observer abstract
classes. Also this design push your concrete classes to be inherited from the AbstractSubject
and AbstractObserver
classes to get the patterns advantage.
The C++ component introduced here is more flexible because you don't have to make some inheritance contract to use the observer pattern. It is much more like putting an Event
component in your Subject
class and your Observer
classes can simply subscribe to the Event
object in your Subject
class.
Using the Code
The following code shows the template definition of Event<>
class and an example usage of the Event
template. You cannot use the Event<>
component as a binary component like activeX. Instead you can use it the way you use STL classes.
The main restrictions of using this component are:
- You will have to use the below shown header file in your application.
- This component uses function pointer to give the
Event
callback and the corresponding event handler functions should be either a static
function or a global function. - Though the subscriber objects pointer can be passed as a context information, in the
static
event handler functions, you have to perform the static_cast<>(contextptr)
to get the pointer to the observer object which subscribed for the event.
#ifndef OBSERVER_EVENT_H_
#define OBSERVER_EVENT_H_
#include <vector>
#include <algorithm>
namespace GoFPatterns
{
using namespace std;
template<typename SourceType, typename EventArgType>
class Event
{
protected:
typedef void (*EventHandler)(SourceType *source, EventArgType eventArg,
void* context);
class SubscriberRecord
{
private:
EventHandler handlerProc; void *handlerContext; SubscriberRecord(EventHandler handler, void* context = 0) :
handlerProc(handler), handlerContext(context)
{
}
friend class Event;
};
protected:
vector<SubscriberRecord> Subscribers;
SourceType *eventSource;
public:
Event(SourceType *source) :
eventSource(source)
{
}
virtual ~Event()
{
}
void operator +=(EventHandler handler)
{
Subscribers.push_back(SubscriberRecord(handler));
}
void operator -=(EventHandler handler)
{
typename vector<SubscriberRecord>::iterator itr =
find(Subscribers.begin(),Subscribers.end(),handler);
if( itr != Subscribers.end())
{
Subscribers.erase(itr);
}
}
void Subscribe(EventHandler handler, void* context = 0)
{
Subscribers.push_back(SubscriberRecord(handler, context));
}
void Unsubscribe(EventHandler handler, void* context = 0)
{
typename vector<SubscriberRecord>::iterator itr =
find(Subscribers.begin(),Subscribers.end(),SubscriberRecord(handler, context));
if( itr != Subscribers.end())
{
Subscribers.erase(itr);
}
}
void FireEvent(EventArgType eventArg)
{
for (unsigned int i = 0; i < Subscribers.size(); i++)
{
Subscribers[i].handlerProc(eventSource, eventArg,
Subscribers[i].handlerContext);
}
}
};
}
#endif /* OBSERVER_EVENT_H_ */
#include <iostream>
#include "../GOFLib/Event.h"
#include "GOFExample.h"
using namespace std;
namespace GoFExample
{
class Book
{
private:
Book()
{
}
public:
int bookId;
string bookName;
Book(int id, string name) :
bookId(id), bookName(name)
{
}
};
class Library
{
private:
vector<Book> bookList; Library() :
newBookEvent(this) {
}
public:
string libraryName;
GoFPatterns::Event<Library, Book&> newBookEvent;
Library(string name) :
libraryName(name), newBookEvent(this) {
}
void AddBook(Book newBook)
{
bookList.push_back(newBook);
newBookEvent.FireEvent(newBook);
}
};
class Student
{
public:
int studentId;
string studentName;
private:
Student()
{
}
public:
Student(int id, string name) :
studentId(id), studentName(name)
{
}
void AdviseLibrary(Library& library)
{
library.newBookEvent.Subscribe(onNewBookArrived, this);
}
static void onNewBookArrived(Library *eventSrc, Book &newBook,
void* context)
{
Student* stPtr = static_cast<Student*> (context);
cout << endl << "==========================" << endl
<< "New Book notification" << endl
<< "==========================" << endl << "Book name = "
<< newBook.bookName << endl << "From library = "
<< eventSrc->libraryName << endl;
if (stPtr)
cout << "To Student = " << stPtr->studentName << endl;
cout << "==========================" << endl;
}
};
class College
{
public:
Library phyLib;
Library bioLib;
vector<Student> physicsStudents;
vector<Student> biologyStudents;
College() :
phyLib("Physics"), bioLib("Biology")
{
}
void EnrollStudentForPhysics(Student& newStudent)
{
newStudent.AdviseLibrary(phyLib);
physicsStudents.push_back(newStudent);
}
void EnrollStudentForBiology(Student& newStudent)
{
newStudent.AdviseLibrary(bioLib);
biologyStudents.push_back(newStudent);
}
};
void GlobalNewBookHandler(Library *eventSrc, Book& newBook, void* context)
{
cout << endl << "============================" << endl
<< "Global New Book notification" << endl
<< "============================" << endl << "Book name = "
<< newBook.bookName << endl << "From library = "
<< eventSrc->libraryName << endl;
}
int main()
{
College col1;
col1.bioLib.newBookEvent += GlobalNewBookHandler;
col1.EnrollStudentForBiology(*(new Student(1, "Jibin")));
col1.EnrollStudentForBiology(*(new Student(2, "Vinod")));
col1.EnrollStudentForBiology(*(new Student(3, "Anish")));
col1.EnrollStudentForPhysics(*(new Student(1, "Xavier")));
col1.EnrollStudentForPhysics(*(new Student(2, "Shine")));
col1.bioLib.AddBook(Book(1, "Human brain"));
col1.bioLib.AddBook(Book(2, "Stem cells"));
col1.phyLib.AddBook(Book(1, "Quantum mechanics"));
col1.phyLib.AddBook(Book(2, "Velocity and force"));
col1.phyLib.AddBook(Book(3, "Atom secrets"));
return 0; }
Output
============================
Global New Book notification
============================
Book name = Human brain
From library = Biology
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Jibin
==========================
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Vinod
==========================
==========================
New Book notification
==========================
Book name = Human brain
From library = Biology
To Student = Anish
==========================
============================
Global New Book notification
============================
Book name = Stem cells
From library = Biology
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Jibin
==========================
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Vinod
==========================
==========================
New Book notification
==========================
Book name = Stem cells
From library = Biology
To Student = Anish
==========================
==========================
New Book notification
==========================
Book name = Quantum mechanics
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Quantum mechanics
From library = Physics
To Student = Shine
==========================
==========================
New Book notification
==========================
Book name = Velocity and force
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Velocity and force
From library = Physics
To Student = Shine
==========================
==========================
New Book notification
==========================
Book name = Atom secrets
From library = Physics
To Student = Xavier
==========================
==========================
New Book notification
==========================
Book name = Atom secrets
From library = Physics
To Student = Shine
==========================
Points of Interest
To make this example program, I used the Eclipse CDT IDE with MinGW.
I find it as a pretty good economical tool to have fun with C++.
History
In his doctoral thesis, Karine Arnout has given a sample implementation of this in Eiffel language.
I don't know whether some other better ways to do this are already published. Anyway I hope to improve.
History
- 9th April, 2009: Initial post
- 11th April, 2009: Added header file attachment to article