In the previous parts, I have shown you how you may break your code by using Observer Pattern implementation that comes along in Java. I have also shown you how you can use a degenerated form of Observer Pattern.
Today I am going to show you yet another way to implement this pattern. I hope you are not getting tired of koalas, since we are about to implement our Koala Zoo example again. This time we are about to use
javax.swing.event.EventListenerList
.Do it better - Use
EventListenerList
Even though
EventListenerList
is located in javax.swing.event
package, it is in no way restricted to user interface usage. Unlike the Java Observable/Observer
, using the EventListenerList
does not lead to fragile code either. You can find the EventListenerList
usage instructions in the header of it's API documentation. It's almost as old as the Observable/Observer
, but still useful today. So go ahead and use it!Let's implement our Koala again:
public class Koala { private final EventListenerList observers = new EventListenerList(); public void addKoalaListener(final KoalaObserver observer) { observers.add(KoalaObserver.class, observer); } public void removeKoalaListener(final KoalaObserver observer) { observers.remove(KoalaObserver.class, observer); } public void setHungry(final boolean isHungry) { final KoalaEvent event = new KoalaEvent(isHungry); fireKoalaChanged(event); } private void fireKoalaChanged(final KoalaEvent event) { // Guaranteed to return a non-null array final Object[] listeners = observers.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == KoalaObserver.class) { ((KoalaObserver) listeners[i + 1]).koalaUpdate(this, event); } } } @Override public String toString() { return "koala"; } }
Now the
Koala
keeps it's observers stored in the EventListenerList
. Whenever changes happen, it notifies the observers.But what is up with the funny backwards loop that skips every other element?
Well, that is taken directly from the usage instructions in the API. The
getListenerList
returns an array that contains ListenerType-Listener pairs. So the first element in the list will be the type of the listener, e.g. KoalaObserver.class
, and the second element will be the actual listener.There only reason that I know for backwards looping is that this thingy was originally written for the Swing Events. The event handling has some rules and one of them is that the last registered listener should be notified first. That way the newly added listeners have possibility to consume events, too. Thus, the list must be processed from the back to the start.
There are some suggestions about other reasons in this StackOverflow thread: Why is EventListenerList traversed backwards in fireFooXXX(), but I am not buying most of those. The listener array is not going to break or anything if you loop it forwards.
But let's get back to Koala Zoo.
The
KoalaObserver
now uses a new KoalaEvent
, but otherwise is has stayed the same:public interface KoalaObserver extends EventListener { void koalaUpdate(Koala koala, KoalaEvent change); }
I kept the
KoalaObserver
interface name the same as in previous implementation, so you can compare these easily. But normally I would have called it the KoalaListener
.The
KoalaEvent
looks like this:public class KoalaEvent { private final boolean hungry; public KoalaEvent(final boolean isHungry) { this.hungry = isHungry; } public boolean isHungry() { return hungry; } }
Note how the
KoalaEvent
hungry
field is defined final. It should be up to the Koala
to decide whether it is hungry or not, so we do not want anyone to change that field value later. Always, if possible, try to make your classes immutable. For non-english-speakers, immutable means not modifiable, something that you cannot change after it has been created.I added the
KoalaEvent
just to demonstrate how you can deliver complex change information. Using event class is not mandatory. We could have just passed a boolean parameter in the koalaUpdate
method.Here is our
ZooKeeper
:public class ZooKeeper implements KoalaObserver { @Override public void koalaUpdate(final Koala koala, final KoalaEvent change) { if (change.isHungry()) { LOGGER.info("Oh, the " + koala + " is hungry. It needs some eucalyptus."); } } }
While handling the events, keep in mind that you do not know who else is going to get them after you are done. So never make any changes to the events that you handle. Exception to this rule is "consuming" the events, like key presses, so that the listeners that come next know that application has already acted based on user input. Most of the events should be immutable, so you can not change them even if you wanted.
And here we test that the notification is sent:
public void testKoalaGetsFood() { final LogMsgCaptor logMsgCaptor = new LogMsgCaptor(LOGGER); final Koala koala = new Koala(); final ZooKeeper zooKeeper = new ZooKeeper(); koala.addKoalaListener(zooKeeper); koala.setHungry(true); assertEquals("The zookeeper should get notification", "Oh, the koala is hungry. It needs some eucalyptus.", logMsgCaptor.getMsg()); }
LogMsgCaptor
is a Mockito-based helper class that I wrote to collect logged messages so that I can test what was logged. Assert.assertEquals
comes from JUnit and it makes our test case fail if the message logged does not match to what we expected.What is the difference between Java
Observable/Observer
and EventListenerList?Functionally, they have only minor differences.
The
EventListenerList
is designed to hold several different types of listener in it. If you would be implementing an user interface class, you could stuff all your MouseListeners, KeyListeners, ActionListeners
and what have you, to this same EventListenerList
. When the notification time comes, you can easily define which listeners should receive which event. While Observable
happily stores any type of Observers
, it always notifies all of them using the same argument. But
Observable
has an inbuilt change-detection system, and that is something EvenListenerList
does not have. The Observable
sends notifications only if it has changed, and after the notifications have been sent, it does not send more notifications unless it is marked changed again.Stylewise,
EventListenerList
is more elegant of the two. You should always favor composition over inheritance.When you use composition, like we did here, you can hide the parts of the functionality that are not needed at the moment. If we wanted, we could easily remove the
removeKoalaListener
method from our Koala
. But when you extend the Observable
, you cannot hide or remove anything.You can also restrict the visibility of the methods that should not be accessed from outside the class. Take a look at the
fireKoalaChanged
method of our Koala
. It is private, so no-one can force the Koala
to send notifications from outside. The respective methods in the Observable
are all public or protected. Also, as the
EventListenerList
does not come with predefined notification interface, you probably end up defining interfaces that suit your purposes better.If you really need to implement a full-fledged Observer Pattern,
EventListenerList
is an good way to do that.In the next part we are going to revisit the Java
Observer/Observable
using Generics.