Introduction

We implement a generic observer pattern framework below. The observer design pattern can be employed as a core architectural element of a software package. In this pattern, objects called subscribers can arrange to have notified of any state change from an object called subject. GUI systems use this pattern to implement distributed event handling.

In our terminology the the object observed is called Observed and the object who wants to get notified is called a Subscriber. The subscription function which a Subscriber wants to get notified through, is housed in a lightweight class called Subscription.

We use a mixture of dynamic polymorphism and generic programming to connect the observer functions to the object observed. The user of this framework is hidden from much of the template complexity that happens internally. A good sample of test cases are also provided which covers most of the ways subscribers are hitched to an observed object.

An illustrative example can be downloaded here. Here is another viewable version of the code.

Consider the following button class.

class IObserved
{
 public:
        typedef void  (*TFun) ();
        virtual ~IObserved(){}
        virtual void ClickMe()=0;
};

class ExampleButton : public IObserved
{

    public:
      typedef std::vector < TFun > TFunContainer;
       void ClickMe()
      {
          TFunContainer::iterator tit;
          for( tit = m_subscriptions.begin();
                tit != m_subscriptions.end();
               ++tit
              )
             {
                  TFun fun = *tit;
                  (*fun)();
             }
      }
      protected:
           TFunContainer m_subscriptions;
};

The ExampleButton is the Signal. When it is clicked, the GUI frame work ensures that ClickMe is invoked. That much is given. Now we want a few subscribers to be notified when the ClickMe is executed. Note that, if the signature of the function subscribed or unsubscribed is anything other than void (*fun) () this will result in a compilation error. This ensures type safety.

We should be able to connect and disconnect subscribers at any part of the program.

Let us consider a subscriber, which is a global static function of signature void (*fun)().

Let us modify our class to include the subscribing an unsubscribing of the observer.

class IObserved
{
    public:
        typedef void (*TFun) ();
        virtual ~IObserved(){}
        virtual void ClickMe()=0;
        virtual void Subscribe( TFun fun )=0;
        virtual void Unsubcribe( TFun fun )=0;
};

class ExampleButton : public IObserved
{
    public:
        typedef std::vector TFunContainer;
        void ClickMe()
        {
          TFunContainer::iterator tit;
          for( tit = m_subscriptions.begin();
                tit != m_subscriptions.end();
               ++tit
              )
             {
                  TFun fun = *tit;
                  (*fun)();
             }
      }

      void Subscribe( TFun fun )
      {
          m_subscriptions.push_back( fun );
      }

      void Unsubscribe( TFun fun )
      {
          TFunContainer::iterator lit;
          lit = find( m_subscriptions.begin(),
                        m_subscriptions.end(),
                        fun );
          m_subscriptions.erase( lit );
       }
  protected:
       TFunContainer  m_subscriptions;
};

We would like to generalize this.

  • We should be able to add subscriptions which are either class member functions , member static functions or global static functions.
  • The signature of the subscription functions that are added, should be general enough and should be specified by the IObserved interface at compile time.
Observed and abstract ISubscription-s

To achieve the above generalizations, we come up with an interface for the subscription, which is template-ized by the arguments and return value of the subscription function. Based on the number of the arguments, we come up with N different interfaces. N is limited to 3 in the current implementation.

 // R is the return type of the subscription function
 // Ai is the ith argument of the subscription function

class ISubscription
{
    public:
        ISubscription(){}
        virtual ~ISubscription(){}
        void DeleteMe()
        {
             delete this;
        }
};

template < typename R >
class ISubscription0 : public ISubscription
{
    public:
        ISubscription0(){}
        virtual ~ISubscription0(){}
        R operator()()=0;
};

template < typename R, typename A1 >
class ISubscription1 : public ISubscription
{
    public:
        ISubscription1(){}
        virtual ~ISubscription1(){}
        R operator()( A1 a1 )=0;
};

template < typename R, typename A1, typename A2 >
class ISubscription2 : public ISubscription
{
    public:
        ISubscription2(){}
        virtual ~ISubscription2(){}
        R operator()( A1 a1, A2 a2 )=0;
};

template <
       typename R,
       typename A1,
       typename A2,
       typename A3  >
class ISubscription3 : public ISubscription
{
    public:
        ISubscription3(){}
        virtual ~ISubscription3(){}
        R operator()( A1 a1, A2 a2, A3 a3 )=0;
};

The ISubscription-n classes abstract away whether the actual subscription function is a class member function, member static function or a global static function. The user of the ISubscription-n subscription, which according to the above example would be a ExampleButton class, does not need to know

  • whether the subscription was through the member function of an instance of any particular class, and if so of what class
  • whether the subscription was through the constant member function of an instance of any particular class, and if so of what class
  • whether the subscription was through the static member function of any particular class and if so of what class
  • whether the subscription was through a global static function.

The ExampleButton class provided was a concrete example of a Observed. To play the role of the ExampleButton class, we introduce the Observed class. The ClickMe function of the ExampleButton is replaced by Fire function in the Observed class. We give the capability to Observed to decide at compile time to accept any of the above 4 ISubscription-s.

template <
    typename R,
    typename A1=DummyType,
    typename A2=DummyType,
    typename A3=DummyType
    >
class Observed : public IObserved
{
public:
/*-------------------------------------------------/
//  Determine at compile time
//  the type value of TSubscription
//------------------------------------------------*/
    typedef ISubscription0  <
                                    R
                                    >  ISubscription_0;
    typedef ISubscription1  <
                                    R, A1
                                    > ISubscription_1;
    typedef ISubscription2  <
                                    R, A1, A2
                                    > ISubscription_2;
    typedef ISubscription3  <
                                    R, A1, A2, A3
                                    > ISubscription_3;
    //Now based on the template parameter,
    //choose the correct
    //function signature as 'TSubscription'
    typedef typename TSwitch
                         <
                   TIsDummy<A1>::m_value,
                   ISubscription_0,
                   ISubscription_1
                         >::TValue TSubscription_01 ;
    typedef typename TSwitch
                         <
                   TIsDummy<A2 >::m_value,
                   TSubscription_01,
                   ISubscription_2
                         >::TValue TSubscription_012;
    typedef typename TSwitch
                         <
                   TIsDummy<A3>::m_value,
                   TSubscription_012,
                   ISubscription_3
                         >::TValue TSubscription;

     typedef std::vector
                         <
                    TSubscription
                         > TSubscriptionContainer;
public:
     Observed()
     {}

     ~Observed()
     {
	//Signal owns the subscriptions
        //and are deleted
        //upon the observed's destruction
	for_each
           (
           m_slots.begin(),
	   m_slots.end(),
	   std::mem_fun( &ISubscription::DeleteMe )
	   );
     }

/*-------------------------------------------------/
//  Fire functions of all possible signatures are
//  provided as member template functions.
//  Only the Fire function that matches with
//  the TSubscription can be used by the Observed.
//  Any other use will result in compilation error.
//  Either the constant or the non constant variant
//  of the Fire can be used by the Observed.
//------------------------------------------------*/
     void Fire(  )
     {
	TSubscriptionContainer::iterator tit;
        cout <<"calling 'void Fire()'"
             << endl;
	for( tit = m_subscriptions.begin();
		tit !=  m_subscriptions.end();
	   	++tit
	   )
	{
		TSubscription *psub = *tit;
		(*psub)( );
	}
     }
/*------------------------------------------------*/
     void Fire(   ) const
     {
	TSubscriptionContainer::const_iterator tit;
        cout << "calling 'void Fire() const'"
             << endl;
	for(  tit = m_subscriptions.begin();
		tit !=  m_subscriptions.end();
		++tit
	   )
	{
		TSubscription  *  psub = *tit;
		(*psub) ( );
	}
     }
/*------------------------------------------------*/
     template < typename A >
     void Fire( A a )
     {
	TSubscriptionContainer::iterator tit;
        cout << "calling 'void Fire(A a)'"
             << endl;
	for( tit = m_subscriptions.begin();
	     tit !=  m_subscriptions.end();
	     ++tit
           )
	{
		TSubscription *psub = *tit;
		(*psub)( a  );
	}
     }
/*------------------------------------------------*/
     template < typename A >
     void Fire( A a  ) const
     {
	TSubscriptionContainer::const_iterator tit;
        cout << "calling 'void Fire(A a) const'"
             << endl;
	for( tit = m_subscriptions.begin();
             tit !=  m_subscriptions.end();
             ++tit
           )
	{
		TSubscription  *  psub = *tit;
		(*psub)( a  );
	}
     }
/*------------------------------------------------*/
     template <
            typename A,
            typename B >
     void Fire( A a , B b )
     {
	TSubscriptionContainer::iterator tit;
        cout << "calling 'void Fire(A a, B b)'"
             << endl;
	for( tit = m_subscriptions.begin();
             tit !=  m_subscriptions.end();
             ++tit
           )
	{
		TSubscription *psub = *tit;
		(*psub) ( a, b );
        }
     }
/*------------------------------------------------*/
     template <
            typename A,
            typename B >
     void Fire( A a , B b ) const
     {
	TSubscriptionContainer::const_iterator tit;
        cout << "calling 'void Fire(A a, B b)const'"
             << endl;
	for( tit = m_subscriptions.begin();
             tit !=  m_subscriptions.end();
             ++tit
           )
	{
		TSubscription  *  psub = *tit;
		(*psub) ( a, b );
	}
     }
/*------------------------------------------------*/
     template <
            typename A,
            typename B,
            typename C >
     void Fire( A a , B b , C c)
     {
	TSubscriptionContainer::iterator tit;
        cout << "calling 'void Fire(
                                           A a,
                                           B b,
                                           C c
                                           )'"
             << endl;
	for( tit = m_subscriptions.begin();
	     tit !=  m_subscriptions.end();
	     ++tit
           )
	{
		TSubscription *psub = *tit;
		(*psub) ( a, b, c );
	}
     }
/*------------------------------------------------*/
     template <
            typename A,
            typename B,
            typename C >
     void Fire( A a , B b, C c ) const
     {
	TSubscriptionContainer::const_iterator tit;
        cout << "calling 'void Fire(
                                          A a,
                                          B b,
                                          Cc) const'"
             << endl;
	for(  tit = m_subscriptions.begin();
		tit !=  m_subscriptions.end();
		++tit
	   )
	{
		TSupscription  *  psub = *tit;
		(*psub)( a, b, c );
	}
     }
/*------------------------------------------------*/
     void Subscribe( TSubscription &sub )
     {
	m_subscriptions.push_back( &sub );
     }

     void Unsubscribe( TSubscription &sub )
     {
        TSubscriptionContainer::iterator lit;
        lit = find( m_subscriptions.begin(),
                      m_subscriptions.end(),
                      sub );
        m_subscriptions.erase( lit );
     }

     TSubscriptionContainer  m_subscriptions;
};

DummyType is an auxilliary structure used to pass default template arguments. With the DummyType, we could templatize Observed as Observed < int > or Observed < int, int > or Observed < int, int, int >. The missing template parameters will be of type DummyType. TIsDummy is a templatized structure that accepts one template parameter and the value of its enum m_value is 1 only if the template parameter is DummyType. TSwitch has three template parameters. If the first parameter, which is of int type is 0, its TValue will be the third template type argument. For all other cases, its TValue is by default the second template type argument.

struct DummyType{};

//template structure to detect DummyType
template < typename T >
struct TIsDummy
{
	enum { m_value=0};
};

template < >
struct TIsDummy < DummyType >
{
	enum { m_value=1};
};

//If the value of the template parameter i passed in
//is non-zero TValue is A, else TValue is B
template <
            int i,
            typename A,
            typename B >
struct TSwitch
{
	typedef typename  A TValue;
};

template < typename A, typename B >
struct TSwitch < 0, A, B >
{
	typedef typename B TValue;
};

Going back to our Button class, let us implement a ExampleButton. The ExampleButton class should derive from the Observed class of appropriate template parameters. The ExampleButton’s ClickMe function should call one of the Fire functions. In fact, the ExampleButton’s ClickMe can call only the Fire function of appropriate template parameters. Any other inappropriate Fire function called would result in a compilation error. Let us conceive a ExampleButton that notifies the subscribers, by calling a subscription function that accepts the id(int) of the ExampleButton as the only argument. Let the subscription function return void. Here is an example of such a ExampleButton.

class ExampleButton : public Observed< void, int>
{
    public:
       ExampleButton( int id):
           m_id(id),
           m_state(false){}
    public:
       void ClickMe()
      {
            m_state = (m_state) ? false : true;
            Fire( m_id );
      }
    protected:
        int m_id;
        int m_state;
};

When the ExampleButton is clicked, it should call the function template void Signal::Fire( A a ). If the ClickMe function of the ExampleButton was of signature void ClickMe() const then the compiler would have automatically chosen the function template < typename A > void Signal::Fire( A a ) const for firing.

As we can see from the above example, we are more or less done on providing the frame work on the signal side. The signal doesnt know about the actual subscription nature, (ie is it a member function, or a constant member function of an instance of some particular class, a static member function of some particular class or a global static function). Also the Signal can be templatized at compile time to use Slots of widely varying subscription signitures which accepts upto 3 arguments.

Subscription implementation for class member functions

So far we have talked only about abstract interfaces to subscription-s. We should provide a subscription framework that makes it easier for the user to construct a subscription by denoting one the following

  • a class instance and a member function,
  • a class instance and a constant member function
  • a member static function
  • a global static function

First let us come up with a concrete implementation of Subscription that encompass these generalizations. As an example we choose to implment Subscription1 which derives from ISubscription1.

//IsConst = is the subscription function
//	a constant member function,
//	as in 'R (H::*Fun) (Arg1) const'
//R = return type of the member function
//H = holder class of the member function
//A1 = First Argument of the member function
template < int IsConst,
	typename R,
	typename H,
	typename A1 >
class Subscription1 : public ISubscription1
                                         <R, A1>
{
    public:
	//get all possible function signatures here
	typedef R  (H::*TSubscriptionFun1)(A1);
	typedef R  (H::*TSubscriptionFun1C)(A1)const;
	//Now based on the template parameter,
	//choose the correct
	//function signature as 'TSubscriptionFun'

	typedef typename TSwitch
                                     <
                                IsConst,
				TSubscriptionFun1C,
				TSubscriptionFun1
                                     >::TValue
				TSubscriptionFun;

    public:
        Subscription1 (
                          H &holder,
                          TSubscriptionFun sFun
                           ):
            m_pHolder( &holder ),
            m_subscriptionFun( sFun )
            {}

        //main execute function
        R operator() ( A1 a )
        {
            return( m_pHolder->*m_subscriptionFun)(a);
        }

        //trivial destructor
        ~Subscription1()
        {}
    protected:
        H *         m_pHolder;
        TSubscriptionFun  m_subscriptionFun;
};

So we have now a concrete implementation of a Subscription. As an example, we need a subscriber who is going to use this subscription to get notified. Consider the following ExampleSubscriber.

class  ExampleSubscriber
{
    public:
       ExampleSubscriber( int id ):
           m_id( id ){}
      //details of the class
    public:
        //This is the example subscription function
        void NotifyMe( int signalId )
        {
             std::cout <<  m_id << " is notified by "
                           << signalId << std::endl;
         }
        int m_id;
};

Using the ExampleSubscriber we can make a Subscription1 as follows

ExampleSubscriber subscriber(78);
//IsConst = 0, The signature of Notify denotes that
//                    it is a non-constant function
//R = void
//H = ExampleSubscriber
//A1 = int
Subscription1 < 0, void, ExampleSubscriber, int >
            subscription(
                   subscriber,
                   ExampleSubscriber::Notify
                        );

To be used by a Observed, the subscription has to be created in heap, because once subscribed, the subscription is owned by the Observed. This is because, Subscription1 (or in general Subscription-n is a light weight class and it is better if the keeper of the class takes care of its deletion. Also, to aid in the creation of a subscription, we can come up with helper functions called SubscriptionMaker.

 template <
        typename R,
        typename H,
        typename A1
         >
Subscription1 < 0, R, H, A1 >* SubscriptionMaker
                           (
                                   H &h,
                                   R (*H::Fun)( A1 )
                           )
{
        return new Subscription1
                                   < 0, R, H, A1
                                   >( h, Fun);
}

Now we can use the framework to tie the Notify function of the ExampleSubscriber to the ExampleToggleButton’s ClickMe function as follows.

{
    ExampleButton button( 96 );
    ExampleSubscriber sub1( 78) ;
    button.Subscribe(
      *SubscriptionMaker
                     (
                     sub1,
                     &ExampleSUbscriber::Notify
                     );
    button.ClickMe();
}

As you can see, all the template ugliness is hidden away from the user, and as a result the following outputs should be produced.

calling void Fire( A1 a )
78 is notified by signal 96
Subscription implementation for static functions

The above Subscription class worked only for subscription functions which are constant or non-constant class member functions. We now need to provide a solution that covers member static functions of some particular classes or global static functions. We can form an equivalent Subscription class for these static functions by conveniently specializing the template parameter H of Subscription to void.

//IsConst = 0, since static functions
//                       cannot be constant.
//R = return type of the member function
//H = void
//A1 = First Argument of the member function
template               <
        typename R,
	typename A1
                          >
class Subscription1 < 0, R, void, A1 >
                     : public ISubscription1
                              < R, A1 >
{
    public:
	typedef R  (*TSubscriptionFun)(A1);

    public:
        Subscription1 (
                          TSubscriptionFun sFun
                           ):
            m_subscriptionFun( sFun )
            {}

        //main execute function
        R operator() ( A1 a )
        {
            return(*m_subscriptionFun)(a);
        }

        //trivial destructor
        ~Subscription1()
        {}
    protected:
        TSubscriptionFun  m_subscriptionFun;
};

The subscription maker corresponding to that would be

 template <
        typename R,
        typename A1
         >
Subscription1 < 0, R, void, A1 >* SubscriptionMaker
                           (
                                   R (*Fun)( A1 )
                           )
{
        return new Subscription1
                                   < 0, R, void, A1
                                   >( Fun);
}

The following example code can be used for that.

   //notify function is defined
    void Notify( int signalId )
   {
        cout <<
             "Notify function notified by signal"
             << signalId <<endl;
   }

   class NotifyClass
   {
       static void Notify( int signalId )
       {
             cout << "NotifyClass::Notify notified by signal"
                    << signalId << endl;
       }
   } ;
  //else where in the code body
    ExampleToggleButton button( 96 );
    button.Subscribe(
      *SubscriptionMaker
                     ( &Notify
                     );
    button.Subscribe(
      *SubscriptionMaker
                    ( &NotifyClass::Notify
                    );
    button.ClickMe();
}

which produces the output

calling void Fire( A1 a )
Notify function is notified by signal 96
NotifyClass::Notify notified by signal 96


blog comments powered by Disqus