是否可以在包含多个侦听器类型的Java中创建自己的事件侦听器列表?

我正在实现一个客户端 – 服务器系统,其中客户端处于连续阻塞读取循环中,侦听来自服务器的消息。 当收到消息时,我想根据消息的类型引发“事件”,其他GUI类可以添加监听器。 我对C#事件更熟悉,所以我仍然习惯于Java的做事方式。

将有许多消息类型,所以我将需要每个接口,称为MessageTypeAListener,MessageTypeBListener等,每个都将包含一个句柄方法,我的GUI类将实现。 但是,将会有许多类型,而不是维护每种类型的侦听器列表,并且有几个“fire”方法,我想要一个大的侦听器列表和一个类型化的fire方法。 然后,fire方法可以说“只有那些类型是我指定的火灾听众”。

所以例如(伪代码):

ListenerList.Add(MessageTypeAListener); ListenerList.Add(MessageTypeBListener);  fire(message) { ListenerList.Where(type is T).handle(message) } ... fire(message); 

但是,类型擦除似乎使这很困难。 我可以尝试投射和捕获exception,但这似乎是错误的。 是否有一种干净的方式来实现这一点,或者为每种类型保留一个单独的侦听器列表更为明智,即使有大量的类型?

我实现了类似的东西,因为我对Java的EventListenerList有一种内心的厌恶。 首先,实现一个通用的Listener。 我基于它接收的事件定义了监听器,基本上有一个方法

 interface GenericListener { public void handle(T t); } 

这样可以节省你必须定义ListenerA,ListernerB等…尽管你可以用ListenerA,ListenerB等方式完成它,所有这些都像MyListener一样扩展了一些基础。 两种方式都有优点和缺点。

然后我使用CopyOnWriteArraySet来保存所有这些侦听器。 一套是需要考虑的因素,因为很多时候听众会被邋co的编码器添加两次。 因人而异。 但是,实际上你有一个Collection> or a Collection

现在,正如您所发现的,通过类型擦除,Collection只能容纳一种类型的侦听器。 这通常是个问题。 解决方案:使用地图。

因为我把所有事情都放在了事件上,所以我用过

 Map, Collection>> 

根据事件的类,获取想要获取该事件的侦听器列表。
您的替代方案是将其基于侦听器的类

 Map, Collection> 

上面可能有一些错别字……

使用访客模式的老式模式方法:

 class EventA { void accept(Visitor visitor) { System.out.println("EventA"); } } class EventB { void accept(Visitor visitor) { System.out.println("EventB"); } } interface Visitor { void visit(EventA e); void visit(EventB e); } class VisitorImpl implements Visitor { public void visit(EventA e) { e.accept(this); } public void visit(EventB e) { e.accept(this); } } public class Main { public static void main(String[] args) { Visitor visitor = new VisitorImpl(); visitor.visit(new EventA()); } } 

更现代的方法就是在事件类之间建立Map,这些事件不应该相互派生,以及这些事件的各个处理程序。 通过这种方式,您可以避免访问者模式的缺点(即,每次添加新事件时,您都需要更改所有访问者类别,至少是这些访问者类别的基础)。

另一种方法是使用Composite模式 :

 interface Listener { void handleEventA(); void handleEventB(); } class ListenerOne implements Listener { public void handleEventA() { System.out.println("eventA"); } public void handleEventB() { // do nothing } } class CompositeListener implements Listener { private final CopyOnWriteArrayList listeners = new CopyOnWriteArrayList(); void addListener(Listener l) { if (this != l) listeners.add(l); } public void handleEventA() { for (Listener l : listeners) l.handleEventA(); } public void handleEventB() { for (Listener l : listeners) l.handleEventB(); } } 

在完成对每个人的建议的迭代之后,我最终得到了一个标准的Listener接口和监听器列表的略微修改的路径。 我从Swing的EventListenerList开始,只对数十种消息类型的添加/删除方法的数量感到失望。 我意识到在保持单个EventListenerList同时无法压缩它,所以我开始考虑每种类型的单独列表。 这使得它类似于.NET事件,其中每个事件都拥有自己的代表列表,以便在引发时触发。 我想避免大量的添加/删除方法,所以我创建了一个快速的Event类,它看起来像这样:

 public class Event { private List listeners = new ArrayList(); public void addListener(T listener) { listeners.add(listener); } public void removeListener(T listener) { listeners.remove(listener); } public List getListeners() { return listeners; } } 

然后我保留了这个类的几个实例,每个实例都是根据一个监听器输入的,所以Event等等。然后我的类可以调用add方法将自己添加到该特定事件中。 我希望能够在Event实例上调用genericsRaise方法然后解雇所有处理程序,但我不希望它们都必须具有相同的“句柄”方法,所以这是不可能的。 相反,当我准备好解雇听众时,我就是这么做的

  for (MessageTypeAListener listener : messageTypeAEvent.getListeners()) listener.onMessageTypeA(value); 

我确信这不是一个新想法,可能已经在以前以更好/更强大的方式完成,但它对我很有用,我很高兴。 最重要的是,它很简单。

谢谢你的帮助。

如果您只有简单事件,即没有数据的事件或所有事件具有相同数据类型的事件,则枚举可能是一种前进方式:

 public enum Event { A, B, C } public interface EventListener { void handle(Event event); } public class EventListenerImpl implements EventListener { @Override public void handle(Event event) { switch(event) { case A: // ... break; } } } public class EventRegistry { private final Map> listenerMap; public EventRegistry() { listenerMap = new HashMap>(); for (Event event : Event.values()) { listenerMap.put(event, new HashSet()); } } public void registerEventListener(EventListener listener, Event event) { Set listeners = listenerMap.get(event); listeners.add(listener); } public void fire(Event event) { Set listeners = listenerMap.get(event); for (EventListener listener : listeners) { listener.handle(event); } } } 

注释:

如果EventListnerImplswitch语句仅注册到单个事件,或者它应始终以相同的方式操作,无论它接收哪个Event ,都可以省略它。

EventRegister已将EventListener存储在一个映射中,这意味着每个侦听器只会获得它已订阅的Event类型。 此外, EventRegister使用Set s,这意味着EventListener最多只接收一次事件(以防止如果有人意外地将侦听器注册两次,侦听器将接收两个事件)。