Click here to Skip to main content
Click here to Skip to main content

An easy to use Observer Pattern Implementation (no inheritance required)

By , 26 Jan 2004
 

Introduction

The Observer Pattern (Gamma) is part of lots of implementations and is one of the most used patterns. However, implementing a flexible Observer pattern is not an easy task and most of the time creates tight coupling between the Subject and the Observer.

The most popular implementations are:

  • Using an interface that has to be inherited by the Observers
  • Using function to pointers from the Subject to the Observers

Implementing any of those two models is both complex and error prone, thus time-consuming. More than this, the coupling that normally appears between the Subject and Observer is most of the time unnecessary, while the requirement of implementing an interface sometimes breaks the design, adds the multiple-inheritance costs or requires you to create Adaptor classes between the Subject and the Observer.

The proposed implementation tries to overcome those problems by using the power of templates and creating an easy way of implementing the Observer Pattern in existing or new code without requiring inheritance or function pointers and making it easy to decouple the Subject from the Observer.

Background

Observer Pattern (also called the "Publish-Subscribe" mechanism) as presented by Gamma defines a "one-to-many" so that when one object changes its state, the dependent objects will be informed about this.

The Observer Pattern is widely used in GUI applications (Model-View-Controller model) and other systems where specific objects (Observers) need to be informed when the Subject changes its state. (e.g.: function handler called when a button is pressed).

C# implements this model as Events:

  • Button class defines the event as an event and the delegation of the event.
    public event ClickEventHandler Click(object sender, System.EventArgs e)
    public delegate void ClickEventHandler(object sender, System.EventArgs e)
  • Observer class (your form) subscribes for the event:
    this.btnMyButton.Click += new System.EventHandler(this.btnMyButton_Click);
  • Observer class expects the event to be received when the button is pressed and the Click event is generated in the function:
    private void btnMyButton_Click(object sender, System.EventArgs e) {...}

Now, this model is simple and basic for C# without too many dependencies. This Observer implementation tries to bring this ease to C++ with maybe a little bit more flexibility.

Using the code

First things first: we have a Subject and more Observers. The Subject is the one that defines the Events and the Observers are the one that have to be subscribed to those events and receive them when they are fired. The only real requirement on any Observer implementation is that the Event defined by the Subject is defined in the same way in the Observer. E.g.:

class CSubject1{
public:
    void Event1( int param1, int param2, bool param3 );
    // event definition
};

implies one of the following Observer implementations:

class CObserver1{
public:
    void OnEvent1( int param1, int param2, bool param3 );
    // event definition
};
class CObserver2{
public:
    void OnEvent1WithSubject( CSubject1 *pObject, 
       int param1, int param2, bool param3 ); 
    // event definition with reference to the
    // Subject that generated the event
};

Both event definitions are useful. In the first definition, we have the advantage that we don't need to know the class that generated the event, thus it can be any class, not only the CSubject1. In the second definition, we have to know the CSubject1 class thus we can make specific operations based on the details of CSubject1.

Now, to get to the subject, how do we connect those 2 classes and make the CSubject1::Event1 function behave as an Event and call the CObserver1::OnEvent1 or CObserver2::OnEvent1WithSubject? The answer is quite simple. Add to the Subject, a member of SubjectEvents and connect the Observers with the Subjects:

class CSubject1{
public:
    CSubject1();
    // event definition
    void Event1( int param1, int param2, bool param3 );
    // the Events manager
    SubjectEvents<CSubject1> Events;
};
CSubject1::CSubject1()
// required to initialize the Events with the "this" pointer
: Events(this)    
{ }

void main()
{
    CSubject1 s1;
    CObserver1 o1;
    CObserver2 o2;
    // Connect s1 event to o1, o2

    // First Connect the Event1 to CObserver1 o1. Type checking
    // is automatically done on both ends
    // thus the event implementation in the Observer has
    // to be correct or the code will not compile
    // ClientObserver is used when the event is implemented
    // without requiring the CSubject1 as parameter
    s1.Events ( CSubject1::Event1 ) += 
         ClientObserver ( &o1, CObserver1::OnEvent1 );
    // connect o2. SubjectObserver is used when the event
    // implementation has the CSubject1 as the first parameter
    s1.Events ( CSubject1::Event1 ) += 
         SubjectObserver ( &o1, CObserver1::OnEvent1WithSubject );

    // both event handlers are not connected to the event generator.
    // Now, generate an event:
    s1.Events ( CSubject1::Event1 ).Notify ( 1, 2, true ); 
    // type checking is done again assuring
    // the validity of the parameters !!!

    // if you like more explicit/shorter function
    // calls you can call the event like:
    s1.Events ( CSubject1::Event1 ) ( 1, 2, true );
    // or
    s1.Events.Event ( CSubject1::Event1 ).Notify ( 1, 2, true );
}

However, most of the times, you don't want to "generate" the event from outside of the class that defined the event, neither to write too often a line like:

s1.Events ( CSubject1::Event1 ) ( 1, 2, true );

to generate an event, and more than that, we also have the CSubject1::Event1(...) function not implemented and not used, thus a small change will make our life easier.

void CSubject1::Event1( int param1, int param2, bool param3 )
{
    Events ( CSubject1::Event1 )( param1, param2, param3 );
    // type checking is done again assuring
    // the validity of the parameters !!!
}

Right now, we have a complete Event generating and handling mechanism, completely type-safe and quite flexible. To trigger an event, we only have to call Event1 ( ... ) and the event is automatically generated and dispatched to all subscribed Observers. The main advantages are:

  • No need to define an interface in order to make an Observer receive events generated from a Subject.
  • No need for the observer to know the subject. (possible but not mandatory)
  • No need for complex pointers to functions cast, or observer registration model.
  • Easy to use: with 2 lines of code you have a publish-subscribe mechanism.
  • Safe: all function definitions and calls are typed-checked
  • Secure: you cannot connect two functions with different parameters
  • Decouples Subject from Observers

The main "drawbacks" of the implementation are:

  • return type has to be void
  • a maximum of 5 parameters is supported in the current implementation
  • the overhead of an event generation is:
    • the cost to find the event definition in a list +
    • one virtual function call (after the code is optimized by the compiler)

Implementation details

The implementation uses pointers to functions and template's capabilities of auto-detection of parameters. When a function is registered as an event s1.Events ( CSubject1::Event1 ), the type of the parameters of the function are stored and any function that is registered as an Observer function has to be compliant with the parameters defined by the Subject function.

The SubjectEvents class maintains a list of registered events. Every time the Event(...) or operator() ( ... ) is called, the function send as parameter is looked up and registered as an Event.

The Event(...) and operator() ( ... ) have the same prototype(s), overloaded for different types of functions that can be registered, from:

template<typename _Subject2>
    Blue::TSubjectEvent<_Subject2>& Event ( void (_Subject2::*_FuncPoint)() )

for functions without any parameter to:

template<typename _Subject2, typename _Param, 
  typename _Param2, typename _Param3, typename _Param4, typename _Param5>
     Blue::TSubjectEvent<_Subject2,_Param, _Param2,_Param3,_Param4,_Param5>& Event 
        ( void (_Subject2::*_FuncPoint)(_Param,_Param2,_Param3,_Param4,_Param5) )

for function with five parameters. Thus, every call will automatically detect the exact parameters that are passed and return the class TSubjectEvent constructed with the specified parameters. The TSubjectEvent has the operator+= overloaded and accepts to add to its internal list of observers - TClientObserver classes created on the same mode and having the same parameters. The TClientObserver is returned from the call to the ClientObserver or SubjectObserver function and it's built in the same way as the Event(...) function by detecting the parameters of the function:

template <typename _Observer, typename _Subject>
TClientObserver<_Observer,_Subject> 
  SubjectObserver( _Observer* pObserver, 
  void (_Observer::*_FuncPoint)(_Subject) )
{    // SubjectObserver requires function
     // that contain the "_Subject" as the first parameter
    return TClientObserver<_Observer,_Subject> ( pObserver, _FuncPoint );
}
template <typename _Observer>
TClientObserver<_Observer,NullType> 
  ClientObserver( _Observer* pObserver, void (_Observer::*_FuncPoint)() )
{    // ClientObserver requires functions
     // that do not _Subject as the first parameter
    return TClientObserver<_Observer> ( pObserver, _FuncPoint );
}

If you are trying to add an Observer function using SubjectObserver/CodeObserver with different parameters than the ones defined in the TSubjectEvent, the compiler will not compile the code as the parameters are different.

When trying to Fire an Event from a Subject, the same parameter type detection mechanism is used as in the case of function registration. The only noticeable implementation detail is the final function call that gets executed from a template function based on different details of the Observer and Observer's function:

_Call ( param, param2, param3, param4, param5, 
  ParamCount<ArgCount>(), Type2Type<_Subject>() );

In case of an Observer registered NOT to receive the Subject as a parameter, one of the following functions is automatically selected by the compiler:

template<typename T> void _Call( _Param param, 
  _Param2 param2, _Param3 param3, _Param4 param4, _Param5 param5, 
  ParamCount<0>, Type2Type<NullType> )
{
    ( ((_Observer*)m_pObserver)->*m_pFunction) ( );
}
[...]
template<typename T> void _Call( _Param param, 
  _Param2 param2, _Param3 param3, _Param4 param4, 
  _Param5 param5, ParamCount<5>, Type2Type<NullType> )
{
    ( ((_Observer*)m_pObserver)->*m_pFunction) 
            ( param, param2, param3, param4, param5 );
}

or one of the followings if the Observer wants the Subject pointer:

template<typename T> void _Call( _Param param, 
  _Param2 param2, _Param3 param3, _Param4 param4, 
  _Param5 param5, ParamCount<0>, ... )
{
    ( ((_Observer*)m_pObserver)->*m_pFunction) ( (_Subject)m_pSubject );
}
[...]
template<typename T> void _Call( _Param param, 
  _Param2 param2, _Param3 param3, _Param4 param4, 
  _Param5 param5, ParamCount<5>, ... )
{
    ( ((_Observer*)m_pObserver)->*m_pFunction) 
      ( (_Subject)m_pSubject, param, param2, param3, param4, param5 );
}

The two final parameters help the compiler detect the exact function to be compiled according to:

  1. Number of valid parameters of the Observer's function based on ParamCount<0>
  2. Whenever to pass the Subject pointer or not based on Type2Type<NullType>

If the Observer was registered NOT to receive the Subject, the Type2Type<_Subject>() will resolve to Type2Type<NullType> (as the _Subject is by set to NullType).

If the Observer is registered to receive the Subject, the function defined with the last parameter ... will be selected by the compiler.

In the same way, the ParamCount is used to select the number of parameters to be sent to the final function.

Conclusion

The Observer pattern can be implemented in multiple ways, most of them requiring relying on defining an interface to be implemented (in some way or another) by another class.

This implementation uses a non-intrusive way of implementing this pattern, allowing you to completely de-couple the Observer from the Subject.

History

  • 25.Jan.2004: version 1.0

    First released version.

Copyright

You can use these sources for absolutely free.

Related articles

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

About the Author

Corneliu Tusnea
Web Developer
Australia Australia
Member
8 years commercial experience out of which 7 years developing financial applications for different stock exchanges.
5 years VC++ and about 2.5 years C# experience and a bit of Java.
 
Now working with the fantastic guys from Readify, a group of elite consultants specialising in technical readiness.
 
Keeping a blog at:
www.acorns.com.au
 
Developing:
Hawkeye - The .Net Runtime Object Editor - A developer's best tool.
 
Keeping myself busy with my site:
www.bestgames.com.au website.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 5memberMC197210 Aug '11 - 20:15 
Smile | :)
GeneralIt seems to me that you are doing something wrong herememberjesusdesantos3 Feb '04 - 14:00 
This line:
 
Blue::TSubjectEvent<_Subject2,_Param,_Param2,_Param3,_Param4,_Param5> *pObs = (Blue::TSubjectEvent<_Subject2,_Param,_Param2,_Param3,_Param4,_Param5>*) (*it);
 

You are casting from parent to child in a unsafe way. Most of the times you are casting to the wrong class. But in your compiled you are being lucky.
I think that your TestParentFunc() should be implemented in ObserverFunctionCallBase as a virtual function.
 
Am I wrong?
 


Generalvery goodmembervictor_cui29 Jan '04 - 20:00 
Very Good! thanks a lot. but when i compiler with stlport 4.6 replace the stl lib, it cannot work, why?
GeneralRe: very goodmemberTutu30 Jan '04 - 2:14 
Hi,
I have no clue why you get an error, but maybe if you post the error including the line of code I could give it a try and see what's wrong and hopefully fix it.
 
Tutu.
GeneralRe: very goodmembervictor_cui1 Feb '04 - 13:49 
Hi, thanks for your reply. the error occur at _alloc.c, so maybe this is a bug from stlport 4.6, I use the stlport version 5.0 beta instead of theversion 4.6, the problem has been fixed!;P
Generalmore generic solutionmemberkig27 Jan '04 - 13:13 
With templates, I believe there is a more generic (dare I say modern) solution.
 
'variant' and 'function' types can be found in TTL
or boost
 
The following code is untested, but you should get the idea.
 
struct empty {};
 
//base for subjects
template< typename Event1, typename Event2=empty, typename Event3=empty >
struct subject
{
    //observer callbacks
    typedef function< void (Event1& ) > observer_function1;
    typedef function< void (Event2& ) > observer_function2;
    typedef function< void (Event3& ) > observer_function3;
    //an observer could be any of these functions
    typedef variant<observer_function1, observer_function2, observer_function2> observer_function;
 
    //heterogenous list of observers
    std::vector<observer_function> observers_;
 
    //observer visitor
    template< typename Event >
    struct observer_visitor
    {
        Event& e_;
 
        visitor( Event& e ) : e_(e) {}
        visitor( const visitor& v ) : e_(v.e_) {}
 
        void operator()( function< void (Event&) >& f )
        {
            f( e_ ); //call observer
        }
        template< typename T >
        void operator()( T& f ) //skip observer
        {}
    };
 
    //generate events
    template< typename E >
    void generate_event( E& e )
    {
        observer_visitor< E > v(e);
        for_each( observers_.begin(), observers_.end(), std::bind1st( apply_visitor, v ) );
    }
};
 
//events generated by my_subject
struct event1 { ... };
struct event2 { ... };
 
struct my_subject : subject<event1, event2>
{
};
 
//my observers
struct observer1
{
    void on_event( event1& e );
};
 
//my observers
struct observer2
{
    void on_event( event2& e );
};
 
main()
{
    my_subject s;
    observer1 o1;
    observer2 o2;
 
    //connect observers
    s.observers_.push_back( std::bind1st(std::mem_fun(&observer::on_event), &o1)) );
    s.observers_.push_back( std::bind1st(std::mem_fun(&observer::on_event), &o2)) );
 
    //generate event1
    event1 e1;
    s.generate_event( e1 );
 
    //generate event2
    event1 e1;
    s.generate_event( e2 );
};

GeneralRe: more generic solutionmemberTutu27 Jan '04 - 17:25 
Indeed this looks like a more generic solution for event generating, but it still uses inheritance, something I've tried to avoid and more than that:
- it adds the overhead of using boost or TTL into your project (I've seen TTL as beeing extremely small, but still quite big for such a simple task)
- you have to define structures in order to be able to pass more than one parameter.
Except this, I think your TTL is realy a nice implementation for event generating, thus for more complex event structures.
One more advantage I would add of the "no inheritance observer" is that you can easly pass the originator subject as the parameter in the Observer's function call.
 
Tutu.

GeneralRe: more generic solutionmemberkig27 Jan '04 - 20:26 
If you need to pass the subject to observers and get rid of the inheritance,
you can make some simple modifications.
 
template< typename Subject, typename Event1, typename Event2=empty, typename Event3=empty >
struct subject
{
  typedef function< void (Subject&, Event1& ) > subject_observer_function1;
  ...
 
  typedef variant< subject_observer_function1, ... > observer_function;
 
  Subject& subj_;
  subject( Subject& s ) : subj_(s) {}
 
  ...
  template< typename Event >    
  struct observer_visitor
  {
    Subject& subj_;
    Event& e_;
    observer_visitor( Subject& s, Event& e ) : subj_(s), e_(e) {}
    ...
    void operator()( function< void (Subject&, Event&) >& f )
    {
       f_( subj_, e_ );  //the observer wants the subjects
    }
    void operator()( function< void (Event&) >& f )
    {
       f_( e_ );  //the observer doesn't want the subject
    }
    ...
  };
  ...
  
};
 
struct my_subject 
{
  subject< my_subject, event1, event2 > observer_interface_;
 
  my_subject : observer_interface_( *this ) {}
}
 
As for passing more than one event parameter, IMHO, a set of
well defined event types makes the code more manageable and readable.
 
Having said that, my solution can be modified to support as many parameters
as you like.
 

GeneralRe: more generic solutionmemberkig27 Jan '04 - 20:43 
BTW: don't get me wrong. Your solution is quite good. You have my 5 Smile | :) .
I just wanted to share some different ideas.

GeneralInteresting stuff!memberJim Crafton27 Jan '04 - 5:55 
This is pretty interesting
Could you compare/contrast your implementation with libsigc++[^] ?
 
It seems to me that both are very very similar approaches.
 
¡El diablo está en mis pantalones! ¡Mire, mire!
 
Real Mentats use only 100% pure, unfooled around with Sapho Juice(tm)!
 
SELECT * FROM User WHERE Clue > 0
0 rows returned

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 27 Jan 2004
Article Copyright 2004 by Corneliu Tusnea
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid