flipCode - Tech File - Jeroen Bouwens [an error occurred while processing this directive]
Jeroen Bouwens
Click the name for some bio info

E-Mail: flipcode@jeroen.ws



   09/10/2000, Observe Your Objects


Today, in part 4 of my series on OOP programming, I would like to introduce the Observer pattern.

Why Use It?


I think it is absolutely great when I create a class and I find out I can use in different applications. After all, that's one of the reasons to use Object Orientation. One of the main problems that prevents a lot of reuse is tight binding between classes. Tight binding means that one class has to know about another class in order to function. This is not necessarily a Bad Thing, but it becomes one as soon as this knowledge of other classes is tied to a single application.

A good example of what I unfortunately did in the past is binding between Functionality and the User Interface. When I'm developing something, I like to see what's going on inside my objects. So I usually make a simple UI that displays all sorts of information like number of faces/vertices, timing profile, memory-usage, fps etc. This often resulted in either my functional class (e.g. 3D-engine) having to know about UI-elements, or my UI-class containing functionality that should really be in the 3D-engine class. Both cases prevent reuse because the Functionality needs the UI to function properly, and a UI is very application specific.

The Observer pattern allows an object to let other objects know about its internal values without knowing anything specific about the classes of these other objects.



How To Do It


Observer uses inheritance to achieve its goal. The first thing to do is to create two base-classes, which I dubbed TEventServer and TEventClient (You can see I use a Borland compiler :) ). These classes look something like this:

class TEventClient
{
    public:
        TEventClient();
        ~TEventClient();

        virtual void ParameterUpdate() = NULL;
};

class TEventServer
{
    public:
        TEventServer();
        ~TEventServer();

        bool            Attach(TEventClient* Client);
        bool            Detach(TEventClient* Client);
        void            ParameterUpdate();

    private:
        TEventClient**  m_pClientList;
};
As you can see, TEventServer has three methods. Attach() adds a pointer to a TeventClient to the ClientList. Detach() removes it from this list. ParameterUpdate() loops through the ClientList and calls the ParameterUpdate method of all clients in the list. The good bit here is that you can have lots of clients. So, for example, all the 3D-objects in you world may want to know about the often changing gravity of the world they reside in. Instead of having all your 3D-objects poll the "world" for the current value/direction of gravity on a regular basis, you can let the world call all objects ONLY WHEN NEEDED. This is clearly advantageous when the number of objects grows very large.

The class containing the functionality of your fancy 3D-engine derives from TEventServer. The UI displaying all sorts of interesting information about the engine is derived from TEventClient and implements the ParameterUpdate function. The UI Attaches itself to the engine by calling Attach(this). In the ParameterUpdate function of the UI, it finds out about the values of parameters/variables of the engine it wishes to display and updates its labels/edit-boxes/statics etc.



How To Improve It


The classes I showed above do the job, but pretty naïve. For instance, if your server exposes a hundred variables you have to update all hundred of them in the UI for every Update. This is because the Clients have no way of knowing which parameters need to be updated. There are several ways to improve on this. One way I have seen is to give each parameter a text-name, and pass this name as a parameter to the Update method. This goes something like this:

In the server:

void ParameterUpdate(string ParameterName);
...
...
m_iNumOfFaces++;
ParameterUpdate("NumOfFaces");
And in the client:

void ParameterUpdate(string ParameterName)
{
if (strcmp(ParameterName, "NumOfFaces"))
	//Update UI-element representing num of faces.
}
The reason to use this is that it gives you pretty readable code: You can see in the client-code which parameter is changed, and what action this results in, without looking in the Server-code. I would never use this method, though, because it's a performance-killer: Passing strings and comparing them is a relatively slow. In my implementation, I use parameter-groups, which are identified by a number:

In the server:

void ParameterUpdate(unsigned int ParameterGroup = 0);
...
...
m_iErrorCode = 5;
ParameterUpdate(1);
And in the client:

void ParameterUpdate(unsigned int ParameterGroup)
{
    switch (ParameterGroup)
    {
	case 0:
		//General parameter group
        case 1:
	        //Error parameter-group
        break;
    }
}
This allows you to decide what granularity you want: You can have all parameters in one group, or you can have as many groups as parameters

Another possible improvement is to implement the "push"-model. As you can see, in the implementation I gave, the client (e.g. UI) is responsible for getting the necessary information it wants to display. The push model means the server passes information about what changed and what the new values are. There are several advantages to this method, but there is again a performance penalty because a lot more data is transferred between functions.



Conclusion


Observer can reduce tight binding of classes, thereby reducing tight binding, and offers an interesting approach in situations where some kind of "many-to-one" relationship is desirable. This can go beyond a simple mechanism to inform others about internal the values of an object. Observer can be the basis for an elaborate event-driven system, similar to the windows event-system, where objects attach to multiple servers and respond to a variety of different kinds of events, ranging from user-input to network-activity.

As always, any feedback is appreciated (Well, as long as it's positive anyway :-) ).

By the way, on a totally unrelated note: Is anyone interested in setting up a flipCode Q3A contest/tournament? Would be a lot of fun, I think.

Happy coding,
      Jeroen.





  • 04/01/2002 - Thoughts On Software Development
  • 09/10/2000 - Observe Your Objects
  • 01/31/2000 - Factories
  • 09/14/1999 - Polymorphism and Inheritance
  • 08/14/1999 - Object Design
  • 06/04/1999 - OpenGL Mania
  • 05/04/1999 - 3D Clipping
  • 04/29/1999 - Introduction

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    Download The Sample Program: ManicMotion.exe

    [an error occurred while processing this directive]