views:

101

answers:

2

Hello. I have an abstract class AbstractEvent and some "real" classes extending it. I want to make an abstract class AbstractListener with a method process(??? event) so that non-abstract classes extending AbstractListener would be required to have at least one method accepting a class extending AbstractEvent. Is that possible?

A: 
public abstract class AbstractListener {
    public abstract void process(AbstractEvent event...);
}

Try that. That creates an abstract class called AbstractListener that has one abstract method that takes one or more abstract events. If you absolutely must always have one event, you could try something like this (although I cannot remember if this is technically possible):

public abstract class AbstractListener {
    public abstract void process(AbstractEvent event1, AbstractEvent otherEvents...);
}

Hope that helps.

aperkins
+13  A: 

You've already got the name of the mechanism you want - generics!

First, make your event class:

abstract class AbstractEvent {
    // Insert fields/methods common for all events here
}

Nothing strange about that. Next, create a parameterized listener class/interface, and give its type parameter an upper bound to your event object class:

interface Listener<T extends AbstractEvent> {
    void process(T event);
}

You can now go on making your specific event classes:

class PonyEvent extends AbstractEvent {
    // Pony-specific stuff goes here
}

And, well, that should be pretty much all you need. Go on and implement your listener classes:

class LoggingPonyListener implements Listener<PonyEvent> {
    @Override
    public void process(PonyEvent event){
        System.out.println("Pony event occurred: " + event);
    }
}

Now, you may be tempted to write a generic event dispatching class like this:

class EventDispatcher<T extends AbstractEvent> {
    private final List<Listener<T>> listeners =
        new CopyOnWriteArrayList<Listener<T>>();
    public void addListener(Listener<T> listener) {
        listeners.add(listener);
    }
    public void dispatchEvent(T event) {
        for (Listener<T> listener : listeners) 
            listener.process(event);
    }

}

Looks pretty sweet, eh? You can do stuff like this:

EventDispatcher<PonyEvent> dispatcher = new EventDispatcher<PonyEvent>();
dispatcher.add(new LoggingPonyListener());
dispatcher.dispatchEvent(new PonyEvent());

Totally sweet, we can just use this stuff, and then when we've used it, just keep on reusing it. There's one problem though. Assume you've got a clever developer who wants a super-simple listener that doesn't actually do anything with the event object, but just prints a specified message whenever the event occurs.

Not really considering your awesome EventDispatcher utility class, it was written thus:

class DebugListener implements Listener<AbstractEvent> {
    private final String msg;
    public DebugListener(String msg) { this.msg = msg; }
    @Override
    public void process(AbstractEvent event){
        System.out.println(msg);
    }
}

This should be reusable, right? No. This wouldn't work:

EventDispatcher<PonyEvent> dispatcher = new EventDispatcher<PonyEvent>();
dispatcher.add(new DebugListener("pony event"));

Because DebugListener is a Listener<AbstractEvent>, not a Listener<PonyEvent>. The way to solve this would be to use a lower bound for the parameter type:

class EventDispatcher<T extends AbstractEvent> {
    private final List<Listener<? super T>> listeners =
        new CopyOnWriteArrayList<Listener<? super T>>();
    public void addListener(Listener<? super T> listener) {
        listeners.add(listener);
    }
    public void dispatchEvent(T event) {
        for (Listener<? super T> listener : listeners) 
            listener.process(event);
    }

}

This gives you the behaviour you're after: Just like you can send a PonyEvent to the process method of a Listener<AbstractEvent> (because a PonyEvent is-an AbstractEvent), you can now use the event dispatcher class parameterized with a type to fire listeners parameterized with one of its supertypes.

gustafc
Thank you, just tried and it works)
roddik
Amazing answer.
EricBoersma