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

Implementing a Subject/Observer Pattern with Templates

Rate me:
Please Sign up or sign in to vote.
4.71/5 (34 votes)
10 Mar 20034 min read 174K   1.1K   59   24
Using C++ Templates to overcome some of the original Subject/Observer design pattern problems

Introduction

The traditional Subject/Observer pattern as described in the famous Design Patterns book has one annoying disadvantage: the observer does not get a handle to the notifying subject in its update method. This means that:

  • The observer needs to store a pointer to the subject to which it is attached.
  • If the observer is attached to multiple objects, it has no way of determining which one of them is notifying him.

This article tries to solve these problems by making use of C++ templates.

The Original Design Pattern

The original design pattern makes use of two base classes: Subject and Observer. The Subject base class contains all the logic of storing all the attached observers. The Observer class just contains a pure virtual method (update()), that needs to be filled in by the inheriting observer class. Please read the 'Design Patterns' book for all details. The update() method of the Observer class does not get any parameters, which means that a class that inherits from Observer does not know where the notification came from. It is not difficult to add a 'Subject' parameter to the update method, but since the real subject inherited from the 'Subject' base class, the observing class always needs to perform a down-cast, which could be dangerous.

The Solution

Instead of defining two base classes, we will define two template classes. Both template classes will be based on the subject-class that is able to notify other classes (the observers).

C++
template <class T>
class Observer
   {
   public:
      Observer() {}
      virtual ~Observer() {}
      virtual void update(T *subject)= 0;
   };

The first enhancement here is that our pure virtual update method gets a pointer to the subject as argument; not the base Subject class (which is shown hereafter), but the class that was given as parameter to the template definition.

C++
template <class T>
class Subject
   {
   public:
      Subject() {}
      virtual ~Subject() {}
      void attach (Observer<T> &observer)
         {
         m_observers.push_back(&observer);
         }
      void notify ()
         {
         std::vector<Observer<T> *>::iterator it;
         for (it=m_observers.begin();it!=m_observers.end();it++) 
              (*it)->update(static_cast<T *>(this));
         }
   private:
      std::vector<Observer<T> *> m_observers;
   };

Here, we defined the basic Subject class/template. The attach method simply adds the observer (which is of the basic Observer<T> class) to a vector. The notify method simply notifies all observers. Both templates can be used in any situation where the Subject/Observer pattern can be used. The following classes describe how they are used.

C++
class Temperature : public Subject<Temperature>
   {
   public:
      Temperature() {}
      ~Temperature() {}
      void temperatureChanged () {notify();}
      void getTemperature() {std::cout << 
         "   Getting the temperature." << std::endl;}
   };

Our Temperature class is a class that monitors the temperature, and notifies its observers when the temperature changes. As you can see, all it has to do is call the notify() method. The getTemperature method simply writes something on the screen, but of course in real-life situations, it should return the actual temperature. Taking a look at the implementation of the notify() method. It simply calls the update() method of all attached observers, but with itself as argument. Since 'this' (which is the Subject<t> class) is cast to the type T, the update() method of the observer will get the correct argument type, as shown in the following example:

C++
class PanicSirene : public Observer<Temperature>
   {
   public:
      PanicSirene() {}
      ~PanicSirene() {}
      void update (Temperature *subject)
         {
         std::cout << "Temperature was changed, sound the sirene" 
               << std::endl;
         subject->getTemperature();
         }
   };

As you can see, a pointer to the Temperature instance that triggers the notification is given as argument to the update method. The observing class (PanicSirene in this case) can simply call any method of the notifying subject, in this case simply getTemperature. The following source shows how it is effectively used:

C++
Temperature       temp;
PanicSirene       panic;

temp.attach (panic);

temp.temperatureChanged ();

The following output will be generated:

Temperature was changed, sound the sirene
   Getting the temperature.

Observing Multiple Subjects of a Different Type

The templates are still easy to use if you need to attach the observer to multiple objects. Suppose that we have a similar subject-class for measuring the pressure.

C++
class Pressure : public Subject<pressure>
   {
   public:
      Pressure() {}
      ~Pressure() {}
      void pressureChanged () {notify();}
      void getPressure() {std::cout << "   Getting the pressure." 
          << std::endl;}
   };

If we want to show both the temperature and pressure in a window that shows all environment-related information, we simply create our EnvironmentWindow like this:

C++
class EnvironmentWindow : public Observer<Temperature>, 
        public Observer<Pressure>
   {
   public:
      EnvironmentWindow() {}
      ~EnvironmentWindow() {}
      void update (Temperature *subject) {std::cout << 
             "Temperature was changed" << 
             std::endl; subject->getTemperature();}
      void update (Pressure    *subject) {std::cout << 
             "Pressure was changed"    << 
             std::endl; subject->getPressure   ();}
   };

The class simply inherits twice from the Observer template, for both the Temperature and for the Pressure. Notice that we have two update methods here, one for the temperature, one for the pressure. The following example shows how it can be used:

C++
Temperature       temp;
Pressure          press;
EnvironmentWindow win;
PanicSirene       panic;

temp.attach (win  );
temp .attach (panic);
press.attach (win  );

temp.temperatureChanged ();
press.pressureChanged ();

And it shows the following output:

Temperature was changed
   Getting the temperature.
Temperature was changed, sound the sirene
   Getting the temperature.
Pressure was changed
   Getting the pressure.

Observing Multiple Subjects of the Same Type

If our PanicSirene class needs to verify both the internal temperature and the external temperature, we don't need to modify anything to our implementation of the PanicSirene class. We simply attach our class instance to both Temperature classes.

C++
Temperature       internalTemp;
Temperature       externalTemp;
PanicSirene       panic;

internalTemp.attach (panic);
externalTemp.attach (panic);

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
Software Developer (Senior) Patrick Van Cauteren
Belgium Belgium

Comments and Discussions

 
QuestionGood but not yet ! Pin
Axel Vanden Eynden8-Dec-14 22:37
Axel Vanden Eynden8-Dec-14 22:37 
Questionsupersensor Pin
llllskywalker18-Aug-14 6:04
llllskywalker18-Aug-14 6:04 
Generalg++ main.cc:40:error expected ';' before "it" Pin
Simon Asselbergs18-May-05 14:31
Simon Asselbergs18-May-05 14:31 
GeneralRe: g++ main.cc:40:error expected ';' before &quot;it&quot; Pin
Patje18-May-05 20:39
Patje18-May-05 20:39 
GeneralRe: g++ main.cc:40:error expected ';' before "it" Pin
Simon Asselbergs19-May-05 4:12
Simon Asselbergs19-May-05 4:12 
GeneralRe: g++ main.cc:40:error expected ';' before &quot;it&quot; Pin
Anonymous1-Aug-05 18:28
Anonymous1-Aug-05 18:28 
GeneralRe: g++ main.cc:40:error expected ';' before &quot;it&quot; Pin
Anonymous1-Sep-05 4:04
Anonymous1-Sep-05 4:04 
GeneralSTL container of instances issues & other limitations Pin
eohrnberger28-Jan-05 9:42
eohrnberger28-Jan-05 9:42 
GeneralRe: STL container of instances issues &amp; other limitations Pin
Patje31-Jan-05 0:24
Patje31-Jan-05 0:24 
GeneralGood place to start from but... Pin
elemings24-Oct-03 23:29
elemings24-Oct-03 23:29 
GeneralRe: Good place to start from but... Pin
Patje28-Nov-03 22:25
Patje28-Nov-03 22:25 
GeneralNotification on a Observer vector copy and other stuff Pin
MyttO22-Apr-03 5:53
MyttO22-Apr-03 5:53 
Interesting article.

I wrote my own Observation pattern using STL and have been running it for years.

Here is a couple of enhancements experience incited me to write I think you
may have in yours:

  • Add a Detach(Observer<T> &observer) method

    • Make a copy of the m_observers vector, and run updates on it: as an update to a notification, it may be possible for the observer to detach (views in particular).

  • Clean all observations up when destroying both subjects and observers, it prevents you from updating a dead guy.
  • Try to take into account cyclic notifications, and avoid which are often unregular though possibly correct: may be have a bool m_bNotifyMutex you should set to true at the begining of notify and false at the end. At least warn, or -- better -- through exception when a cyclic notification occurs.
  • use a bool m_bInhibited and escorting get/set to prevent the subject from notifying in certain circumstences. It might boost performance up, typically when subject batches a lot of notifying modification to its state.


-MyttO
Software Architect
www.graisoft.com
GeneralRe: Notification on a Observer vector copy and other stuff Pin
DanZx13-Jul-04 14:25
DanZx13-Jul-04 14:25 
GeneralRe: Notification on a Observer vector copy and other stuff Pin
Patje14-Jul-04 21:18
Patje14-Jul-04 21:18 
GeneralNice job! Pin
Alvaro Mendez10-Apr-03 11:14
Alvaro Mendez10-Apr-03 11:14 
GeneralRe: Nice job! Pin
Patje10-Apr-03 21:19
Patje10-Apr-03 21:19 
Generalnice idea, problematic implementation Pin
Anonymous10-Dec-02 18:33
Anonymous10-Dec-02 18:33 
GeneralRe: nice idea, problematic implementation Pin
Patje10-Dec-02 21:44
Patje10-Dec-02 21:44 
GeneralRe: nice idea, problematic implementation Pin
ChrisHowe9-Jan-03 1:13
ChrisHowe9-Jan-03 1:13 
GeneralNicely presented, well written Pin
Paul Evans8-Dec-02 1:24
Paul Evans8-Dec-02 1:24 
GeneralRe: Nicely presented, well written Pin
Patje8-Dec-02 21:13
Patje8-Dec-02 21:13 
GeneralRe: Nicely presented, well written Pin
Paul Evans8-Dec-02 22:25
Paul Evans8-Dec-02 22:25 
GeneralTypedef Subject's Observer Pin
4-Dec-02 15:08
suss4-Dec-02 15:08 
GeneralRe: Typedef Subject's Observer Pin
Patje4-Dec-02 20:43
Patje4-Dec-02 20:43 

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.