我正在实现一个客户端 - 服务器系统,其中客户端处于连续阻塞读取循环中,侦听来自服务器的消息。当收到消息时,我想根据消息的类型引发“事件”,其他GUI类可以添加监听器。我对C#事件更熟悉,所以我仍然习惯于Java的做事方式。
会有很多消息类型,所以我需要一个接口,称之为MessageTypeAListener,MessageTypeBListener等,每个都将包含一个句柄方法,我的GUI类将实现它。但是,将有许多类型,而不是维护每种类型的侦听器列表,并且有几个“火”方法,我想要一个大的侦听器列表和一个类型的fire方法。然后fire方法可以说“只有那些类型是我指定的火灾听众。”
所以例如(伪代码):
ListenerList.Add(MessageTypeAListener);
ListenerList.Add(MessageTypeBListener);
<T> fire(message) {
ListenerList.Where(type is T).handle(message)
}
...
fire<MessageTypeAListener>(message);
然而,类型擦除似乎使这很困难。我可以尝试投射和捕获异常,但这似乎是错误的。是否有一种干净的方式来实现这一点,或者为每种类型保留一个单独的侦听器列表是否更明智,即使有大量的类型?
答案 0 :(得分:2)
我实现了类似的东西,因为我对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>>
上面可能有一些错别字......
答案 1 :(得分:1)
老式模式方法,使用Visitor pattern:
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 pattern:
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();
}
}
答案 2 :(得分:1)
在完成对每个人的建议的迭代之后,我最终得到了一个非常轻微修改过的标准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方法将自己添加到该特定事件中。我希望能够在Raise
实例上调用泛型Event
方法然后解雇所有处理程序,但我不希望它们都必须具有相同的&# 34;处理&#34;方法,所以这是不可能的。相反,当我准备好解雇听众时,我就是
for (MessageTypeAListener listener : messageTypeAEvent.getListeners())
listener.onMessageTypeA(value);
我确信这不是一个新主意,并且可能已经在以前以更好/更强大的方式完成,但它对我来说很有用,而且我很满意。最重要的是,它很简单。
感谢您的帮助。
答案 3 :(得分:0)
如果您只有简单事件,即没有数据的事件或所有事件具有相同数据类型的事件, enum 可能是一种前进方式:
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);
}
}
}
评论:
switch
中的EventListnerImpl
语句只有在单个事件中注册时才会被省略,或者如果它始终以相同的方式操作,无论它收到哪个Event
EventRegister
已将EventListener
(s)存储在地图中,这意味着每个侦听器只会获得它已订阅的Event
种类。此外,EventRegister
使用Set
s,这意味着EventListener
最多只会接收一次事件(以防止如果有人意外地将侦听器注册两次,侦听器将收到两个事件) 。