There are many different events, all implementing the same interface:
interface Event {}
class FooEvent implements Event {}
class BarEvent implements Event {}
Every event has a dedicated handler:
interface EventHandler<T extends Event> {
void handle(T event);
}
class FooEventHandler implements EventHandler<FooEvent> {
@Override
public void handle(FooEvent event) { }
}
class BarEventHandler implements EventHandler<BarEvent> {
@Override
public void handle(BarEvent event) { }
}
All event handlers are created once and added to a map. Whenever an event occurs, this map should be used to find the proper event hander.
class Main {
Map<Class<? extends Event>, EventHandler<? extends Event>> eventHandlerRegistry = Map.of(
FooEvent.class, new FooEventHandler(),
BarEvent.class, new BarEventHandler()
);
void handleEvent(Event event) {
EventHandler<? extends Event> handler = this.eventHandlerRegistry.get(event.getClass());
handler.handle(event); // DOES NOT COMPILE: needed=capture<? extends Event>, given=Event
}
}
Unfortunately this last line does not compile. I can make it compile by leaving out the type parameter of EventHandler like this:
EventHandlerhandler = this.eventHandlerRegistry.get(event.getClass());
handler.handle(event); // WARNING: unchecked call to 'handle(T)' as a member of raw type 'EventHandler'
But this does not quite feel right... I am aware of PECS, but I feel kind of trapped because I produce AND consume my EventHandlers.
How can I implement this cleanly?
A generic map gives the value to a generic. Usually given in an instance but can also appear in a configuration. The values can be given via positional association or via named association. Use of named association is advised to improve readability and reduce the risk of making errors.
Generic Map in simple language can be generalized as: Map< K, V > map = new HashMap< K, V >(); Where K and V are used to specify the generic type parameter passed in the declaration of a HashMap.
Java has provided generic support in Map interface.
The Syntax of a java generic interface is as follows:class class-name <type-parameter-list> implements interface-name <type-arguments-list> { ... ... ... }
You can't have type safety if you're going to mix your (generic) handlers in the same map. As far as I can see, the way to make your code type-safe is to get rid of the generic type parameter on EventHandler
; but this is one thing you want to avoid.
If you may sacrifice type safety, knowing that your handlers will always match the specified classes, then you can try something like:
private <T extends Event> EventHandler<T> getHandler(Class<?> eventClass) {
return (EventHandler<T>)
this.eventHandlerRegistry.get(eventClass); //Unchecked cast
}
Then make your handleEvent
method generic:
<T extends Event> void handleEvent(T event) {
EventHandler<T> handler = this.getHandler(event.getClass());
handler.handle(event);
}
This method would then compile successfully, without warning. The only thing you'd need to make sure of is that eventHandlerRegistry
never gets polluted with something like this:
put(FooEvent.class, new BarEventHandler())); //this can happen
Here's an example of what you can do using a new EventType
enum:
Declare the enum:
public enum EventType {
FOO_EVENT, BAR_EVENT
}
Declare Event
interface:
interface Event {
EventType getType();
}
class FooEvent implements Event {
EventType getType() {
return FOO_EVENT;
}
}
class BarEvent implements Event {
EventType getType() {
return BAR_EVENT;
}
}
and EventHandler
s:
interface EventHandler {
void handle(Event event);
}
class FooEventHandler implements EventHandler {
@Override
public void handle(Event event) {
//cast Event to FooEvent when processing
}
}
class BarEventHandler implements EventHandler {
@Override
public void handle(Event event) {
//cast Event to BarEvent when processing
}
}
Declare a Map
between EventType
and EventHandler
:
Map<EventType, EventHandler> eventHandlerRegistry = //... fill the map here
And finally, when an event occurs, simply do:
eventHandlerRegistry.get(event.getType()).handle(event);
P.S. Note, for enums it's better to use java.util.EnumMap
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With