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

Observing the world - how to build a reuseable implementation of a design pattern

Rate me:
Please Sign up or sign in to vote.
4.58/5 (11 votes)
11 Jun 200111 min read 93.8K   395   86   9
An article about the techniques to pour reusable design (a design pattern) into reusable code with an example of the observer pattern.

Abstract

This article describes how it is possible to put a reusable design idea into a really reusable code with an example of the observer pattern. After a short repetition about patterns in general and the observer pattern I will show you the typical disadvantages of applying it to a specific problem because of inheritance dependencies. I will provide some general techniques to reduce such dependencies and eventually present a reusable implementation of the observer pattern that can be easily applied to your projects. You should be familiar with the idea of patterns in general, the observer pattern, concepts of OOA/OOD and the C++ template mechanism.

Introduction

Reusable design...

"A design pattern names, abstracts, and identifies the key aspects of a common design structure that makes it useful for creating a reusable object-oriented design" [Gamma et al in [1]]

As Gamma stated out, a design pattern addresses the common problem in creating object-oriented software designs. It helps you to communicate with other developers about your design at a high level of abstraction, which is really great. However, someday you will have to put the design into a concrete implementation, meaning you have to pour it into some code.

... versus reusable code

At this point you will find yourself doing the same coding again and again. Wouldn't it be nice to have some reusable code for the reused design? The rest of this article will show you how it is possible to achieve this with an example of the observer pattern.

The observer pattern

The observer pattern is one of the best and most usable patterns in practice. This comes from its simplicity and the fact that it addresses a very common problem: The problem of propagating changes in an object's state to dependent objects. There is an excellent article by T. Kulathu Sarma about the observer pattern, here at CodeProject [2], so I do not want to go in detail. The following diagram (modified slightly from [1]) shows the structure of the observer pattern:

Image 1

The problem

Our goal is to create a reusable implementation of the observer pattern. That looks simple, just put the above structure straight into a class Subject and an abstract interface IObserver and use them whenever you need to implement the observer pattern. But you will find that it is difficult to reuse your code in real world projects. The main problem is that it involves structural dependencies on the project's architecture. And these dependencies come from the usage of inheritance.

Why inheritance is bad

Some of you may wonder: "Come on, what's so bad about inheritance? It's inheritance that helps us build reusable components!". Of course, you are right. Inheritance is a very powerful - if not the most powerful - concept in OOP. But in general the usability of inheritance ends beyond the kind-of relationship. For the observer pattern it sounds quite strange to call a real-world ConcreteSubject, like a temperature sensor used in weather forecasts, a "kind of subject". It is more common to think of the possibility to notify other objects on state changes as a feature of our temperature sensor.

The structural impacts of inheritance

1. Modification of the inheritance tree

Consider the following class structure of an automatic weather forecast system with classes TempSens (a temperature sensor) and HurrDet (a hurricane warning detector) which are derived from some base classes. We don't want to make any assumptions about these base classes, so we just call them A and B:

Image 2

Now we want HurrDet to act as an observer of TempSens. To reuse our implementation TempSens has to be derived from Subject and HurrDet from the IObserver interface. This is a bit tricky, because we have to:

  • change the existing inheritance tree or
  • use multiple inheritance

Image 3

Solution a) involves changes to the inheritance tree which are often not acceptable. Solution b) uses multiple inheritance to add the necessary functionality into the class TempSens. The Subject class is used as a mixing class that mixes the functionality into our concrete class. This is a very common approach. However, multiple inheritance on polymorph classes can be tricky and most people tend to avoid it. At the end most developers would not reuse our class Subject, and build their own implementation of the observer pattern for this special case. In other words: They would reuse the design - but not the code:

Image 4

2. Serving and observing more than one kind of object

Now we add a new class Strategy that describes the algorithms to use for all weather forecast components. These algorithms are adopted from time to time by some meteorologist to implement new research results into the system. We want HurrDet to act as an observer for both the TempSens and the Strategy objects. This means that HurrDet now acts as two single observers.

At the same time we add a new feature to our TempSens. Because the reliability of the temperature sensor is crucial for the system, it should be able to monitor its physical state and report it to the technical staff. As you might already guess, this is another excellent application for the observer pattern. Our class TempSens now is a subject in two very different manners. It acts as two single subjects, one propagating temperature information and the other propagating physical state information.

At this point the reusage-by-inheritance approach fails. We cannot derive our class TempSens more than once from Subject or derive HurrDet more than once from IObserver. Indeed, we have to create a lot of IObserver-like interfaces and again here we are reusing only the design - and not the code:

Image 5

Conclusion

The above example has shown that reusing the code of a generic component like our observer implementation tends to be difficult in a real world project. This is because we make too many assumptions about the structure of the project:

  1. It must be possible to add the functionality by inheritance.
  2. Every class needs the functionality at most once.

The solution

It is important to note that both the assumptions are a drawback of the usage of inheritance. Having understood this, it is clear that we have to find a way to build our components in a way that does not depend on inheritance. We can achieve this by replacing inheritance with aggregation. Instead of deriving from a class with some functionality, we include it as a data member (aggregate it). However, data members are usually declared private and are not accessible from other classes. So we need to add member functions to the outer class to export the interface of the aggregate. These member functions are simple one-liners, they just call the corresponding members of the aggregate, which is often referred to as delegation. Delegation is a very popular technique in COM development because COM lacks implementation inheritance and delegation is the only possible way to reuse an implementation.

Implementing the subject without inheritance

We start with implementing the subject, because this is the easiest part to solve. Instead of deriving our concrete subject TempSens from Subject we now aggregate it and use delegation. This solves all our problems, because by using aggregation it becomes easy to include it twice into TempSens. As described above, we have to (partially) export Subjects interface from TempSens because other objects must still be able to attach or detach themselves as observer:

Image 6

Replacing inheritance by aggregation in our TempSens is quite easy. This is because of the fact that we don't need the abstract interface of Subject. Indeed the observers are semantically connected to the concrete subject TempSens and need to know its interface. So it is not a problem for them that the Attach() and Detach() methods are now part of the TempSens interface and are not inherited from some abstract ISubject interface.

Implementing the observer without inheritance

However, on the observer side, things are quite different. At runtime, the subject may be connected to any number of observers, which may be instances of different classes. On a state change the subject calls Update() for every observer object, so all observers have to be derived from the abstract IObserver interface. In other words: Here we need Update() to be a part of an abstract interface, it is not possible to move it to the concrete observer classes.

To come around this we again use delegation, but now in another direction. We create a helper class ObserverAdapter that implements the IObserver interface. We aggregate this into our HurrDet class. The ObserverAdapter::Update() implementation just calls a member function of the outer HurrDet class - it delegates the call to the outer class:

Image 7

Because we don't want to write such a special helper class for each observer we generalize our ObserverAdapter implementation: We parameterize it by the outer class and use a member callback into the outer class for delegation. To use member functions as callbacks is not that common. Maybe this is because of the confusing syntax of pointer-to-member constructions in C++. However, if you look at the code of class ObserverAdapter below, you will find that it is not really difficult to use a member function as callback.

A reusable implementation of the observer pattern

Now we have all the elements that we need to create the reusable implementation of the observer pattern. Besides I have added some more extensions to the original structure of the observer pattern which makes it more applicable to the real world projects:

  1. I declared IObserver as a subclass of ISubject. This makes sense because of the close relationship between a subject and the interface of its observers. In other words: The subject defines the interface of its observers.
  2. The Notify() call accepts a single parameter and returns a value. The types for parameter and return value are passed as template arguments to ISubject. It's up to you what to do with these parameters; I usually use them to pass information about the changes in the subject's state. This technique is known as push-variation of the observer pattern.
  3. Another template argument is used to store an observer-related attribute in the subject's list of observers. The semantics of this attribute is in the responsibility of the subject implementer. For example, it can be used as event mask or threshold to pass observers only those events they are really interested in.
  4. All the classes and interfaces are declared in the namespace tool.

The above extensions are useful to avoid unnecessary method invocations, which is especially important for distributed computing scenarios.

The ISubject and IObserver interfaces

template<class OBSERVER_INFO = int, 
              class RETURN = int, class ARG1 = int>
struct ISubject
{
    // type of observer related data
    typedef typename OBSERVER_INFO observer_info_t; 
    // return type of the callback fn             
    typedef typename RETURN  return_t;      
    // type of the callback fn argument     
    typedef typename ARG1 arg1_t;          
                                   

    // Classes that act as observer have 
    // to implement this interface
    struct IObserver
    {
        typedef typename RETURN return_t;
        typedef typename ARG1   arg1_t;

        virtual return_t Update( arg1_t ) = 0;
    };


    virtual bool AttachObserver(IObserver* pObserver, 
                            observer_info_t Info) = 0;
    virtual bool DetachObserver(IObserver* pObserver) = 0;
};

As said above, the subject interface ISubject gets two template parameters to parameterize the signature of ISubject::IObserver::Update(). A third template parameter is used to declare the attribute type for the information to be stored with each observer. Nothing fancy so far. Now let's have a look at the reusable ISubject implementation:

The SimpleSubject class

template<class OBSERVER_INFO = int, 
          class RETURN = int, class ARG1 = int>
class SimpleSubject : public ISubject<OBSERVER_INFO, 
                                        RETURN, ARG1>
{
public:
    virtual bool AttachObserver(IObserver* pObserver, 
               observer_info_t Info = observer_info_t() )
    {
        m_aObservers.push_back(observer_entry_t(pObserver, 
                                                   Info));
        return true;
    }

    virtual bool DetachObserver( IObserver* pObserver )
    {
        clients_t::iterator i = m_aObservers.begin();
        for( ; i != m_aObservers.end(); ++i )
            if( i->pObserver == pObserver ) {
                m_aObservers.erase( i );
                return true;
            }
        return false;
    }

    virtual void NotifyObserver( arg1_t arg1 = arg1_t() )
    {
        for( clients_t::iterator i = m_aObservers.begin(); 
                            i != m_aObservers.end(); ++i )
            i->pObserver->Update( arg1 );
    }

protected:

    struct observer_entry_t
    {
        observer_entry_t( IObserver* o, observer_info_t i )
                             : pObserver( o ), Info( i ) {}
        IObserver* pObserver;
        observer_info_t Info;
    };
    
    typedef std::vector< observer_entry_t > clients_t;
    clients_t   m_aObservers;
};

The SimpleSubject class provides an easy implementation of the IObserver interface and uses a std::vector to store observer_entry_t elements. Each element represents an attached observer with its attribute. The sample application shows how you can use the attribute by overriding SimpleObserver::NotifyObserver.

The ObserverAdapter class

template<class OUTER, class SUBJECT>
struct ObserverAdapter : public SUBJECT::IObserver
{
    // type of outer class
    typedef typename OUTER outer_t; 
    // returntype of callback fn  
    typedef typename SUBJECT::return_t return_t;  
    // type of callback fn argument
    typedef typename SUBJECT::arg1_t arg1_t;    
                                                    
    // type of a pointer to member fn in class outer
    typedef return_t (OUTER::*mfunc_t)(arg1_t);     
                                                     

    ObserverAdapter( outer_t* pObj = NULL, 
                 mfunc_t pmfUpdate = NULL )
                 : m_pObj( pObj ), 
                     m_pmfUpdate( pmfUpdate ) {}

    virtual return_t Update( arg1_t arg1 = arg1_t() )
    {
        return (m_pObj->*m_pmfUpdate)( arg1 );
    }

    virtual void SetUpdate( outer_t* pObj, 
                             mfunc_t pmfUpdate )
    {
        m_pObj      = pObj;
        m_pmfUpdate = pmfUpdate;
    }

    outer_t*    m_pObj;
    mfunc_t     m_pmfUpdate;

};

This is obviously the most interesting class that contains only a few lines of code. It gets two template parameters: The type of the outer class (OUTER) and the subject interface (SUBJECT). The first is needed to declare the pointer-to-member-function type mfunc_t. The latter is used to derive from the actual IObserver interface and to get the appropriate return_t and arg1_t types. The callback function to use can be passed to the constructor, or can be changed later by the SetUpdate() method. Both get two parameters: The instance of an object you want to process the callback and the address of a member function to call. The member function's prototype has to be of the form [virtual] return_t func(arg1_t). (It does not matter if the member function has been declared virtual or not, virtual is not part of a member function's signature.) Whenever the subject calls Update(), the real work is just delegated to the callback. The stored member function's address m_pmfUpdate is called in the context of the stored object m_pObj and passed the given parameter arg1.

The sample application

The sample application is a simple dialog-based MFC app. It implements the example of a weather forecasting system using the above subject and observer classes. It also shows how to use the template parameters to utilize the per-observer attribute by overriding SimpleSubject::NotifyObserver(). The example classes of our weather forecasting are declared and implemented in the file weather_forecast.h. The apps dialog can be used to simulate events like "temperature change" or "power failure" and show that they are propagated to the observing objects.

Summary

Design patterns are great for developers and it prevents them from reinventing the wheel and helps them to look at the design on a high level of abstraction. However, even if we do not have to reinvent the wheel, we have to rebuild it each time, because of the existing gap between the reusable design and reusable code. It has been shown that naive implementations usually involve structural dependencies which inhibit their application in real world projects. These (bad) structural dependencies are related to the usage of inheritance. We can solve this problem by replacing inheritance with aggregation and using the delegation technique. In combination with callbacks to member functions this is a very flexible approach that allows us to build pluggable components and generic features without making any assumptions about the environment they are used in.

References

  • [1] Erich Gamma, Richard Helm, Ralph Johnson, Jon Vlissides : "Design Patterns - Elements of Reusable Object-Oriented Software". Addison Wesley, 1995.
  • [2] T. Kulathu Sarma : "Applying Observer Pattern in C++ Applications", 2001.

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
Germany Germany
Daniel Lohmann (daniel@losoft.de) is Assistant Professor at the Distributed Systems and Operating Systems department at Friedrich-Alexander-University Erlangen-Nuremberg, Germany. His main research topic is the design of a highly customizable and scalable operating system product line for deeply embedded systems using static configuration and aspect-oriented techniques. Before joining Universität Erlangen he worked as a freelance trainer and consultant for NT system programming, advanced C++ programming and OOA/OOD. He is interested in upcoming programming techniques like aspect-oriented programming, generative programming and C++ meta coding and has written some nice and handy tools for Windows NT which you can download at his web site.

Comments and Discussions

 
Generalportable Pin
samael_25-Nov-06 15:46
samael_25-Nov-06 15:46 

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.