Java. Correct pattern for implementing listeners

Jake picture Jake · Jun 4, 2010 · Viewed 40.8k times · Source

Very typically I have a situation where a given object will need to have many listeners. For instance, I might have

class Elephant {
  public void addListener( ElephantListener listener ) { ... }
}

but I'll have many such situations. That is, I'll also have a Tiger object that'll have TigerListeners. Now, TigerListeners and ElephantListeners are quite different:

interface TigerListener {
  void listenForGrowl( Growl qrowl );
  void listenForMeow( Meow meow );
}

while

interface ElephantListener {
  void listenForStomp( String location, double intensity );
}

I find that I always have to keep re-implementing the broadcasting mechanism in each animal class, and the implementation is always the same. Is there a preferred pattern?

Answer

matt b picture matt b · Jun 4, 2010

Instead of each Listener having specific methods for every event type you can send it, change the interface to accept a generic Event class. You can then subclass Event to specific subtypes if you need, or have it contain state such as double intensity.

TigerListener and ElephentListener then become

interface TigerListener {
    void listen(Event event);
}

In fact, you can then further refactor this interface into a plain Listener:

interface Listener {
    void listen(Event event);
}

Your Listener implementations can then contain the logic that they need for the specific events they care about

class TigerListener implements Listener {
    @Overrides
    void listen(Event event) {
        if (event instanceof GrowlEvent) {
            //handle growl...
        }
        else if (event instance of MeowEvent) {
            //handle meow
        }
        //we don't care about any other types of Events
    }
}

class ElephentListener {
    @Overrides
    void listen(Event event) {
        if (event instanceof StompEvent) {
            StompEvent stomp = (StompEvent) event;
            if ("north".equals(stomp.getLocation()) && stomp.getDistance() > 10) { 
                ... 
            }
        }
    }
}

The key relationship between the subscriber and the publisher is that the publisher can send events to the subscribers, it isn't necessarily that it can send it certain types of events - this type of refactoring pushes that logic from the interface down into the specific implementations.