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

时间:2011-11-22 18:13:14

标签: java addeventlistener event-listener type-erasure

我正在实现一个客户端 - 服务器系统,其中客户端处于连续阻塞读取循环中,侦听来自服务器的消息。当收到消息时,我想根据消息的类型引发“事件”,其他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);

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

4 个答案:

答案 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最多只会接收一次事件(以防止如果有人意外地将侦听器注册两次,侦听器将收到两个事件) 。