Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C

Observer Pattern as C++ Component

4.26/5 (13 votes)
14 Apr 2009CPOL2 min read 34.1K   332  
Observer pattern is made as a reusable C++ component

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.

Observer pattern UML Diagram

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.
C++
#ifndef OBSERVER_EVENT_H_
#define OBSERVER_EVENT_H_
#include <vector>
#include <algorithm>
/*
 * An Event component which encapsulates the Observer pattern.
 * Observer pattern is one among popular 23 design patterns listed by Gang of Four (GoF).
 * The Event class's object can be used in a subject class to represent 
 * an event of the subject.
 * Make this object public so that subscribers can easily subscribe to this event
 */
namespace GoFPatterns
{
using namespace std;
template<typename SourceType, typename EventArgType>
class Event
{
protected:
 /*
  * Event handler function pointer definition
  * source   - Subject - the object which fired the event.
  * eventArg  - The event argument
  * context  - Context information, which a subscriber needs to get with an 
  * event notification
  *            Usually, this can be a pointer to the subscriber object itself.
  */
 typedef void (*EventHandler)(SourceType *source, EventArgType eventArg,
   void* context);
 /*
  * This inner class, for each EventHandler, stores the associated context information -
  * pointer. This context pointer can be used to pass additional information
  * from the point of subscription to the event handler function.
  * The best use of context pointer is to use the "this" pointer of subscriber itself.
  */
 class SubscriberRecord
 {
 private:
  EventHandler handlerProc; // The event handler function pointer
  void *handlerContext; // pointer to context information
  SubscriberRecord(EventHandler handler, void* context = 0) :
   handlerProc(handler), handlerContext(context)
  {
  }
  friend class Event;
 };
protected:
 vector<SubscriberRecord> Subscribers;
 SourceType *eventSource;
public:
 /*
  * Constructor - sets the Event source
  */
 Event(SourceType *source) :
  eventSource(source)
 {
 }
 /*
  * Virtual destructor - perform clean up if any.
  */
 virtual ~Event()
 {
 }
 /*
  * Operator used to subscribe a handler C# style event subscription
  */
 void operator +=(EventHandler handler)
 {
  Subscribers.push_back(SubscriberRecord(handler));
 }
 /*
  * Operator used to unsubscribe a handler C# style event subscription
  */
 void operator -=(EventHandler handler)
 {
  typename vector<SubscriberRecord>::iterator itr = 
		find(Subscribers.begin(),Subscribers.end(),handler);
  if( itr != Subscribers.end())
  {
   Subscribers.erase(itr);
  }
 }
 /*
  * Function used to subscribe a handler with optional context information
  */
 void Subscribe(EventHandler handler, void* context = 0)
 {
  Subscribers.push_back(SubscriberRecord(handler, context));
 }
 /*
  * Function used to unsubscribe a handler with optional context information
  */
 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);
  }
 }
 /*
  * Fire the event and notify all observers with event argument, -
  * source and context information if any provided.
  */
 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_ */   
//####################### EXAMPLE USAGE OF EVENT COMPONENT ######################
#include <iostream>
#include "../GOFLib/Event.h"
#include "GOFExample.h"
using namespace std;
namespace GoFExample
{
/* This example shows a Library lending system in a College
 * The College has different Libraries for different disciplines
 * When a Student is enrolled to a discipline, they are advised to join
 * the respective Library.
 * A Library contains a collection of Books.
 * Whenever a new Book is added to the Library, it notifies all members  -
 * through an Event.
 *
 * Classes involved are
 * Book    - has an id and name
 * Library   - has a collection of Books and a newBookEvent
 * Student   - has an id and name, subscribes to a Library 's -
 *       newBookEvent when advised from college.
 * College   - has a collection of students for different disciplines -
 *       and a Library for each discipline
 */
/*
 * A simple Book class
 */
class Book
{
private:
 /*
  * Private constructor to prevent making nameless books
  */
 Book()
 {
 }
public:
 int bookId;
 string bookName;
 Book(int id, string name) :
  bookId(id), bookName(name)
 {
 }
};
/* A typical Library with a collection of books.
 * This library will publish an event to all members whenever a new book is arrived
 */
class Library
{
private:
 vector<Book> bookList; //Collection of books.
 Library() :
  newBookEvent(this) //Set the source of event as the current object
 {
 }
public:
 string libraryName;
 GoFPatterns::Event<Library, Book&> newBookEvent; 	//Declaring an event 
						//which will be fired when a 
				          	//new book is added to library
 /*
  * Library public constructor which initializes library name and the Event
  */
 Library(string name) :
  libraryName(name), newBookEvent(this) //Set the source of event will
            //be the current object
 {
 }
 /*
  * Function to add a new book to library
  */
 void AddBook(Book newBook)
 {
  //Add the book to collection
  bookList.push_back(newBook);
  //Fire the newBookEvent with the newly added book as argument
  newBookEvent.FireEvent(newBook);
 }
};
/*/
 * A Student class used as a library event subscriber
 */
class Student
{
public:
 int studentId;
 string studentName;
private:
 /*
  * private constructor - to prevent making nameless books
  */
 Student()
 {
 }
public:
 Student(int id, string name) :
  studentId(id), studentName(name)
 {
 }
 /*
  * The student gets advised to subscribe for a library so that -
  * she will get notifications when ever new books arrive in library.
  */
 void AdviseLibrary(Library& library)
 {
  //Subscribe to the event and specify the context as this object itself.
  library.newBookEvent.Subscribe(onNewBookArrived, this);
 }
 /*
  * Sample for handling event in a class's static member function
  */
 static void onNewBookArrived(Library *eventSrc, Book &newBook,
   void* context)
 {
  //Cast the context to Student pointer so we can know which object got this event.
  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;
 }
};
/*
 * College class holds many libraries and many students.
 * When a student is enrolled in the college, he is advised to -
 * subscribe to an appropriate library.
 */
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);
 }
};
/*
 * Sample for handling event in a global function
 */
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;
}    
/*
 * Program entry point; main() function
 */
int main()
{
  College col1;
  //Subscribe to newBookEvent in C# style using the += operator
  //Sample for event handling in global functions.
  col1.bioLib.newBookEvent += GlobalNewBookHandler;
  //Enroll Students to college
  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")));
  //Add Books to college libraries
  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

License

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