Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

The 'event generator' idiom

How and when to make a Java class observable

  • Print
  • Feedback

Page 3 of 5

In the design context covered by this idiom, however, the basic approach of holding a reference to the recipient doesn't work so well. The requirements of this design context are:

  • One or more recipient objects must be notified of state changes or events provided by an information provider object

  • The number and type of recipient objects may be unknown at compile-time, and can vary throughout the course of execution

  • The recipient and information provider objects should be loosely coupled


The trouble with the basic approach is that the programmer has to know exactly what objects will be recipients when the information provider class is written. In this design context, however, the actual recipients may not be known until runtime.

The solution
The solution is to implement an event delegation mechanism between the information provider (the event generator) and the recipients (the listeners).

Here's a step-by-step outline of Java's idiomatic solution to this problem:

Step 1. Define event category classes

  • Define a separate event category class for each major category of events that may be experienced and propagated by the event generator.

  • Make each event category class extend java.util.EventObject.

  • Design each event category class so that it encapsulates the information that needs to be propagated from the observable to the listeners for that category of events.

  • Give the event category class a name that ends in Event, such as TelephoneEvent.


Step 2. Define listener interfaces

  • For each event category, define a listener interface that extends java.util.EventListener and contains a method declaration for each event (of that category) that will trigger an information propagation from the event generator to its listeners.

  • Name the listener interface by substituting Listener for Event in the event category class name. For example, the listener interface for TelephoneEvent would be TelephoneListener.

  • Give the methods of the listener interface verb-based names describing in past tense the situation that triggered the event propagation. For example, a listener method for receiving a TelephoneEvent that was triggered by the phone ringing would be named telephoneRang().

  • Each method should return void and take one parameter, a reference to an instance of the appropriate event category class. For example, the full signature of the telephoneRang() method would be:

    void telephoneRang(TelephoneEvent e);
    


Step 3. Define adapter classes (optional)

  • For each listener interface that contains more than one method, define an adapter class that implements the interface fully with methods that do nothing.

  • Name the adapter class by replacing Listener in the listener interface name with Adapter. For example, the adapter class for TelephoneListener would be TelephoneAdapter.


Step 4. Define the observable class

  • For each category of events that will be propagated by instances of this class, define a pair of listener add/remove methods.

  • Name the add method add<listener-interface-name>() and the remove method remove<listener-interface-name> (). For example, the listener add and remove methods for a TelephoneEvent would be named addTelephoneListener() and removeTelephoneListener().

  • For each method of each event listener interface, define a private event propagator method that takes no parameters and returns void in the event generator's class that fires (propagates) the event.

  • Name the event propagator method fire<listener-method-name>. For example, the name of the event propagator method for the event propagated via the telephoneRang() method of TelephoneListener would be fireTelephoneRang().

  • Update the code of the event generator's class so that it invokes the appropriate event propagator methods at the appropriate times.


Step 5. Define listener objects

  • To be a listener for a certain category of events, an object's class must simply implement the listener interface for that category of events.


Structure
These UML diagrams depict the structure of the telephone example, which is shown in Java code in the next section. For information about UML, see the Resources section.

Example code
Here's some Java code that illustrates using the event generator idiom to pass information from a Telephone object to interested listeners. The first class to define is the event category class, which will be called TelephoneEvent:

// In file eventgui/ex1/TelephoneEvent.java
public class TelephoneEvent
    extends java.util.EventObject {
    public TelephoneEvent(Telephone source) {
        super(source);
    }
}


Note that TelephoneEvent extends java.util.EventObject and takes as the only parameter to its only constructor a reference to the Telephone object that generated this event. The constructor passes this reference to the superclass (java.util.EventObject) constructor. Event handler methods can then invoke getSource() (a method defined in java.util.EventObject) on the event object to find out which telephone generated this event.

Requiring an event source reference to be supplied every time an event object is created enables a single listener to register with multiple sources of the same event category. For example, a secret listening device object could register as a listener for multiple telephones. Upon being notified of a telephone event, it could then query the event object to find out which telephone generated the event.

In addition, allowing the handler method to get a reference to the event source object enables the handler to ask the source for more information by invoking methods on the source. This is called the pull model in the observer-design-pattern literature, because the listener is pulling information out of the event generator after being notified of an event. It contrasts with the push model, in which all the information needed by the listener is encapsulated in the event object itself.

On the subject of encapsulating data, note that this event category class does not encapsulate any data of its own. It is conceivable, however, that a class like this one could be enhanced to contain data such as the telephone number of the caller, if it is available, the time of day the event occurred, or other relevant information. Such information would need to be supplied to or produced by the constructor (or constructors) of the event category class and made available to handlers via get methods.

Given the event category class, the next thing to define is the listener interface:

// In file eventgui/ex1/TelephoneListener.java
public interface TelephoneListener
    extends java.util.EventListener {
    void telephoneRang(TelephoneEvent e);
    void telephoneAnswered(TelephoneEvent e);
}


Note that this interface extends java.util.EventListener, a tagging interface that doesn't contain any members. This interface defines handler methods for the two kinds of events that fall into the TelephoneEvent category: telephoneRang() and telephoneEvent(). Note that both methods accept one parameter, a reference to a TelephoneEvent object, and return void.

Because TelephoneListener declares more than one event handler method, it is a good idea to define an adapter class:

// In file eventgui/ex1/TelephoneAdapter.java
public class TelephoneAdapter implements TelephoneListener {
    public void telephoneRang(TelephoneEvent e) {
    }
    public void telephoneAnswered(TelephoneEvent e) {
    }
}


As described earlier in the article, an adapter class should fully implement the interface with methods that do nothing but return. This enables listeners that are not interested in all the events to subclass the adapter and just override the handler methods of interest.

At long last, it is time to make the Telephone object itself into an event generator:

// In file eventgui/ex1/Telephone.java
import java.util.Vector;
public class Telephone {
    private Vector telephoneListeners = new Vector();
    public void ringPhone() {
        fireTelephoneRang();
    }
    public void answerPhone() {
        fireTelephoneAnswered();
    }
    public synchronized void addTelephoneListener(TelephoneListener 
l) {
        if (telephoneListeners.contains(l)) {
            return;
        }
        telephoneListeners.addElement(l);
    }
    public synchronized void 
removeTelephoneListener(TelephoneListener l) {
        telephoneListeners.removeElement(l);
    }
    private void fireTelephoneRang() {
        Vector tl;
        synchronized (this) {
            tl = (Vector) telephoneListeners.clone();
        }
        int size = tl.size();
        if (size == 0) {
            return;
        }
        TelephoneEvent event = new TelephoneEvent(this);
        for (int i = 0; i < size; ++i) {
            TelephoneListener listener = (TelephoneListener) 
tl.elementAt(i);
            listener.telephoneRang(event);
        }
    }
    private void fireTelephoneAnswered() {
        Vector tl;
        synchronized (this) {
            tl = (Vector) telephoneListeners.clone();
        }
        int size = tl.size();
        if (size == 0) {
            return;
        }
        TelephoneEvent event = new TelephoneEvent(this);
        for (int i = 0; i < size; ++i) {
            TelephoneListener listener = (TelephoneListener) 
tl.elementAt(i);
            listener.telephoneAnswered(event);
        }
    }
}


This class has addTelephoneListener() and removeTelephoneListener() methods that enable listeners to register and unregister themselves with the Telephone object. These methods make sure the internal list of listeners (stored in a Vector) contains no duplicates -- so that each event is reported to each listener only once. If a listener attempts to register twice with the same Telephone object, it won't be added to the list the second time. Such an overly enthusiastic listener will still be notified of each event only once.

The fire methods of class telephone clone the Vector of listeners before propagating the event. In this implementation, when an event is "fired," a snapshot is taken of the current registered listeners, and all those listeners are notified of the event. This means that a listener may be notified of an event even after it has unregistered itself from a telephone; that's because the event would have been fired before the listener unregistered itself.

The four classes defined above -- TelephoneEvent, TelephoneListener, TelephoneAdapter, and Telephone -- fully comprise one implementation of the event generator idiom. To see the idiom in action, however, you must define a few more classes. Here are two simple listeners for this event generator:

  • Print
  • Feedback

Resources
  • Bill's next book is Flexible Java http://www.artima.com/flexiblejava/index.html
  • The discussion forum on event generators can be found at http://www.artima.com/flexiblejava/fjf/eventgen/index.html
  • Links to all previous Design Techniques articles http://www.artima.com/designtechniques/index.html
  • A description of the "I shall return" idiom, which describes a way to return multiple values from a Java method http://www.artima.com/flexiblejava/ishallreturn.html
  • Recommended books on Java design, including information on the Gang of Four's Design Patterns book http://www.artima.com/designtechniques/booklist.html
  • A transcript of an e-mail debate between Bill Venners, Mark Johnson (JavaWorld's JavaBeans columnist), and Mark Balbe on whether or not all objects should be made into beans http://www.artima.com/flexiblejava/comments/beandebate.html
  • Source packet that contains the example code used in this article http://www.artima.com/flexiblejava/code.html
  • A nice page that describes UML. http://www.holub.com/goodies/oo_design/uml.html
  • Object orientation FAQ http://www.cyberdyne-object-sys.com/oofaq/
  • 7237 Links on Object Orientation http://www.rhein-neckar.de/~cetus/software.html
  • The Object-Oriented Page http://www.well.com/user/ritchie/oo.html
  • Collection of information on OO approach http://arkhp1.kek.jp:80/managers/computing/activities/OO_CollectInfor/OO_CollectInfo.html
  • Design Patterns Home Page http://hillside.net/patterns/patterns.html
  • A Comparison of OOA and OOD Methods http://www.iconcomp.com/papers/comp/comp_1.html
  • Object-Oriented Analysis and Design MethodsA Comparative Review http://wwwis.cs.utwente.nl:8080/dmrg/OODOC/oodoc/oo.html
  • Patterns discussion FAQ http://gee.cs.oswego.edu/dl/pd-FAQ/pd-FAQ.html
  • Patterns in Java AWT http://mordor.cs.hut.fi/tik-76.278/group6/awtpat.html
  • Software Technology's Design Patterns Page http://www.sw-technologies.com/dpattern/
  • Previous Design Techniques columns http://www.javaworld.com/topicalindex/jw-ti-techniques.html