我正在尝试创建一个系统来响应我的应用程序中发生的事件,类似于Observer模式。在我的系统中,EventProducer
触发事件,EventConsumer
响应这些事件,两者通过中央集线器连接:
目前,我将忽略EventProducer
并关注EventHub
和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 */
}
}
问题在于声明HashMap
:我想要能够做类似的事情
HashMap<Class<E extends Event>, HashSet<EventConsumer<E>>>
// or
<E extends Event> HashMap<Class<E>, HashSet<EventConsumer<E>>>
因此EventConsumer
的参数化类型与Class
相同,但我能得到的最接近的是
HashMap<Class<? extends Event>, HashSet<EventConsumer<? extends Event>>>
但是,假设HashSet<EventConsumer<MouseClickEvent>>
和Class<KeyPressEvent>
子类KeyPressEvent
都假定为MouseClickEvent
,Event
就会被分配给subscribeToEvent
。
第二个问题出现在subscriptions.get(E.class).put(consumer)
中:我需要能够将消费者存储在与其事件相对应的正确集合中,例如
{{1}}
但我无法在运行时获得E类。
我该如何解决这些问题?我是以错误的方式解决这个问题吗?
答案 0 :(得分:2)
你可以做的是用它自己的参数化类包装Map。给出类的参数 - 您可以在地图中使用它。类似的东西:
public class EventsMap<E extends Event> {
HashMap<Class<E>, HashSet<E>> map;
}
至于订阅 - 我将使用ty1824的答案..
答案 1 :(得分:1)
至于地图,我将其保留如下:
HashMap<Class<? extends Event>, Set<EventConsumer<? extends Event>>> subscriptions;
然后使用参数化的访问器方法,如:
<E extends Event> void addSubscription(Class<E> eventClass, EventConsumer<? super E> eventConsumer)
<E extends Event> Set<EventConsumer<? super E>> getSubscriptions(Class<E> eventClass)
正如您已经指出的那样,您无法在运行时获取事件类,因此您需要由API用户提供它,例如上面提供的addSubscription
方法签名。
答案 2 :(得分:1)
您可以从EventConsumer
类中删除泛型。但是你必须在EventConsumer
的每个实现中强制转换Event对象。
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);
}
}
答案 3 :(得分:0)
为什么不参数化你的EventHub类?任何挑战?
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 */
}
}
然后在所有功能签名中使用E.
编辑1: 好的,因为我更清楚地理解你的问题,我们走了:
您的EventConsumer类可以保留它支持/处理的EventType。 EvenType是一个枚举。在Map中,您可以根据EventType存储使用者。
答案 4 :(得分:0)
好吧,从结尾开始:
首先,您可以在运行时实际获取泛型类型参数类型。只是你只能在特殊情况下这样做:
static class A<E extends EventObject> {
}
static class B extends A<MouseEvent> {
}
public static void main(String[] args) {
System.out.println(B.class.getGenericSuperclass());
}
注意B
是非泛型的,但是继承自通用父级。
如果超类是参数化类型,
Type
返回的对象必须准确反映实际类型 源代码中使用的参数。
是否可以将其用于任何用途是一个不同的问题。我没试过。我看到的大多数代码都明确地传递Class
实例作为解决方法。
其次,Map
这里不能(好吧,据我所知)用泛型来修复。你当然可以实现一个类型安全的地图,但我认为还有一个有趣的事情需要考虑:当你发起一个事件时,你可能想把它发送给所有订阅这个特定事件类的人和所有谁订阅了它的父母。所以只需要event.getClass()
就足够了。