News

Implementing the Java Delegation Event Model and JavaBeans Events in C++

Craig Larman is the author of Applying UML and Patterns and the forthcoming Java 2 Style and Idiom Guide, in addition to a regular column "Modeling and Java" in Java Report. He is Principal Instructor at ObjectSpace, a company specializing in object technologies, and can be contacted at [email protected].

THIS ARTICLE EXAMINES the design of the delegation event model (DEM) used in Java and how it may be implemented in C++. In addition, the design and C++ implementations of JavaBeans-style bound and constrained properties are presented. DEM experts will note that some minor modifications have been made to take advantage of multiple implementation inheritance in C++, and to make some simplifications.

Why bother to learn about an implementation of the DEM in C++? Three reasons:

  1. The DEM is a relatively elegant and efficient publish-subscribe style design, worthy of emulation in C++.
  2. Java—and thus the use of the DEM—is widespread, and many C++ developers need to work in both languages. Thus, it is advantageous to use similar publish-subscribe architectures across languages in order to increase the congruence in designs, improve porting opportunities, and reduce the cross-language learning curve.
  3. To promote examples of interface-based design in C++, which is a Good Thing.
INTERFACES IN C++ AND THE UML
First, a definition of interfaces and interface implementation in the context of C++ is required because Java supports interfaces, which are used in the DEM. Readers who understand this topic may wish to skip this section. Informally, an interface specifies a set of one or more operation signatures; for example, the ILowAltitudeListener interface defines the operation signature onLowAltitude (see Fig. 1). It is a relatively common convention to name interface classes starting with the capital letter "I." In the Unified Modeling Language (UML), the element names are italicized to indicate that they are abstract, and the interface name is stereotyped with «interface» to indicate it as such.


Figure 1. An interface in the UML notation.

In C++, interfaces are defined by pure abstract base classes that only contain pure virtual function definitions, such as:

class /* INTERFACE */ ILowAltitudeListener
{
public:
   virtual void onLowAltitude( LowAltitudeEvent* evt ) = 0;
};

In C++, interface implementation is achieved by publicly inheriting from the interface and implementing the abstract operations. In addition, it is common that multiple inheritance is present; the derived class often inherits the interface specification from the interface and the implementation details from another superclass (Figure 2). In the UML, interface implementation is indicated with a dashed-line version of the generalization-specialization association, as opposed to the regular solid-line version.


Figure 2. Interface implementation and subclassing in the UML.

The derived C++ class that implements an interface must, of course, provide a member function body for the abstract operations it inherits:

class AirTrafficControl :
    public Organization,
    public ILowAltitudeListener
{
public:
    virtual void onLowAltitude( LowAltitudeEvent* evt )
    {
        cout < "warning:="" low="" altitude="" plane";="" }="" ...="" };="">

THE JAVA DELEGATION EVENT MODEL
The DEM is an implementation of the Observer or Publish-Subscribe pattern.1 Publishers are capable of generating and sending (publishing) events; subscribers register interest (subscribe) in the events of particular publishers. When a publisher has an event, it notifies all the subscribers interested in that event.

A basic design goal of Publish-Subscribe (e.g., the DEM) is to provide a form of loosely coupled signaling from a publisher to subscribers, and a means to dynamically register/deregister subscribers with a publisher. A common implementation technique to achieve low coupling from publisher to subscriber is to design in terms of interfaces, rather than regular classes. This will be illustrated in the following sample implementation.

In DEM nomenclature, the publisher is called the event source, and the subscriber is the event listener.

Events are represented as classes of objects, and the publishing of an event is implemented as a regular message send (synchronous member function invocation).

Most of the concepts presented in the first part of this article are summarized in Figure 3, which illustrates a sample use of the DEM with an Airplane, which is a source of LowAltitudeEvents, and AirTrafficControl, a listener for these events. The DEM is quite simple, so this diagram might provide enough insight to proceed directly to the second major section on JavaBeans-style bound and constrained properties.


Figure 3. An example use of the DEM.

Event Objects Events in the DEM are represented as objects, and all derive from class EventObject, which is responsible for knowing the source object of the event (Figure 4). It is common, though not required, that subclasses of EventObject add additional properties pertaining to the event. For example, a KeyboardEvent could know the key pressed, and so forth. A sample definition of EventObject is:

template 
class EventObject
{
public:
    EventObject( SOURCE_TYPE* source )
       : m_source( source ) {}

    virtual ~EventObject() {}

    SOURCE_TYPE* getSource() { return m_source; }

private:
    SOURCE_TYPE* m_source;
};


Figure 4. Events are represented as objects that know their source.

The most common difference between the Java implementation and this C++ implementation of EventObject and other classes in the DEM is the inclusion of parameterized types in the C++ version (for example, for the event source class). In contrast, the Java version declares the event source as of class Object—the root of all Java classes—and requires downcasting from class Object for most usage. Of course, the template classes in C++ eliminate the need to cast.

Event Listeners (Subscribers) Event listener classes (i.e., subscribers) are responsible for responding to event notification when an event source sends them a message. In effect, the event source says to the listener, "Hey! I'm having an event," and the listener must react to that notification.

A concrete event listener must implement a particular interface for an event. By convention, the interface names all end with the term Listener, such as ILowAltitudeListener. The name of the listener operation(s) ideally suggests the signaling of the event, such as onLowAltitude or alarmRaised.

It is required that all event-handling operations must receive the EventObject (or a subclass) as a parameter, such as onLowAltitude(LowAltitudeEvent*) (see Fig. 5). This gives the listener potential visibility back to the source object of the event because an EventObject knows its source.


Figure 5. Examples of Listener interface names and operations signatures.

In the C++ implementation, the listener interface requires a template parameter for the event source type because the event object parameter definition requires it. An example is:

template 
class /* INTERFACE */ ILowAltitudeListener
{
public:
    virtual void onLowAltitude( 
        LowAltitudeEvent* evt ) = 0;
};

Concrete event listener classes that implement the listener interface are responsible for responding to event notification when an event source sends them a message. To repeat a previous example, the AirTrafficControl class implements the ILowAltitudeListener interface (Figure 2), and is thus ready to subscribe to listen for LowAltitudeEvents. When an event source sends an onLowAltitude message to an AirTrafficControl instance, it reacts to the "event" as it wishes, such as displaying a message:
class AirTrafficControl :
    public Organization,
    public ILowAltitudeListener
{
public:
    // this event handling method is called when a 
    // source "has an event" and sends this message.

    void onLowAltitude( LowAltitudeEvent* evt )
    {
        // in contrast to the Java DEM, the parameterized event
        // class avoids the need to down-cast the event source

        Airplane* plane = evt->getSource();

        cout < "warning:="" low="" altitude="" plane";="" }="" snip="" ...="" };="">

Event Sources (Publishers) An event source (i.e., publisher) has two responsibilities:
  • Register listener objects.
  • Notify listener objects when an event happens by sending them a message.
Any number of classes can be a source of the same event. For example, both Airplane and Bird can be a source of the LowAltitudeEvent.

The event source class must implement methods to register—add and remove—listeners. By convention, the signature pattern for the registration methods will usually be:


void addFooListener( FooListener* )
void removeFooListener( FooListener* )

Why addFooListener rather than a more generic method name such as simply addListener, overloaded by parameter type? The latter is not sufficient because the class of the actual parameter can implement multiple listener interfaces; thus, overloading on parameter type is not a sufficient discriminator.

For example, if Airplane is a source of LowAltitudeEvents, it must be able to register ILowAltitudeListener instances. The group of registered listeners will probably be saved in an STL container, such as a vector. To illustrate:

class Airplane
{ 
public:
    void addLowAltitudeListener( ILowAltitudeListener* lis )
    {
        // add lis to a container
    }

    void removeLowAltitudeListener( ILowAltitudeListener* lis )
    {
       // remove lis from a container
    }

// ...
};

Broadcasting Events from a Source to Listeners When an event source "has an event," it must notify all registered listeners by broadcasting an event notification message to them. In the DEM, this is called event multicasting. The event source class requires a method, such as broadcastLowAltitude, to perform this. By convention, this method takes an already existing event object as a parameter, so the event object can be created in some other method and passed along. For example:
class Airplane
{ 
public:
    // broadcast the event to all listeners

    void broadcastLowAltitude( LowAltitudeEvent* evt )
    {
        for each listener lis in the container,
            lis->onLowAltitude( evt )
    }

    // modify the altitude, and possibly signal
    // event multicasting

    void setAltitude( int alt )
    { 
        m_altitude = alt;

        // if necessary, broadcast the LowAltitudeEvent

        if ( alt < getaltitudethreshold()="" )="" {="">* evt =
                 new LowAltitudeEvent( this ) ;

             broadcastLowAltitude( evt );

             delete evt ;
        }
    }
// snip ...
};

The Abstract Client Pattern Not only does the DEM illustrate the Publish-Subscribe pattern, but also the Abstract Client pattern.2 When a server object (such as an event source) needs to register callbacks on client objects and send messages to clients (such as event listeners), lower coupling is achieved by requiring that the client objects implement a client interface. The server object only knows about the type of the client in terms of its client interface type. The concrete clients inherit from—or implement—an abstract client interface, hence the pattern name Abstract Client.

In the DEM, the listener interface definition is the Abstract Client definition, and the event sources are the servers that register callbacks on the event listener clients. The event-handling method that a listener implements is the callback method.

Adding a Common EventSourceMulticaster Superclass The previous implementation of the event source class Airplane can be improved upon in C++. Because the responsibilities of listener management and broadcasting are invariably the same for all event sources, it is desirable to factor the listener management and broadcasting code into a common abstract base class such as EventSourceMulticaster (the Java DEM calls such classes multicasters). The only points of variation are the source type, which can be specified with a parameterized type, and the specific listener method to invoke, which can be specified with a function pointer. Please see Listing 1 for an example implementation.

The base EventSourceMulticaster can be further specialized into specific derived event source multicaster subclasses, which fill in the details required by the very generic EventSourceMulticaster class. For example, a LowAltitudeEventSource class definition is shown in Listing 2. Note that the derived multicaster inherits privately from the base multicaster, because we want to ensure that none of the base class behavior will ever encroach upon the ultimate concrete derived class, such as Airplane.

Once these helper abstract superclasses are defined, we can finally derive our concrete event source classes from them, and inherit the correct behavior for being an event source.

class Airplane :
    public LowAltitudeEventSource
{
    // snip ...
};

For example, the Airplane class is a source of LowAltitudeEvents. It will inherit the appropriate broadcastLowAltitude, addLowAltitudListener, and removeLowAltitudListener methods. Figure 6 illustrates the design using these helper classes.


Figure 6. Example implementation of the DEM, with helper multicaster classes.

PUTTING IT ALL TOGETHER
We have defined the following components:

  • AirTrafficControl (a LowAltitudeListener)
  • Airplane (a source of LowAltitudeEvents)
  • Various supporting components (ILowAltitudeListener, LowAltitudeEvent, and so on)
With the pieces in place, we can register a listener with a source and cause an event to be signaled. For example:
// Airplane is a source of LowAltitudeEvents.

Airplane plane;

// ATC (AirTrafficControl) is a listener
// for LowAltitudeEvents

ATC atc;

// register the ATC as a listener with
// the source.

plane.addLowAltitudeListener( &atc );

// change the plane's altitude so that it emits
// a LowAltitudeEvent. the ATC::onLowAltitude
// method will be invoked.

plane.setAltitude( 50 );

JAVABEANS-STYLE BOUND AND CONSTRAINED PROPERTIES
The simple Publish-Subscribe model provided by the DEM can be used in myriad ways to support loosely coupled signaling, and the dynamic registering of listeners on sources. One example is to implement support for what in JavaBeans are called bound and constrained properties.

Bound Properties A bound property is a logical attribute of a class that emits a PropertyChangeEvent when the property changes. A PropertyChangeEvent knows the property name and old and new values. For example:

template 
class PropertyChangeEvent :
    public EventObject
{
public:
    // snip ...

    VAL_TYPE*   getNewValue() { return m_newValue; }
    VAL_TYPE*   getOldValue() { return m_oldValue; }
    string  getPropertyName() { return m_propertyName; }

    // snip ...
};

To illustrate, if destination is a bound property in Flight, then the setDestination mutator will create and broadcast a PropertyChangeEvent using a helper multicaster base class named PropertyChangeEventSource, as follows:
class Flight :
    public PropertyChangeEventSource
{
public:
    void setDestination( string dest )
    {
        string old = m_destination;
        m_destination = dest;

        PropertyChangeEvent* evt =
            new PropertyChangeEvent
                (this, "destination", &old, &dest) ; 

        broadcastPropertyChange( evt ) ;

        delete evt ;
    }

    // snip ...
};

The IPropertyChangeListener interface and an example are illustrated in Figure 7.


Figure 7. Bound properties and IPropertyChangeListeners.

What are bound properties good for? One common use is when data widgets in a window need to refresh when the data they are associated with changes. For an example in Java, a TextField in a FlightApplet (a kind of graphical panel displayable in Web browsers) may display the destination property of a Flight instance. The FlightApplet can be an IPropertyChangeListener on a Flight instance. When the Flight destination changes, the Flight can emit a PropertyChangeEvent to the FlightApplet, which then refreshes the TextField widget with the new value. To generalize this case: Bound properties are useful to communicate state changes in a domain layer of objects up to a presentation layer of windows that are visualizing domain state, while maintaining low coupling from the domain to presentation layer (which is a desirable design goal). This technique can also be applied in C++ to the Document-View framework in Microsoft's MFC.

Constrained Properties A constrained property is a logical attribute of a class that emits a VetoableChangeEvent when the property is attempting to change. Like PropertyChangeEvents, a VetoableChangeEvent knows the property name and old and new values. The key idea is that the listener may veto the potential property change, and the change will be aborted. The "vetoing" of a change request occurs with C++ exceptions. The exception may be thrown before the line of code that would actually cause the data member to be assigned.

To illustrate, if altitude is a constrained property in ConstrainedAirplane, then the setAltitude mutator will create and broadcast a VetoableChangeEvent using a helper multicaster base class named VetoableChangeEventSource. Note that the AirTrafficControl listener may throw an exception, thus causing an exit from the setAltitude method before the data member change can occur.

First, here is the ConstrainedAirplane class that has the constrained property:

class ConstrainedAirplane :
    public VetoableChangeEventSource
{
public:

    void setAltitude( int alt ) throw (PropertyVetoException)
    {

        cout < endl="">< "before="" veto="" "="">< alt="">< endl="" ;="">* evt =
            new VetoableChangeEvent
                (this, "altitude", &m_altitude, &alt) ;

        // ask my listeners if they want to veto the change.
        // a PropertyVetoException can occur here

        try { broadcastVetoableChange( evt ) ; }
        catch (...)
        {
            delete( evt ) ;
            throw ;
        }
    
        // only if we get past the above attempt
        // do we change the property value

        cout < "success.="" no="" veto="" "="">< endl="" ;="" delete(="" evt="" )="" ;="" m_altitude="alt" ;="" }="" snip="" ...="" };="">

Second, in the following code is the AirTrafficControl class that is an IVetoableChangeListener, and which vetoes the change if the new altitude is less than some threshold.

Presumably, at some point an AirTrafficControl instance has registered as an IVetoableChangeListener with the ConstrainedAirplane. The vetoableChange method is the event-handling callback method that the listener must implement, and which does the source object invoke when it is attempting to change a constrained property, such as the altitude property in the ConstrainedAirplane.

class AirTrafficControl :
    public IVetoableChangeListener
{
public:

    void vetoableChange( VetoableChangeEvent* evt )
        throw (PropertyVetoException)
    {
        int altitude = *( evt->getNewValue() );

        if ( altitude < getaltitudethreshold()="" )="" {="" cout="">< endl="">< "vetoing="" the="" change"="">< endl;="" throw="" propertyvetoexception();="" }="" }="" snip="" ...="" };="">

What are constrained properties good for? In Java, they are rarely used; bound properties are much more common than constrained. One possible use is for delegated data validation. An IVetoableChangeListener can be a data validator, which examines potential new values and may reject some. If you use them this way, you may wish to set a meaningful message in the PropertyVetoException, so that the exception handler can discern, and perhaps display, details about the rejected data.

Source Code The complete source code for this article that demonstrates the basic DEM implementation, plus support for bound and constrained properties, can be found at www.oma.com/C++Report/Issues/Apr99/Larman.

CONCLUSION
In this article we examined a C++ implementation of the Java DEM. It deviates from the Java implementation to take advantage of multiple implementation inheritance for common event source responsibilities. In addition, we explored the use of the DEM to support bound and constrained properties—an idiosyncratic use of the DEM for notification to listeners when object properties in a source object change.

References
1. Gamma, E. et al. Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, Reading, MA, 1995.
2. Martin, R. Pattern Languages of Programming, Vol. 1, Prentice-Hall, Englewood Cliffs, NJ, 1996.