Reputation: 27436
I am trying to make a system for responding to events that happen in my application, similar to the Observer pattern. In my system, EventProducer
s trigger events and EventConsumer
s respond to those events, and the two are connected through a central hub:
For the moment, I'm going to ignore EventProducer
and focus on EventHub
and EventConsumer
:
interface EventConsumer<E extends Event> {
void respondToEvent(E event);
}
class EventHub {
private HashMap<Class</*event type*/>, HashSet<EventConsumer</*event type*/>>> subscriptions;
public <E extends Event> void fireEvent(E event) {
/* For every consumer in the set corresponding to the event type {
consumer.respondToEvent(event);
} */
}
public <E extends Event> void subscribeToEvent(EventConsumer<E> consumer) {
/* Insert consumer into the set corresponding to E */
}
}
The problem lies in the declaration of the HashMap
: I want to be able to do something like
HashMap<Class<E extends Event>, HashSet<EventConsumer<E>>>
// or
<E extends Event> HashMap<Class<E>, HashSet<EventConsumer<E>>>
So that the EventConsumer
is parameterized by the same type the Class
is, but the closest I can get is
HashMap<Class<? extends Event>, HashSet<EventConsumer<? extends Event>>>
But then this would allow things like a HashSet<EventConsumer<MouseClickEvent>>
being assigned to Class<KeyPressEvent>
, assuming both KeyPressEvent
and MouseClickEvent
subclass Event
.
A second problem is in subscribeToEvent
: I need to be able to store the consumer in the correct set corresponding to its event, like in
subscriptions.get(E.class).put(consumer)
but I cannot get the class of E at run-time.
How can I solve these problems? Am I going about this the wrong way?
Upvotes: 5
Views: 156
Reputation: 3163
Why don't you parameterise your EventHub class? any challanges?
interface EventConsumer<E extends Event> {
void respondToEvent(E event);
}
class EventHub<E extends Event> {
private HashMap<Class<E>, HashSet<EventConsumer<E>>> subscriptions;
public void fireEvent(E event) {
/*
* For every consumer in the set corresponding to the event type {
* consumer.respondToEvent(event); }
*/
}
public void subscribeToEvent(EventConsumer<E> consumer) {
/* Insert consumer into the set corresponding to E */
}
}
And then use E in all your function signatures.
EDIT 1: Okay, since i understand your question more clearly, here we go:
your EventConsumer Class can keep an EventType(s) that it supports/handles. EvenType is an Enum. In your Map you store Consumers against a EventType.
Upvotes: 0
Reputation: 35341
You could remove the generics from the EventConsumer
class. But you'd have to cast the Event object in each implementation of EventConsumer
.
interface EventConsumer {
void respondToEvent(Event event);
}
class ClickEventConsumer implements EventConsumer {
public void respondToEvent(Event event){
ClickEvent ce = (ClickEvent)event;
//...
}
}
class EventHub {
private HashMap<Class<? extends Event>, HashSet<EventConsumer>> subscriptions;
public void fireEvent(Event event) {
HashSet<EventConsumer> consumers = subscriptions.get(event.getClass());
if (consumers != null){
for (EventConsumer ec : consumers){
ec.respondToEvent(event);
}
}
}
public void subscribeToEvent(Class<? extends Event> clazz, EventConsumer consumer) {
HashSet<EventConsumer> consumers = subscriptions.get(clazz);
if (consumers == null){
consumers = new HashSet<EventConsumer>();
subscriptions.put(clazz, consumers);
}
consumers.add(consumer);
}
}
Upvotes: 1
Reputation: 8513
Well, starting from the end:
First, you can actually obtain generic type parameter type at runtime. It's just that you can only do it in a special case:
static class A<E extends EventObject> {
}
static class B extends A<MouseEvent> {
}
public static void main(String[] args) {
System.out.println(B.class.getGenericSuperclass());
}
Note B
is non-generic, but is inherited from a generic parent.
If the superclass is a parameterized type, the
Type
object returned must accurately reflect the actual type parameters used in the source code.
Whether you can put it in any use is a different question. I didn't try. Most of the code I saw explicitly pass a Class
instance as a workaround.
Second, the Map
here cannot (well, as far as I know) be fixed with generics. You can implement a typesafe map of course, but I think there's one more interesting thing to consider: when you fire an event, you probably want to send it to all who subscribed to this particular event class and all who subscribed to its parents. So just event.getClass()
would be just a little bit less than enough.
Upvotes: 0
Reputation: 3029
What you can do is to wrap the Map with it's own parameterized class. given the parameter to the class - you can use it in the map. something like that:
public class EventsMap<E extends Event> {
HashMap<Class<E>, HashSet<E>> map;
}
As for subscribing - I'll use ty1824's answer..
Upvotes: 2
Reputation: 2348
As for the Map, I'd leave it as follows:
HashMap<Class<? extends Event>, Set<EventConsumer<? extends Event>>> subscriptions;
And then use parameterized accessor methods like:
<E extends Event> void addSubscription(Class<E> eventClass, EventConsumer<? super E> eventConsumer)
<E extends Event> Set<EventConsumer<? super E>> getSubscriptions(Class<E> eventClass)
As you already pointed out you cannot obtain the event class at runtime, so you'll need to have it provided by your API users as for example with the method signature of addSubscription
provided above.
Upvotes: 1