Chapter 11 Introduction to Program Design

All the examples (including a Makefile) from the book and the extra examples on this page are in the chapter-11.tar.gz tarball.

Examples from the book

The observer pattern

The files observer0.h and tobserver0.C contain the example from the book (p. 262).

It is mentioned in the book that the solution is restricted by the requirement that all observers of a single observable must be of the same type.

In the files observer1.h and tobserver1.C a more general solution can be found.

The technique from observer1.h can be used in general to build heterogeneous containers of objects of different unrelated types that support a common interface.

The idea is the following:

  1. Define an abstract class AC (for ``Abstract Class'') that declares the interface using pure virtual member declarations.
            class AC {
    	public:
    	  virtual int f(int) = 0;
    	};
    
  2. An heterogeneous container will consist of AC pointers, e.g.
       	vector<AC*>	container;
    
  3. We want to be able to add any object of type C that supports the AC interface with minimal fuss, e.g. by writing something like
    	C	c(..);
    	D	d(..); // D and C not related through inheritance.
    
          	container.push_back(&c);
          	container.push_back(&d);
    
    This would be possible if C and D would be subclasses of AC, but they are not.
  4. A partial solution may be to have a class C' derived from AC which wraps around a C object:
          	class C': public AC {
    	public:
    	  C'(C& c): c_(&c) {}
    	  int f(int i) { return c_->f(i); }
            private:
    	  C*	c_;
    	};
    
    With this, we could write
            container.push_back(new C'(c));
    
  5. To make the solution work also for D and any class supporting the AC interface, it suffices to replace C' by a class template ACT.
    	template<class X>
            class ACT: public AC {
    	public:
    	  ACT(X& x): x_(&x) {}
    	  int f(int i) { return x_->f(i); }
            private:
    	  X*	x_;
    	};
    
    Now we can write:
          	container.push_back(new ACT<C>(c));
          	container.push_back(new ACT<D>(d));
    
    and, e.g.,
           for_each(container.begin(), container.end(), mem_fun(&AC::notified)); 
    
    As a real example, the code from observer1.h is reproduced below.
    #ifndef	OBSERVER_H
    #define	OBSERVER_H
    
    #include	<list>
    #include	<functional>
    #include	<algorithm>
    
    class AbstractObserver {
    public:
      virtual void notified() = 0;
    };
    
    template <class T>
    class Observer: public AbstractObserver {
    public:
      Observer(T& t): t_(&t) {}
      void notified() { t_->notified(); }
    private:
      T*	t_;
    };
    
    // An Observable has a number of associated Observers that will be
    // warned using Observer::notified() each time the Observable changes
    // state.
    class Observable { 
    public:
      template <class T>
      void add_observer(T& observer) { 
             observers_.push_back(new Observer<T>(observer)); }
      void remove_observer(AbstractObserver& observer) {
             observers_.remove(&observer); }
    protected: // an observable that changes state will call notify()
      void	notify() { 
        for_each(observers_.begin(), observers_.end(), 
        	     mem_fun(&AbstractObserver::notified)); 
        }
    private:
      list<AbstractObserver*>		observers_;
    };
    #endif
    

    Dirk Vermeir (dvermeir@vub.ac.be) [Last modified: Sun Jul 15 15:58:52 CEST 2001 ]