I think Event API in general should cover following things:
- Be type safe, avoid casting of Events/Event Types classes - it will save you from unexpected class cast Exceptions in the future.
- Be developer friendly. API should be short and sweet. You should be able to easily find the right listener within your IDE (e.g. Intellij IDEA or Eclipse). Type safety will give you that.
- Adding of new event types should be easy and fast.
- Event structures should be reusable.
Here is one of possible approaches:
Create base event and more complex event structure.
class BaseEvent {
}
class ComplexEvent extends BaseEvent {
int importantData;
public ComplexEvent(int i) {
importantData = i;
}
}
Create a separate class for EventType, make each event type dependent on event. We now can reuse existing structures for our event types. It is also very easy to add new Events without breaking the existing code.
final class EventType<E> {
private EventType(){}
public final static EventType<BaseEvent> SimpleEvent = new EventType<BaseEvent>();
public final static EventType<BaseEvent> SimpleEvent2 = new EventType<BaseEvent>();
public final static EventType<ComplexEvent> ComplexEvent1 = new EventType<ComplexEvent>();
public final static EventType<ComplexEvent> ComplexEvent2 = new EventType<ComplexEvent>();
}
Create listener as a generic interface, dependent on Event.
interface Listener<E extends BaseEvent> {
void handle(E event);
}
Use this classes within EventSource, register listeners for specified event types.
class EventSource {
private final Map<EventType, List<Listener<? extends BaseEvent>>> listenersMap = new HashMap<EventType, List<Listener<? extends BaseEvent>>>();
public <E extends BaseEvent> void addListener(EventType<E> eventType, Listener<E> listener) {
listeners(eventType).add(listener);
}
public <E extends BaseEvent> void fire(EventType<E> eventType, E event) {
for (Listener listener : listeners(eventType)) {
listener.handle(event);
}
}
private List<Listener<? extends BaseEvent>> listeners(EventType eventType) {
if (listenersMap.containsKey(eventType)) {
return listenersMap.get(eventType);
} else {
List<Listener<? extends BaseEvent>> listenersList = new ArrayList();
listenersMap.put(eventType, listenersList);
return listenersList;
}
}
}
Check usage of API, it allows us to get the right event structure in Listener's implementations.
public static void main(String[] args) {
EventSource eventSource = new EventSource();
eventSource.addListener(EventType.SimpleEvent, new Listener<BaseEvent>() {
@Override
public void handle(BaseEvent event) {
log.info("Simple 1 handled!");
}
});
eventSource.addListener(EventType.SimpleEvent2, new Listener<BaseEvent>() {
@Override
public void handle(BaseEvent event) {
log.info("Simple 2 handled!");
}
});
// compile error! we must handle ComplexEvent type
// eventSource.addListener(EventType.ComplexEvent1, new Listener<BaseEvent>() {
// @Override
// public void handle(BaseEvent event) {
// log.info("Complex 1 handled!");
// }
// });
eventSource.addListener(EventType.ComplexEvent1, new Listener<ComplexEvent>() {
@Override
public void handle(ComplexEvent event) {
log.info("Complex 1 handled!" + event.importantData);
}
});
eventSource.addListener(EventType.ComplexEvent2, new Listener<ComplexEvent>() {
@Override
public void handle(ComplexEvent event) {
log.info("Complex 2 handled!" + event.importantData);
}
});
eventSource.fire(EventType.SimpleEvent, new BaseEvent());
eventSource.fire(EventType.SimpleEvent2, new BaseEvent());
eventSource.fire(EventType.ComplexEvent1, new ComplexEvent(1));
eventSource.fire(EventType.ComplexEvent2, new ComplexEvent(2));
// compile error! we must fire ComplexEvent to our listeners
//eventSource.fire(EventType.ComplexEvent1, new BaseEvent());
}
You can also add a shorthand fire
call to EventSource
when you don't need to pass any specific data
public <E extends BaseEvent> void fire(EventType<E> eventType) {
fire(eventType, (E) new BaseEvent());
}
And use it from the client code as simple:
eventSource.fire(EventType.SimpleEvent);