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:
// In file eventgui/ex1/AnsweringMachine.java public class AnsweringMachine implements TelephoneListener { public void telephoneRang(TelephoneEvent e) { System.out.println("AM hears the phone ringing."); } public void telephoneAnswered(TelephoneEvent e) { System.out.println("AM sees that the phone was answered."); } } // In file eventgui/ex1/Person.java public class Person { public void listenToPhone(Telephone t) { t.addTelephoneListener( new TelephoneAdapter() { public void telephoneRang(TelephoneEvent e) { System.out.println("I'll get it!"); } } ); } }
Note that AnsweringMachine
implements the TelephoneListener
interface directly. By contrast, the Person
object instantiates an anonymous inner class that subclasses the TelephoneAdapter
class. This anonymous inner class overrides the only method of interest to the Person
object: telephoneRang()
.
The last class we need to define is an example application that will exercise all these classes and interfaces:
// In file eventgui/ex1/Example1.java public class Example1 { public static void main(String[] args) { Telephone ph = new Telephone(); Person bob = new Person(); AnsweringMachine am = new AnsweringMachine(); ph.addTelephoneListener(am); bob.listenToPhone(ph); ph.ringPhone(); ph.answerPhone(); } }
When executed, the Example1
application prints out:
The answering machine hears the phone ringing. I'll get it! The answering machine sees that the phone was answered.
Implementation guidelines
With the guidelines I list in this section, I am trying to define a default way to implement this idiom. I say default because, unless you have a specific reason to take a different implementation approach, you should automatically use the approach recommended in these guidelines. My theory is that if you adhere closely to the default implementation approach, it will be easier for your fellow programmers to recognize the idiom in your work. More importantly, I feel that such idiom recognition will make it easier for your fellow programmers to understand, use, and change your code.
On the other hand, you should feel free to depart from the default approach to implementing the idiom when you feel it makes sense. In fact, I myself describe two potential "variants" to the default approach in the next section.
Now, on to the guidelines:
Create just one event object for each "firing" and pass it to all listeners.
Make the event object immutable, so there is no possibility that a listener will change the event object as it propagates.
Use a single thread to notify all listeners. In other words, the
fire
method should go through the list of listeners and invoke the appropriate handler method upon one listener after the other.Take a snapshot (clone the list) of registered listeners at the beginning of the firing process, then send the event to each listener registered at the time the snapshot was taken.
Keep event handlers (listener methods) short. These methods should execute quickly because (in the default approach) listeners are notified one at a time. In other words, listeners must wait until all listeners before them in the queue have been notified, so it is good citizenship for listeners to be quick about handling events. If an event handler method really needs to do considerable work as a result of an event notification, consider designing the handler such that it fires off or notifies another thread that does the actual time-consuming work.
Don't write listeners such that they depend on an order of notification.
- If a listener is not interested in all events that compose a particular event category, consider subclassing an adapter class and just overriding the methods of interest.
Variants
The following are variants to the default approach to implementing idioms:
If there are vast differences in the frequency of events in a particular event category, consider defining separate listeners for high frequency and low frequency events. An example of this approach is illustrated by the
MouseListener
andMouseMotionListener
interfaces ofjava.awt.event
. Both of these listener interfaces define handler methods forMouseEvent
s. But becauseMouseEvent
s like "mouse moved" are generated so much more often thanMouseEvent
s like "mouse pressed," the high frequency events like "mouse moved" get their own listener,MouseMotionListener
. Lower frequency events like "mouse pressed" are handled by methods declared in plain oldMouseListener
.- If your listeners need to cooperate with each other, consider making the event object mutable so that listeners can communicate to each other through the event object. An example of this variant is seen in the mutable
AWTEvent
class ofjava.awt
, which is the superclass of all the AWT event classes defined injava.awt.event
. ClassAWTEvent
includes two methods namedconsume()
andisConsumed()
, which enable listeners of AWT events to cooperate with one another. A listener can "consume" an event by invokingconsume()
on the event object. Subsequent listeners can determine that the event has already been consumed by invokingisConsumed()
on the event object. IfisConsumed()
returnstrue
(in other words, if another listener has already invokedconsume()
on the same event object), the listener can ignore the event.
Known uses
This idiom is based on the delegation-event model used by JavaBeans, the post-1.1 AWT, and Swing.
On Observer/Observable
As mentioned earlier in this article, the observer pattern shows up twice in the design of the Java API: once as the idiom described in this article (for JavaBeans, post-1.1 AWT, and Swing) and once in the Observer
and Observable
types of java.util
. So, why don't I think the Observer
/Observable
types set a good example for a Java observer idiom?
It turns out that Observer
/Observable
classes more closely resemble the example code given in the Design Patterns book than they do the event generator idiom described in this article. In my opinion, however, these classes don't make the grade for the following reasons:
Observable
is a class you need to subclass to make an object observable. Thus, you have to find a way to fitObservable
as a superclass in your observable class's single-inheritance hierarchy. This is often difficult.To turn a class into an observer (called a listener elsewhere in this article), you need only implement a single interface,
Observer
. The single method declared in theObserver
interface,update(Observable, Object)
, is used to notify the observers. TheObserver
interface andupdate()
method are generic so they can be used in just about any situation. Unfortunately, this generic design means that a programmer won't be able to easily understand code that usesObserver
/Observable
without digging into the nuts and bolts of theupdate
handler methods. Contrast this with a listener that subclasses aMouseAdapter
and overrides themouseReleased()
method. You already know a lot about the nature and source of the event just by looking at the names of the superclasses and methods, because they are more specific.- One final reason I turned away from
Observer
/Observable
is simply that using the event delegation model used in JavaBeans in non-bean classes eases any future transformation of a given class into a JavaBean. (Note that the AWT and Swing components, which use this event delegation model, are themselves JavaBeans.)
About JavaBeans
If you are at all familiar with JavaBeans, as you read this article you may have exclaimed, "Hey, this is all just JavaBeans stuff!" If you're thinking it would be better to just make every class a JavaBean, you would by definition use the "idiomatic" style in implementing the observer pattern to propagate JavaBeans events.
For those of you unfamiliar with JavaBeans, the minimum requirements for making a class a bean are simply that the class have a no-arg constructor and implement java.io.Serializable
. Although a lengthy treatment of the question of whether or not to make a class a bean is beyond the scope of this article, I include a link to a transcript of an e-mail debate on just this topic in the Resources section. (The resource is titled "To Bean or Not To Bean.") Very briefly, my own opinion on this matter, quoted from the e-mail debate:
If someone is going to use a class in a bean builder, that class had better be a bean. Otherwise, you needn't force it into a bean, though it may be bean-ready by its very nature. I do, however, think you should use the bean/Java naming conventions and JDK1.1 event model scheme regardless of whether your class has a no-arg constructor or implements Serializable
.
For the full discussion of the proper time and place to make classes into beans (and a broader array of opinions) check out the "To Bean or Not To Bean" e-mail debate.
In the telephone example above, class Telephone
is not a JavaBean, because it doesn't implement java.io.Serializable
. I would venture to say that you probably should have Telephone
implement Serializable
, unless you have a specific reason for not doing so. That way, if client programmers ever want to serialize an instance of the class, their lives will be made easier. In this case, while Telephone
isn't a JavaBean, its design benefits from the JavaBeans event delegation model.
Conclusion
In my world view, the two main benefits of idioms, such as the event generator idiom described in this article, are:
Idioms, like patterns, establish a vocabulary for discussing design and serve as an effective way for less experienced programmers to benefit from the hard knocks of more experienced programmers.
- Code that uses idioms is easier to understand, use, and change (for those programmers familiar with the idioms).