Sponsored Link •
|
Advertisement
|
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:
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
java.util.EventObject
.
Event
, such as
TelephoneEvent
.
Step 2. Define listener interfaces
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.
Listener
for Event
in the event category class name. For example, the listener interface
for TelephoneEvent
would be TelephoneListener
.
TelephoneEvent
that was triggered by the phone ringing would be named telephoneRang()
.
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)
Listener
in the listener
interface name with Adapter
. For example, the adapter class
for TelephoneListener
would be TelephoneAdapter
.
Step 4. Define one or more observable interfaces (optional)
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()
.
void
and take one parameter of the appropriate listener
type. For example,
the addTelephoneListener()
and removeTelephoneListener()
methods would take
one parameter of type TelephoneListener
.
Step 5. Define the observable class
void
in the event generator's class that fires
(propagates) the event.
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()
.
Step 6. Define listener objects
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.
|
|
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:
// In file eventgui/ex1/AnsweringMachine.java public class AnsweringMachine implements TelephoneListener { public void telephoneRang(TelephoneEvent e) { System.out.println("The answering machine hears the phone ringing."); } public void telephoneAnswered(TelephoneEvent e) { System.out.println("The answering machine 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:
fire
method should go through the list of listeners and
invoke the appropriate handler method upon one listener after the
other.
Variants
The following are variants to the default approach to implementing
idioms:
MouseListener
and
MouseMotionListener
interfaces of
java.awt.event
. Both of these listener interfaces define
handler methods for MouseEvent
s. But because
MouseEvent
s like "mouse moved" are generated so much more
often than MouseEvent
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 old
MouseListener
.
AWTEvent
class of java.awt
,
which is the superclass of all the AWT event classes defined in
java.awt.event
. Class AWTEvent
includes two
methods named consume()
and isConsumed()
,
which enable listeners of AWT events to cooperate with one another. A
listener can "consume" an event by invoking consume()
on the
event object. Subsequent listeners can determine that the event has
already been consumed by invoking isConsumed()
on the event
object. If isConsumed()
returns true
(in
other words, if another listener has already invoked
consume()
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 fit
Observable
as a superclass in your observable class's
single-inheritance hierarchy. This is often difficult.
Observer
. The single method declared in the
Observer
interface, update(Observable, Object)
, is used to notify the observers. The
Observer
interface and update()
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 uses
Observer
/Observable
without digging into the
nuts and bolts of the update
handler methods. Contrast
this with a listener that subclasses a MouseAdapter
and
overrides the mouseReleased()
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.
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:
The event generator idiom allows one or more listener objects to be notified of state changes or events provided by an event generator. The number and type of listeners may be unknown at compile-time, and can vary throughout the course of execution. The loose coupling of listeners and event generators make the code easier to change or reuse in changing situations.
Sponsored Links
|