What is the best pattern for event notification with different arguments?

Viewed 123

I'm looking for an alternative pattern to the following (method 1):

public interface IEventListener {
    void onFoo(String string, Integer integer, ObjectB objectB);
    void onBar(Double d, ObjectA objectA);
}

I'm thinking something like this (method 2):

public interface IEventListener {
    void onEvent(Event event);
}

public enum EVENT_TYPE {
    FOO, BAR
}

public abstract class Event {
    EVENT_TYPE type;
    abstract EVENT_TYPE getType();
}

public class FooEvent extends Event {
    private String string;
    private Integer integer;
    private ObjectB objectB;

    @Override
    EVENT_TYPE getType() {
        return EVENT_TYPE.FOO;
    }
}

public class BarEvent extends Event {
    private Double d;
    private ObjectA objectA;

    @Override
    EVENT_TYPE getType() {
        return EVENT_TYPE.BAR;
    }
}

But I'm not sure how it is easy to use. To handle the event I need to check the event type and cast the event to the correct one, etc.

Maybe there is no alternative to method 1?

2 Answers

Stay away from common ancestors until you have a clear reason to deal with abstract events (like some logging, timing, queueing, serialization, etc...).

It's a lot simpler to code listeners as lambdas too. You would do well to think about classes of listeners actually, like Consumer<Foo> and Consumer<Bar>.

The event provider would implement something like addFooListener(Consumer<Foo> listener) {...}

The handler might have public void handleFoo(Foo foo) {...}

and the setup code would init provider.addFooListener(somehandler::handleFoo);

A pattern I use sometimes is to have the listener methods be empty default methods and then override each of them in a functional interface subtype that can be created with a lambda, like this:

public interface IEventListener {
    default void onFoo(String string, Integer integer, ObjectB objectB){}
    default void onBar(Double d, ObjectA objectA){}
    interface OnFoo extends IEventListener {
        @Override void onFoo(String string, Integer integer, ObjectB objectB);
    }
    interface OnBar extends IEventListener {
        @Override void onBar(Double d, ObjectA objectA);
    }
}

Then you can use it either with an anonymous class that implements multiple methods you want to recieve or a lambda that just listens for one:

object.addListener((OnBar)(d, a) -> System.out.println(d));

You could still use this with event objects like FooEvent from the question which could be a good idea if you may change the arguments in the future, since you might only need to add a field to FooEvent instead of rewriting all of your listener implementations.

default void onFoo(FooEvent event){}

I think this is better for when you have many listeners that each need to listen for a subset of the events, but I've also used your method 2 from the question before when I needed to process all of the events only in one place.

Related