Home » C++ » Templated implementation of the Observer Pattern

Templated implementation of the Observer Pattern

The purpose of this template is to simplify the creation of classes that implement the Observer Pattern and attempt to eliminate certain runtime errors by turning them into compile time errors. The pattern consists of two template classes. Observer and Observable.

Observable

The class derived from this will be observed by classes derived from Observer. It keeps a list of registered observers and will notify all of them with the raised notifications.
It is parameterised by a supplied observer type.

void AddObserver(TObserver& observer)
Registers an observer with the observable class.

void RemoveObserver(TObserver& observer)
Unregisters an observer.

void ClearObservers()
Unregister all of the observers.

size_type NumberOfObservers()
Gets the number of registered observers.

template <typename TNotification>
void NotifyObservers(TNotification n)

Notifies all of the registered observers with the notification ‘n’.

  template <typename TObserver>
  class observable
  {
  public:

    typedef size_t size_type;

    typedef std::vector<TObserver*> ObserverList;

    //*****************************************************************
    /// Add an observer to the list.
    //*****************************************************************
    void AddObserver(TObserver& observer)
    {
		  // See if we already have it in our list.
      typename ObserverList::const_iterator i_observer = std::find(observer_list.begin(),
                                                                   observer_list.end(),
                                                                   &observer);

		  // Not there?
      if (i_observer == observer_list.end())
      {
        // Add it.
        observer_list.push_back(&observer);
      }
    }

    //*****************************************************************
    /// Remove a particular observer from the list.
    //*****************************************************************
    void RemoveObserver(TObserver& observer)
    {
      // See if we have it in our list.
      typename ObserverList::iterator i_observer = std::find(observer_list.begin(),
                                                             observer_list.end(),
                                                             &observer);

      // Found it?
      if (i_observer != observer_list.end())
      {
        // Erase it.
        observer_list.erase(i_observer);
      }
    }

    //*****************************************************************
    /// Clear all observers from the list.
    //*****************************************************************
    void ClearObservers()
    {
      observer_list.clear();
    }

    //*****************************************************************
    /// Returns the number of observers.
    //*****************************************************************
    size_type NumberOfObservers() const
    {
      return observer_list.size();
    }

    //*****************************************************************
    /// Notify all of the observers, sending them the notification.
    /// TNotification is the notification type.
    //*****************************************************************
    template <typename TNotification>
    void NotifyObservers(TNotification n)
    {
      for (size_t i = 0; i < observer_list.size(); ++i)
      {
        observer_list[i]->Notification(n);
      }
    }

  private:

    /// The list of observers.
    ObserverList observer_list;
  };

Observer

This template may take up to N notification types.
Each notification type will generate a pure virtual ‘notification’ function. The class that inherits from this must define all of the ‘Notification’ function overloads otherwise the object will remain abstract and will not compile. This ensures that no notification overload can be omitted.

The example below gives the derived observer class the capacity to handle four notification types. A script may be used to generate code that can handle any number of types.
The method that the notification parameter is passed to the notification handler is specified in the template type parameter.
i.e. If the notification is to be passed as const reference to a strut called ‘Info’ then the template type parameter would be declared as const Info&.

//*********************************************************************
  /// The Observer interface for four notification types.
  ///\ingroup Observer
  //*********************************************************************
  template <typename T1, typename T2  = void, typename T3  = void, typename T4  = void>
  class Observer
  {
  public:
    virtual ~Observer() {}
    virtual void Notification(T1) = 0;
    virtual void Notification(T2) = 0;
    virtual void Notification(T3) = 0;
    virtual void Notification(T4) = 0;
  };

  //*********************************************************************
  /// The Observer interface for three notification types.
  ///\ingroup Observer
  //*********************************************************************
  template <typename T1, typename T2, typename T3>
  class Observer<T1, T2, T3>
  {
  public:

    virtual ~Observer() {}
    virtual void Notification(T1) = 0;
    virtual void Notification(T2) = 0;
    virtual void Notification(T3) = 0;
  };

  //*********************************************************************
  /// The Observer interface for two notification types.
  ///\ingroup Observer
  //*********************************************************************
  template <typename T1, typename T2>
  class Observer<T1, T2>
  {
  public:

    virtual ~Observer() {}
    virtual void Notification(T1) = 0;
    virtual void Notification(T2) = 0;
  };

  //*********************************************************************
  /// The Observer interface for one notification type.
  ///\ingroup Observer
  //*********************************************************************
  template <typename T1>
  class Observer<T1>
  {
  public:

    virtual ~Observer() {}
    virtual void Notification(T1) = 0;
  };

In use

First we define three different notification types.

//*****************************************************************************
// Notification1
//*****************************************************************************
struct Notification1
{
  // Some data.
};

//*****************************************************************************
// Notification2
//*****************************************************************************
struct Notification2
{
  // Some data.
};

//*****************************************************************************
// Notification3
//*****************************************************************************
struct Notification3
{
  // Some data.
};

Next we declare the base observer type.
Note that Notification1 is passed by value, Notification2 is passed by reference and Notification3 is passed by const reference.

//*****************************************************************************
// The observer base type.
// Declare what notifications you want to observe and how they are passed to 'notification'.
// Notification1 is passed by value.
// Notification2 is passed by reference.
// Notification3 is passed by const reference.
//*****************************************************************************
typedef Observer<Notification1, Notification2&, const Notification3&> ObserverType;

Now we define an observable class that that can be observed by classes derived from ObserverType

//*****************************************************************************
// The concrete observable class.
//*****************************************************************************
class Observable1 : public Observable<ObserverType>
{
public:
  
  Notification1 data1;
  Notification2 data2;
  Notification3 data3;

  //*********************************
  // Notify all of the observers.
  //*********************************
  void SendNotifications()
  {
    notify_observers(data1);
    notify_observers(data2);
    notify_observers(data3);
  }
};

Let’s now define two observer classes. Both are able to accept notifications Notification1, Notification2 and Notification3.

//*****************************************************************************
// The first observer type.
// If any one of the overloads is missing or a parameter declaration is incorrect
// then the class will be 'abstract' and will not compile.
//*****************************************************************************
class Observer1 : public ObserverType
{
public:

  //*******************************************
  // Notification1 is passed by value.
  //*******************************************
  void notification(Notification1 n)
  {
    // Do something with n.
  }

  //*******************************************
  // Notification2 is passed by reference.
  //*******************************************
  void notification(Notification2& n)
  {
    // Do something with n.
  }

  //*******************************************
  // Notification3 is passed by const reference.
  //*******************************************
  void notification(const Notification3&)
  {
    // Do something with n.
  }
};

//*****************************************************************************
// The second observer type.
// If any one of the overloads is missing or a parameter declaration is incorrect
// then the class will be 'abstract' and will not compile.
//*****************************************************************************
class Observer2 : public ObserverType
{
public:

  //*******************************************
  // Notification1 is passed by value.
  //*******************************************
  void notification(Notification1 n)
  {
    // Do something with n.
  }

  //*******************************************
  // Notification2 is passed by reference.
  //*******************************************
  void notification(Notification2& n)
  {
    // Do something with n.
  }

  //*******************************************
  // Notification3 is passed by const reference.
  //*******************************************
  void notification(const Notification3&)
  {
    // Do something with n.
  }
};

Now the test code.

// The observable objects.
Observable1 observable1;

// The observer objects.
Observer1 observer1;
Observer2 observer2;

observable1.AddObserver(observer1);
observable1.AddObserver(observer2);

// Send the notifications.
observable1.SendNotifications();

A version of this framework can be found as part of the Embedded Template Library, an open source C++ library distributed under the MIT licence.

John Wellbelove

John Wellbelove

Director of Aster Consulting Ltd
United Kingdom
I have been involved in technology and computer systems for all of my working life and have amassed considerable knowledge of designing and implementing systems that are both performant and correct. My role normally encompasses the entire project life-cycle, from specification to maintenance phase. Most systems I have worked on have required high speed and deterministic performance, often within a highly constrained platform. I am experienced in designing and adapting algorithms to solutions that are both space and time efficient, avoiding the normal overheads of standard solutions. Acting as a mentor for colleagues has often been a significant, though unofficial, part of my role.

I administer an open source project on Github.
See http://www.etlcpp.com/

Leave a comment

Your email address will not be published. Required fields are marked *