Можно ли создать свой собственный список слушателей событий в Java, содержащий несколько типов слушателей? - PullRequest
4 голосов
/ 22 ноября 2011

Я внедряю систему клиент-сервер, где клиент находится в непрерывном цикле блокировки чтения, прослушивая сообщения с сервера.Когда сообщение получено, я бы хотел вызвать «событие» в зависимости от типа сообщения, к которому другие классы графического интерфейса могут добавить слушателей.Я более знаком с событиями 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);

Однако удаление типа, кажется, затрудняет это,Я мог бы попробовать бросить и поймать исключения, но это кажется неправильным.Есть ли чистый способ реализации этого или просто разумнее вести отдельный список слушателей для каждого типа, даже если будет много типов?

Ответы [ 4 ]

2 голосов
/ 22 ноября 2011

Я реализовал нечто подобное, потому что у меня внутренняя неприязнь к EventListenerList Java. Сначала вы реализуете универсальный слушатель. Я определил слушателя на основе события, которое он получал, в основном одним методом

interface GenericListener<T extends Event> {
   public void handle(T t);
}

Это избавляет вас от необходимости определять ListenerA, ListernerB и т. Д. ... Хотя вы можете сделать это по-своему с помощью ListenerA, ListenerB и т. Д., Расширяя некоторые основы, такие как MyListener. У обоих способов есть свои плюсы и минусы.

Затем я использовал CopyOnWriteArraySet для удержания всех этих слушателей. Набор - это то, что нужно учитывать, потому что слишком часто слушатели добавляются дважды неаккуратными кодировщиками. YMMV. Но, по сути, у вас есть Collection<GenericListener<T extends Event>> or a Collection<MyListener>

Теперь, как вы обнаружили, при стирании типов коллекция может содержать только один тип слушателей. Это часто проблема. Решение: используйте карту.

Поскольку я основываю все на событии, я использовал

Map<Class<T extends Event>, Collection<GenericListener<T extends Event>>>

в зависимости от класса события, получите список слушателей, которые хотят получить это событие.
Ваша альтернатива - основывать его на классе слушателя

Map<Class<T extends MyListener>, Collection<MyListener>>

Вероятно, есть некоторые опечатки выше ...

1 голос
/ 28 ноября 2011

Пройдя через итерации почти всех предложений, я закончил тем, что пошел по слегка измененному маршруту стандартных интерфейсов слушателей и списков слушателей.Я начал с 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, чтобы затем запускать все обработчики, но я не хотел, чтобы у них всех был один и тот же метод "дескриптора", так что это былоневозможно.Вместо этого, когда я готов уволить слушателей, я просто делаю

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

Я уверен, что это не новая идея, и, вероятно, это было сделано раньше и лучше / более надежно, но этоотлично работает для меня, и я доволен этим.Лучше всего, это просто.

Спасибо за помощь.

1 голос
/ 22 ноября 2011

Старомодный подход, использующий Шаблон посетителя :

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());
    }
}

Более современный подход - это просто иметь карту между классами событий, которые не должны выводить друг друга, и соответствующиеобработчики этих событий.Таким образом, вы избегаете недостатков шаблона посетителя (то есть вам нужно будет менять все свои классы посетителей, по крайней мере, базовые из них, каждый раз, когда вы добавляете новое событие).

И еще один способ - использовать Композитный узор :

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();
    }
}
0 голосов
/ 22 ноября 2011

Если у вас есть только простые события, то есть события без данных или если все события имеют одинаковые типы данных, 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 с, что означает, что EventListener будет получать событие только один раз (чтобы не дать слушателю получить два события, если кто-то случайно зарегистрирует слушателя дважды).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...