Reputation: 15018
In my program I have something close to 50 events that are thrown out. The issue I'm having is settling on a standard way to recieve the events.
My first thought was to use a generic interface and just have listeners implement this and add themselves to the queue of listeners. Unfortunately this meant that classes couldn't have multiple listeners due to type erasure and multiple inheritance issues. So I removed the listener code and started over again.
My current (and origional) setup is to have a interface that listeners for each event. Eg
/**
* Listener for {@link myproject.hooks.events.FingerEvent}events
* @see myproject.hooks.events.Finger
*/
public interface FingerListener extends Listener {
/**
* Invoked when an {@link myproject.hooks.events.FingerEvent}occurs
* @param event The generated FingerEvent
*/
public void onFinger(FingerEvent event);
}
They all extend the interface Listener
which doesn't contain any methods, its just so that they can all be collectively called "Listeners".
The issue with this is that there is a ton of code per event. You have the event and its getters (thankfully this was simplified by Project Lombok), then an interface that receives it, then javadoc on the interface so that when someone wants information they know where to get it.
The other issue I have is that each listener has a different method name, which leads to very interesting code trying to figure out which method to call. Its fragile, slow (uses reflection), and looks like crap. If you don't believe me, then:
public static boolean callListener(Event event, Listener listener) {
//Get base name of event
String name = event.getClass().getSimpleName().split("Event")[0];
//Try and get the correct method if it exists
Method listenerMethod = null;
try {
listenerMethod = listener.getClass().getMethod("on"+name, event.getClass());
} catch (NoSuchMethodException ex) {
//Method doesn't exist, just don't call anything
return false;
} catch (SecurityException ex) {
throw new RuntimeException("Method on"+name+" is unaccessable", ex);
}
//Now that we have the method, attempt to execute it
try {
listenerMethod.invoke(listener, event);
} catch (Exception ex) {
throw new RuntimeException("Unexpected error when invoking method on"+name);
}
//Method executed sucessfully, return true
return true;
}
Is this though the standard way to receive events in java: Have a listener interface for each event then create spaghetti to call the appropriate methods, or am I doing this completely wrong?
Upvotes: 1
Views: 215
Reputation: 114817
At least you can get rid of the reflection/invocation logic by simplifying delegating the "event processing" to the listener. A Listener knows what type of events it can handle, so just add one method to your marker interface, like:
public interface Listener {
public boolean process(Event event);
}
and change the code like this:
public static boolean callListener(Event event, Listener listener) {
return listener.process(event);
}
Now, if we have a Listener that understands the events Breakfast
and Dinner
, we can implement it like this (in MealListener
):
public class MealListener implements Listener {
@Override
public boolean process(Event event) {
if (event instanceof Breakfast) {
this.onBreakfast((Breakfast) event);
return true;
}
if (event instanceof Dinner) {
this.onDinner((Dinner) event);
return true;
}
return false; // MealListener ignores this event
}
private void onBreakfast(Breakfast breakfastCall) {
// eat breakfast
}
private void onDinner(Dinner dinnerCall) {
// eat dinner
}
}
By the way - don't be afraid of "tons of classes" - just find a common source pattern for all events and listeners and autogenerate the source files. In this case, you don't have to maintain the individual event and listener source files but just the code generator and its resource file (a file based list with base names for all events and listeners)
Upvotes: 2
Reputation:
Your design is wrong. Whatever class is firing the events, should call the methods defined in the interface.
So your interface should look something like this:
public interface FingerListener { public void listenerCallback(FingerEvent event); }
And then you can simply iterate over your listeners and call the listenerCallback
method for each registered Listener.
If the receiver needs to distinguish between different types of events, I would add a "type code" property to the FingerEvent class that can be inspected by the receiver.
Upvotes: 0