Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Encapsulating the Observer Pattern


October 1998/Encapsulating the Observer Pattern


A fundamental part of programming is the generation and handling events. All sorts of objects generate events — these events can be anything from "KeyDown" to "EarthQuakeWarning". An event can manifest itself and be handled in many ways. For a Windows SDK programmer, in the posting and receiving of a message. When using a class library (e.g., MFC), handling an event might involve calling a virtual function. Event processing poses a number of problems which often go unresolved.

A common pattern for handling events is known as the Observer pattern, also known as Publish-Subscribe or Multicast, as documented in Design Patterns [1]. A single subject (or publisher), sends events to one or more observers (the subscribers). Each observer implements an interface, supported by the subject, and registers with the subject to receive events. For example, if the subject is an earthquake-monitoring station, dissimilar observers representing the fire service, police, and a research institute might register interest in receiving events from the monitoring station. It is also possible to implement a "unicast" pattern, in which there is a single observer. While unicast is simpler to implement, and has other advantages, this article concentrates on the challenges of implementing multicast.

Implementation Challenges

The observer pattern is common in current software implementations. It is found in the document-view architecture. It is also the basis for how events are fired using Microsoft's COM (Component Object Model). The advantages to this pattern are the support for multiple observers, which require no knowledge of each other, and the minimal coupling between each observer and the subject. The challenges in using this pattern are:

  • passing parameters with the event
  • maintaining efficiency
  • avoiding reentrance
  • maintaining type safety
  • encapsulating the pattern within a reusable module

Nowhere have I found support for firing events that meets these challenges. The code presented in this article does so, and is generic. A version supporting COM is available electronically on the CUJ ftp site (see p. 3 for downloading instructions).

Passing Parameters

Passing parameters with the event is known as the push model. This model has the advantage that the observers do not need to make inquiries of the subject to find out how it has changed. Passing the magnitude of the quake as a parameter enables the fire and police service to ignore minor tremors. In contrast, if the implementation does not pass parameters with the event, each observer must re-read data from the subject. This is known as the pull model.

Efficiency

In many implementations of Observer, efficiency is neglected. Consider an AutoPilot object with the properties Speed, Course, and Altitude, that fires an event named Changed when one of its property values changes. The following code can result in three events being fired:

void CObserver::Foo(unsigned x, unsigned y, unsigned z)
{
   if (AutoPilot != NULL) {
      AutoPilot->SetSpeed(x);
      AutoPilot->SetAltitude(y);
      AutoPilot->SetCourse(z);
   }
}

Typical implementations of these three autopilot functions call code similar to the following:

void CAutoPilot::FireChanged() {
   for each observer
      Observer->OnChanged();
}

In such implementations Foo generates three events when logically only one has occurred. An implementation could avoid signaling the event within each of the autopilot functions SetSpeed, SetAltitude, and SetCourse, and require clients, such as callers to Foo, to signal the event instead. But clients are bound to forget.

Avoiding Reentrance

Reentrance occurs when an observer stimulates the subject by calling its own function, as Foo (above) does. As seen above, calling Foo triggers three separate events which are sent to all observers. The client object will thus be called back (via CObserver::OnChanged) before CObserver::Foo has completed.

An observer's OnChanged function should, where possible, avoid changing its own state or that of the subject. Consider what happens when Foo(0,0,0) is called and the observer's event handler is defined as follows:

void CObserver::OnChanged() {
  if (AutoPilot->GetSpeed() == 0 &&
    AutoPilot->GetAltitude() == 0)
      AutoPilot = NULL; // Landed
}

1. Foo calls AutoPilot->SetSpeed(0), which results in a call to the observer's OnChanged function. Nothing happens within OnChanged — the if statement is not executed.

2. Foo calls AutoPilot->SetAltitude(0), which results in another call to the observer's OnChanged function. This time the if statement within OnChanged is executed, and a state change occurs as AutoPilot is set to NULL.

3. Foo attempts to call AutoPilot-> SetCourse(0), but AutoPilot is NULL. SetCourse(0) is never executed and the program crashes.

Type Safety

Type safety is typically achieved by using a custom function to fire each event. The solution used by MFC is to provide a function with variable arguments and then wrap it in a short wizard-generated function:

void COleControl::FireEvent(DISPID dispid,
     BYTE FAR* pbParams, ... );

void MFCSubject::FireWarning
    (double Magnitude)
{
    FireEvent(eventidWarning,
              EVENT_PARAM(VTS_R8),
              Magnitude);
}

The wizard-generated function gives the illusion of type safety, but relies on the programmer specifying the correct parameter types to the wizard. Variable arguments, as used by printf, eliminate type safety. Using wizards to generate code does not provide good encapsulation.

A Solution

I have written a class Multicast to encapsulate the behavior of the subject (see Figure 1). The interface to the class comprises the following functions:

bool Advise(IObserver *);
void Unadvise(IObserver *);
virtual bool OnActive();
virtual void OnInactive();
void FireEvent(func, parameters);
void FireSingleEvent(func, parameters);

To register to receive events each observer calls function Advise, passing its this pointer as an argument. To stop receiving events an observer calls function Unadvise. The subject can override OnActive and OnInactive to receive notification of when the first observer registers and the last observer de-registers.

Passing Parameters to Observers

To fire an event the subject calls FireEvent, passing a pointer to the observers' member function intended to handle that event. The subject also passes between zero and n parameters. Alternatively, the subject can collapse multiple events (such as the autopilot's Changed event) into a single event by calling FireSingleEvent.

Event firing is simple. In the example below, DisasterTest triggers an earthquake "warning" event of magnitude 6.5:

class IQuake {
   virtual void
   Warning(double Magnitude) = 0;
    .
    .
class QuakeMonitor :
  public Multicast<IQuake> {
   void DisasterTest() {
     FireEvent(IQuake::Warning, 6.5);
    .
    .

The subject and the events it generates are separate entities. Figure 1 shows the two principal classes, Event and Multicast.

The Multicast class gives the illusion of possessing functions with variable arguments. It accomplishes this by providing several overloaded versions of the same template function:

template<class F>
   void FireEvent(F f);
template<class F, class P1>
   void FireEvent(F f, P1 p1);
template<class F, class P1, class P2>
   void FireEvent(F f, P1 p1, P2 p2);

To support events with up to n parameters, n+1 overloaded functions are required. For brevity, Figure 1 shows only two implementations of FireEvent and FireSingleEvent. Code for more parameters can be easily cloned, and is available electronically.

Duplication of code occurs in the reusable Multicast class, not in developers' code. To avoid bloat, each overloaded function, if it is instantiated, constructs an Event object of the correct type, and delegates the rest of the work to a plain old member function. If you find yourself passing more than about seven parameters you should probably consider passing structures or subobjects.

Avoiding Reentrance and Increasing Efficiency

Calling the observer asynchronously enables the implementation to avoid reentrance. Asynchronous calling also enables efficiency to be improved by coalescing equivalent events. When the subject makes multiple calls to FireSingleEvent, the first call creates an event, stores it in a collection, and then triggers an asynchronous function call. Subsequent calls to FireSingleEvent initiate a search of the collection for an equivalent event.

The implementation arranges for the operating system to call the function AsynchEvent a short time later (the time interval is unimportant). Even with a minimal time interval, separate events, such as the three OnChanged events generated by CObserver::Foo (above), can be coalesced. What is important is that AsynchEvent is not called from within an observer's function; instead it is called as soon as the program returns to a quiescent state. It is this procedure that prevents reentrance. When AsynchEvent is called it invokes all events in the collection and then discards them.

Thus, duplicate events can be dropped, but which ones? I have chosen to drop the older events in favor of the most up-to-date. Which event to drop is of significance only if there are parameters associated with the events in question.

Invoking a function asynchronously is a platform-specific operation. On Win32 platforms the traditional method is through posting and receiving a Windows message. However, QueueUserAPC can also be used.

To call an observer's interface function requires an interface pointer, a member function pointer, and the parameters to be passed:

    1) IQuake *
    2) void (IQuake:: *)(double)
    3) double

Each observer's IQuake interface pointer is supplied via the Advise function to the subject, which stores them. When the subject fires an event, it passes the event's member function pointer, and the required parameters, to FireEvent or FireSingleEvent. FireEvent and FireSingleEvent use these parameters to create an Event object.

The event class inherits from AbstractEvent, which defines the interface between the event and the subject, including the operator==. My implementation considers events to be equivalent if they call the same function (even with different parameter values). Events are compared by their member function pointers, but to make this comparison the pointers must be of the same type. Each of the member functions in IFoo below has a different type.

struct IFoo {
   void Func1(int);
   void Func2();
};

The types are respectively:

void (IFoo:: *)(int);
void (IFoo:: *)();

The syntax may be a little confusing, but these are clearly different types, which cannot be converted to void *. (The size of a function pointer could be different from the size of a data pointer.) A member function pointer can be converted only to another member function pointer. The event class implements a virtual function Value, which converts the member function pointer to a fixed type. The value returned by Value is used by the operator== to compare two events. (It would be difficult to define operator< because this operator is not defined for function or member function pointers. After all, the address of functions is determined by the linker, and could be different for each build.)

Maintaining Type Safety

Event objects store a member function pointer and between zero and n parameters. To enable calling of the event function this implementation provides n+1 versions of the template member function InvokeImpl. The first version calls the event function, passing zero parameters; the last version passes n parameters. For each event object only one of these functions is valid; for example, it is only valid to call IQuake::Warning with one parameter. The compiler must be able to decide at compile time which version to invoke.

The implementation manages this by counting the parameters supplied to the object. The default parameter type is DefType. I have specialized a template class ArgC such that ArgC<T>::Count has a constant value of 1 for all types except DefType, for which Count is zero. This technique is also used by the <limits> header in the Standard Library.

The number of parameters supplied is:

ArgC<P1>::Count + ArgC<P2>::Count + ...

The template member function InvokeImpl cannot be overloaded on a value, but by creating a unique type for each of the values 0 to n it can be overloaded on each of those types:

template <unsigned> class NumType {};
template <class T>
   void InvokeImpl(T * p, NumType<0>);
template <class T>
   void InvokeImpl(T * p, NumType<1>);

The virtual function Invoke is now able to choose which version of InvokeImpl to instantiate by passing an object of type NumType<ArgCount>. Finally, each version of InvokeImpl calls the observers' member function using operator->*.

(void)(pObserver->*pFunc)(p1);

The left-hand argument of operator->* is an interface pointer, the right-hand argument is a pointer to a member function of that interface, followed by zero or more parameters. The compiler checks the parameters passed to the member function just as it would any other function.

When using the Multicast class, the parameters passed to FireEvent and FireSingleEvent are not required to have the same type as the parameters of the event function, but the compiler must be able to convert the parameters to the type expected. If it can't perform the necessary conversions the compiler generates an error. Unfortunately, the error may not be generated at the site of the real problem.

Miscellaneous Issues

To complete my presentation of the Multicast class, I discuss how it should be copied and assigned. Finally, I discuss the implications of using the class in a multithreaded environment.

Copying and assignment of subjects using the default C++ functions would copy the currently subscribed observers and outstanding events. This would be dangerous. For example, a new subject might fire events to observers that no longer exist. There are two obvious solutions: either prevent copying and assignment by making the functions private or implement benign functions (which do nothing), thereby providing an interface for copying and assignment to classes deriving from or containing the Multicast class.

Thread safety concerns more than just protecting access to the subject's data. Users must also be aware of the risk of deadlock. For deadlock to occur an attempt must be made to hold more than one lock at a time. Holding a lock from within AsyncCall, while calling the observers' functions, is taking one step on the path to deadlock. On the other hand, if no lock were held, the risk would exist that an observer could be destroyed while simultaneously being called from within AsynchCall. Therefore observers must be aware that when an event function (such as OnChanged) is called, the subject will be locked. Thus all access to the subject from other threads will be blocked until the event function returns.

Consider the following scenario: An observer's event function attempts to call a function of another observer that is already blocked, in another thread, waiting to access the subject. If the second observer is locked, it will not unlock itself until it can access the subject. The first observer becomes blocked waiting for the second observer, and the second observer remains blocked waiting to access the subject — deadlock!

To avoid deadlock with my implementation, the observers' event functions should avoid calling external objects, which might be locked. These functions should especially avoid calling other objects with direct or indirect access to the subject. Dealing with multiple threads is a complex issue to which whole books are dedicated. An in-depth discussion is beyond the scope of this article.

Summary

I have shown how it is possible to avoid using wizard-generated code and still provide an efficient, type-safe, and generic implementation of the subject side of the Observer pattern. Along the way I have shown how to simulate variable arguments and count arguments at compile time, all while maintaining type safety. I have also touched on some issues concerning the manipulation of member function pointers. The code uses many advanced features of C++. I used Visual C++ 5.0; the mileage from other compilers may vary.

The situation often arises in which the user has a more elementary need to call member functions, of the same object, asynchronously while maintaining type safety. The same techniques I demonstrated can be applied and much of the multicast code reused.

Acknowledgements

My thanks to Alan Stokes, Terry Way, and Sunil Kalkunte for reviewing this article, and to my colleagues at RCP Consultants for inspiring it.

References

[1] Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns — Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995).

Mark Bartosik is a software engineer specializing in C++, COM, and Win32 at RCP Consultants, and can be reached at [email protected] or [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.