November 06, 2011

Observer: Part III - Java EventListenerList

This is continuation from Observer: Part II - Degenerated Observer.

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.

No comments:

Post a Comment