The 'event generator' idiom

How and when to make a Java class observable

In January, I began a long series of articles about designing classes and objects. In this month's Design Techniques article, I'll continue that series but with a different approach. Up to now I have been writing in terms of design guidelines. In this article I'll be writing about idioms.

In my view, some design ideas are most effectively communicated via a set of guidelines, whereas other ideas can be communicated best as patterns or idioms. One of my main goals with this column is to "try out" material that I plan to put into my next book, Flexible Java (see Resources), by sending the material out to the public arena and getting feedback. Currently, I plan to organize Flexible Java around both guidelines and idioms, using each where it is most appropriate. In this and the next article, I'll be proposing some idioms on which I welcome any and all feedback.

I call the idiom I will propose in this article the event generator. This idiom arises from implementing the well-known observer pattern in Java by applying the JavaBeans/1.1 AWT/Swing event model to classes that aren't necessarily beans or GUI components. In this article, I'll present a backgrounder on patterns and idioms, discuss the observer pattern, demonstrate the event generator idiom, and explain why I don't think using the Observer/Observable types from java.util is the best approach to implementing the observer pattern in Java.

On patterns and idioms

In this article, I will be demonstrating the idiomatic way to implement the observer pattern in Java. The observer pattern is one of 23 design patterns described in the book Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides (aka the Gang of Four). This book describes patterns that pop up again and again in object-oriented designs, independent of implementation language. It discusses the pros and cons of each pattern, gives advice on when each pattern is appropriate, and shows an example of each pattern implemented in Smalltalk or C++.

The examples I include in this article are not simply Java translations of the C++ code given in the Gang of Four book, because Java has an idiomatic way to implement this pattern. Which brings me to idioms.

What is the difference between a design pattern and an idiom? The difference is one of scope. In general, both design patterns and idioms describe solutions to recurring design problems. But with a design pattern, both the problem and solution are generic enough to be independent of implementation language. An idiom, by contrast, is a low-level pattern that is specific to a programming language. For example, a design pattern might describe a way to ensure there is only one instance of a class (the singleton pattern). An idiom might describe a way to return multiple values in Java, given that the Java language doesn't have a built-in multivalued return capability. (See Resources for a link to my proposed idiom that addresses this problem.)

The observer pattern

Many of the patterns described in the Gang of Four book show up in the design of the Java API, including the observer pattern. In fact, the observer pattern appears twice in the Java API: once in the Observer/Observer types defined in java.util, and again as the JavaBeans/1.1 AWT/Swing event model.

Because the event model described here is used by JavaBeans, the AWT (1.1 and beyond), and Swing, I will make the claim that this is the idiomatic way to implement the observer pattern in the Java language. At the end of this article, I'll explain why I don't feel the Observer/Observable types from java.util make the grade.

Idiom Presentation

If you are familiar with the Gang of Four book, or any other book of patterns or idioms, you will recognize in the coming text the customary style of presenting patterns. Typically, pattern descriptions all adhere to a common template (that is, they use a common set of text subheadings).

For example, the template I will use for all the idioms I describe in this column has the following form:

Idiom name

  • Intent
  • Also known as
  • Example
  • Context
  • Problem
  • Solution
  • Structure
  • Example code
  • Implementation guidelines
  • Variants
  • Known uses

The template used to describe patterns varies from book to book, but usually remains the same for all the patterns presented in one book (or in this case, in one column). The template shown above is one I concocted for my Flexible Java project, and I welcome any suggestions you may have as to how it can be improved.

The event generator idiom

Intent

Enable interested objects (listeners) to be notified of a state change or other events experienced by an "event generator."

Also known as

Observer, Dependents, Publisher-Subscriber

Example

One recent afternoon, I was sitting in my makeshift office at home, trying to think of a good example for explaining Java's event model in a Java class I was teaching. I was having trouble thinking of a decent example, when the phone rang. I got up, walked over to the phone, answered it, and had a short conversation. After I hung up, I realized I had my example.

What if, I asked myself, I had to design a software system that modeled a phone and all the objects that might be interested in knowing it was ringing? Certainly people in vicinity of the phone (i.e.,in the same room or house) might be interested in knowing it was ringing. In addition, an answering machine might want to know, as would a fax machine and a computer. Even a secret listening device may want to know, so it could surreptitiously monitor conversations.

I realized the interested parties might change as my program executed. For example, people might enter and leave the room containing the phone. Answering machines, computers, or top-secret listening devices might be attached to and detached from the phone as the program executed. In addition, new devices might be invented and added to future versions of the program.

So what's a good approach to designing this system? Answer: Make the telephone an event generator.

Context

One or more objects (recipients) need to use information or be notified of state changes or events provided by another object (the information provider).

The problem

In Java, one object (the information provider) customarily sends information to another object (the recipient) by invoking a method on the recipient. But to invoke a method on the recipient, the information provider must have a reference to the recipient object. Furthermore, the type of that reference must be some class or interface that declares or inherits the method to invoke. In a very basic approach, the provider holds a reference to the recipient in a variable whose type is the recipient's class.

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);
}
1 2 3 Page 1
Page 1 of 3