October 22, 2011

Observer: Part I - How to mess up your code with it


The Observer pattern is one of the patterns published in the Design Patterns book by the Gang of Four. It is needed when you have an object that changes it's state and you want other objects to notice the change.

If you are programming Java user interfaces you cannot really avoid this pattern. The whole Java event handling is based on it. So whenever you are writing event listeners for Swing components, you are actually using this pattern.

But Java also offers an implementation of the Observer interface and an Observable class to go with the interface. The problem with the Java Observer and Observable is that they were written before the Generics and so they use plain Objects to pass information. While that is not a problem in small hobby projects, it may lead to disasters in larger applications.


If you want to create easily maintainable code, you need to enforce the strong type checking that Java offers.

I guess most of you have heard that before. But maybe some of you have not seen what may happen when that rule is broken. And that is what I am about to demonstrate today. This is a beginner-level article. If you have already messed up your code by violating the rule and always try to avoid casting objects in your code, you might want to just quickly browse through the code and read the puzzler at the end of the post.


How to mess up your code with Java Observer

I am going to demonstrate the problem with a simple example from the Koala Zoo. In the very first version of the Koala Zoo we have just a Koala and a ZooKeeper, who gives food to the Koala when it is hungry.

Let's start by implementing the Koala using the Java Observable:
    public class Koala extends Observable {

        public void setHungry(final boolean isHungry) {
            setChanged();
            notifyObservers(isHungry);
        }

        @Override
        public String toString() {
            return "koala";
        }
    }

There we go! In real life the Koala class would probably be a bit more complex, but this is enough for our purposes. As you notice, Observable is a class and needs to be extended by our Koala.

Let's continue and create our Observer, the ZooKeeperObserver is an interface with a single method for getting updates from the Observable:
    public class ZooKeeper implements Observer {

        @Override
        public void update(final Observable animal, final Object arg) {
            if (arg instanceof Boolean) {
                final boolean isHungry = (Boolean) arg;
                if (isHungry) {
                    LOGGER.info("Oh, the " + animal
                            + " is hungry. I'll go into the cage and "
                            + "give some eucalyptus to the little fella!");
                }

            }
        }
    }

Our Observable and Observer are ready to go, let's test how they work:
    public void testKoalaGetsFood() {
        final LogMsgCaptor logMsgCaptor = new LogMsgCaptor(LOGGER);
        final Koala koala = new Koala();
        final ZooKeeper zooKeeper = new ZooKeeper();

        koala.addObserver(zooKeeper);
        koala.setHungry(true);
        assertEquals("The zookeeper should get notified",
                "Oh, the koala is hungry. I'll go into the cage and "
                        + "give some eucalyptus to the little fella!",
                logMsgCaptor.getMessage());
    }

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.

As we can see, the code works nicely and ZooKeeper is able to keep the Koala stuffed with eucalyptus. But things are going to change when the code evolves.

The Zoo is growing and getting new animals. Introduce the Tiger!
    public class Tiger extends Observable {

        public void setHungry(final boolean isHungry) {
            setChanged();
            notifyObservers(isHungry);
        }

        @Override
        public String toString() {
            return "tiger";
        }
    }

At this point I might spare a thought on whether I could use a common superclass called Animal. But as it's easy to refactor later and pull the methods up, I am not going to do that now.

The Tiger observable is ready to use, but we have not made any changes to our Observer, the ZooKeeperThe nasty thing is that we can actually add the ZooKeeper as an observer for Tiger already and we get no compilation errors when we compile the code. Huh? The ZooKeeper is an observer for Koala, and as the update method is passing Objects as arguments, compiler have no way to know that the method is not implemented properly.

That means we need to REMEMBER to update the ZooKeeper's update method, so that he knows how the Tiger needs to be feed. But hey, that's no problem at all. I have great memory! If I sometimes forget my keys or the dinner or my pants or something it is just because I am thinking something else. But enough blabbering, let's have a coffee break. I suggest you to have a cup too!

Are you back yet? Where were we? Yeah, the Tiger. Let's continue and implement our new test case, the one where the ZooKeeper is feeding the Tiger:
    public void testTiggerGetsFood() {
        final LogMsgCaptor logMsgCaptor = new LogMsgCaptor(LOGGER);
        final Tiger tigger = new Tiger();
        final ZooKeeper zooKeeper = new ZooKeeper();

        tigger.addObserver(zooKeeper);
        tigger.setHungry(true);
        // As this test goes through OK, we forgot to update the ZooKeeper 
        assertEquals("Hmmm. Should the zookeeper go into the cage?",
                "Oh, the tiger is hungry. I'll go into the cage and "
                        + "give some eucalyptus to the little fella!",
                logMsgCaptor.getMessage());
    };
Oh well, what do you know... I'll fix the code in a minute...

I know that at this point some of you are thinking that this is not a big deal. But as time goes by and code gets filled up with little slips like the one I made above, results can be... interesting.

Let me show you an example with a little puzzler.


Puzzler: Koala Zoo after 2 years

The application has expanded and the Zoo now has new personnel. The ZooKeeper has been changed, and he has a Boss to report to:
    public class Boss implements Observer {

        @Override
        public void update(final Observable observable, final Object arg) {
            if (observable instanceof ZooKeeper) {
                ((ZooKeeper) observable).getStatusReport();
            }

            LOGGER.info("I hate being bugged with little things");
        }

    }

We can see that the ZooKeeper has become Observable too. Here is the new code for the ZooKeeper:
    public class ZooKeeper extends Observable implements Observer {

        public void getStatusReport() {
            LOGGER.info("Well, I have taken care of everything");
        }

        @Override
        public void update(final Observable observable, final Object arg) {
            final boolean argIsTrue = Boolean.TRUE.equals(arg);
            if (observable instanceof Koala && argIsTrue) {
                LOGGER.info("The lazy critter is hungry again, I cannot believe how much it eats");
            }
            else if (observable instanceof WaterPipe && argIsTrue) {
                LOGGER.info("I'll do what needs to be done");
            }

            if (argIsTrue) {
                setChanged();
                notifyObservers();
            }
        }
    }

Oh, it seems that the ZooKeeper now observers a WaterPipe too. But what is the argument? Lets check that out the WaterPipe to find out:
    public class WaterPipe extends Observable {

        public void setBroken(final boolean isBroken) {
            setChanged();
            notifyObservers(isBroken);
        }

        @Override
        public String toString() {
            return "watering system";
        }
    }

Oh, the argument is telling if the pipe is broken!
Let's see how all these classes are used:
        final List fifteenKoalas = initKoalas();
        final WaterPipe pipe = new WaterPipe();
        final ZooKeeper zooKeeper = new ZooKeeper();
        final Boss boss = new Boss();
        zooKeeper.addObserver(boss);
        pipe.addObserver(boss);

        for (final Koala koala : fifteenKoalas) {
            koala.addObserver(boss);
            koala.addObserver(zooKeeper);
        }

And as you might guess, this code is not working.

What is wrong and how would you fix it? Is the Boss supposed to observe the Koalas?

I'll tell you the answers in the next post.




No comments:

Post a Comment