[英]Is it possible to create my own event listener list in Java containing multiple listener types?
我正在实现一个客户端 - 服务器系统,其中客户端处于连续阻塞读取循环中,侦听来自服务器的消息。 当收到消息时,我想根据消息的类型引发“事件”,其他GUI类可以添加监听器。 我对C#事件更熟悉,所以我仍然习惯于Java的做事方式。
会有很多消息类型,所以我需要一个接口,称之为MessageTypeAListener,MessageTypeBListener等,每个都将包含一个句柄方法,我的GUI类将实现它。 但是,将有许多类型,而不是维护每种类型的侦听器列表,并且有几个“火”方法,我想要一个大的侦听器列表和一个类型的fire方法。 然后火方法可以说“只有那些类型是我指定的火灾听众”。
所以例如(伪代码):
ListenerList.Add(MessageTypeAListener);
ListenerList.Add(MessageTypeBListener);
<T> fire(message) {
ListenerList.Where(type is T).handle(message)
}
...
fire<MessageTypeAListener>(message);
但是,类型擦除似乎使这很困难。 我可以尝试投射和捕获异常,但这似乎是错误的。 是否有一种干净的方式来实现这一点,或者为每种类型保留一个单独的侦听器列表是否更明智,即使有大量的类型?
我实现了类似的东西,因为我对Java的EventListenerList有一种内心的厌恶。 首先,实现一个通用的Listener。 我基于它接收的事件定义了监听器,基本上有一个方法
interface GenericListener<T extends Event> {
public void handle(T t);
}
这样可以节省你必须定义ListenerA,ListernerB等...虽然你可以用ListenerA,ListenerB等方式完成它,所有都像MyListener一样扩展了一些基础。 两种方式都有优点和缺点。
然后我使用CopyOnWriteArraySet
来保存所有这些侦听器。 一套是需要考虑的因素,因为很多时候听众会被邋co的编码器加入两次。 因人而异。 但是,实际上你有一个Collection<GenericListener<T extends Event>> or a Collection<MyListener>
现在,正如您所发现的,通过类型擦除,Collection只能容纳一种类型的侦听器。 这通常是个问题。 解决方案:使用地图。
因为我把所有事情都放在了事件上,所以我用过
Map<Class<T extends Event>, Collection<GenericListener<T extends Event>>>
根据事件的类,获取想要获取该事件的侦听器列表。
您的替代方案是将其基于侦听器的类
Map<Class<T extends MyListener>, Collection<MyListener>>
上面可能有一些错别字......
使用访客模式的老式模式方法:
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<Listener> listeners = new CopyOnWriteArrayList<Listener>();
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<T extends EventListener> {
private List<T> listeners = new ArrayList<T>();
public void addListener(T listener) {
listeners.add(listener);
}
public void removeListener(T listener) {
listeners.remove(listener);
}
public List<T> getListeners() {
return listeners;
}
}
然后我保留了这个类的几个实例,每个实例都是根据一个监听器输入的,所以Event<MessageTypeAListener>
等等。然后我的类可以调用add方法将它们添加到该特定事件中。 我希望能够在Event
实例上调用泛型Raise
方法然后解雇所有处理程序,但我不希望它们都必须具有相同的“句柄”方法,所以这是不可能的。 相反,当我准备好解雇听众时,我就是这么做的
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<Event, Set<EventListener>> listenerMap;
public EventRegistry() {
listenerMap = new HashMap<Event, Set<EventListener>>();
for (Event event : Event.values()) {
listenerMap.put(event, new HashSet<EventListener>());
}
}
public void registerEventListener(EventListener listener, Event event) {
Set<EventListener> listeners = listenerMap.get(event);
listeners.add(listener);
}
public void fire(Event event) {
Set<EventListener> listeners = listenerMap.get(event);
for (EventListener listener : listeners) {
listener.handle(event);
}
}
}
评论:
如果EventListnerImpl
的switch
语句仅注册到单个事件,或者它应始终以相同的方式操作,而不管它接收哪个Event
,则可以省略它。
EventRegister
已将EventListener
存储在一个映射中,这意味着每个侦听器只会获得它已订阅的Event
类型。 此外, EventRegister
使用Set
s,这意味着EventListener
最多只接收一次事件(以防止如果有人意外地将侦听器注册两次,侦听器将接收两个事件)。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.