Is it possible to create my own list of event listeners in Java containing several types of listeners?

I implement a client-server system where the client is in a continuous cycle of reading locks, listening to messages from the server. When a message is received, I would like to raise an β€œevent” depending on the type of message to which other GUI classes can add listeners. I'm more familiar with C # events, so I'm still used to what I'm doing in Java.

There will be many types of messages, so I will need an interface for everyone, call it MessageTypeAListener, MessageTypeBListener, etc., each of which will contain one descriptor method that my GUI classes will implement. However, there will be many types, and instead of keeping a list of listeners by type and having several fire methods, I would like to have one large list of listeners and a typed method of fire. Then the method of fire could say "only listeners of fire, the type of which I indicate."

So for example (pseudo code):

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

However, erasing the type seems to make this difficult. I could try casting and catch exceptions, but that seems wrong. Is there a clean way to implement this, or is it just wiser to keep a separate list of listeners for each type, although there will be many types?

+4
source share
4 answers

I implemented something like this because I have a visceral dislike of Java's EventListenerList. First, you implement a common receiver. I determined the listener based on the event that it was receiving, mainly using one method

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

This will help you define ListenerA, ListernerB, etc. Although you can do this with ListenerA, ListenerB, etc., everyone extends some databases, such as MyListener. Both methods have pros and cons.

Then I used CopyOnWriteArraySet to hold all of these listeners. Dialing is something to keep in mind, because all too often, listeners get twice as slow encoders. YMMV. But, effectively, you have a Collection<GenericListener<T extends Event>> or a Collection<MyListener>

Now, as you have discovered, with type erasure, a collection can contain only one type of listener. This is often a problem. Decision. Use a card.

Since I base everything on an event, I used

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

depending on the class of the event, get a list of listeners who want to receive this event.
Your alternative is to base it on a listener class

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

Probably some typos above ...

+2
source

The old-fashioned approach to the template using the Visitor Template :

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

A more modern approach is to have a map between classes of events that should not display each other, and corresponding handlers for these events. This way you avoid the drawbacks of the visitor template (that is, you will need to change all of your visitor classes, at least based on them, every time you add a new event).

And another way is to use the Composite Template :

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

After going through iterations of almost all the sentences here, I ended up with a very slightly modified route for the standard Listener interfaces and listener lists. I started with a Swing EventListenerList , only to EventListenerList on the number of add / remove methods for dozens of message types. I realized that this could not be compressed while maintaining a single EventListenerList , so I started thinking of a separate list for each type. This makes it look like .NET events, where each event has its own list of delegates to run on raise. I wanted to avoid the add / remove methods, so I made a quick Event class that looks something like this:

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

Then I save several instances of this class, each of which is entered according to the listener, therefore Event<MessageTypeAListener> , etc. My classes can then call the add method to add themselves to this particular event. I would like to be able to call the general Raise method in the Event instance to then run all the handlers, but I did not want all of them to have the same β€œhandle” method, so this was not possible. Instead, when I'm ready to fire listeners, I just do

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

I am sure this is not a new idea, and probably it was done earlier and in better / more reliable ways, but it works great for me and I am happy with it. Best of all, it's simple.

Thanks for the help.

+1
source

If you have only simple events, i.e. events without data or where all events have the same data types, enumeration can be a way forward:

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

Comments:

The switch in EventListnerImpl can be omitted if it is registered for only one event, or if it must always act the same, regardless of which Event it receives.

EventRegister stored EventListener (s) on the map, which means that each listener will receive only the Event (s) to which he subscribed. In addition, EventRegister uses Set s, which means that the EventListener will receive an event no more than once (to prevent the listener from receiving two events if someone accidentally registers the listener twice).

0
source

Source: https://habr.com/ru/post/1382547/


All Articles