I would say it isn't a very good idea to allow listeners to add/remove other listeners (or themselves). It indicates a poor separation of concerns. After all, why should the listener know anything about the caller? How would you test such a tightly coupled system?
Something you can do instead is have your event handling method (in the listener) return a boolean flag that indicates that the listener doesn't want to receive more events. That makes it the responsibility of the event dispatcher to do the removal, and it covers most use cases where you need to modify the list of listeners from within a listener.
The major difference in this approach is that the listener simply says something about itself (i.e. "I don't want more events") rather than being tied to the implementation of the dispatcher. This decoupling improves testability and doesn't expose the internals of either class to the other.
public interface FooListener {
/**
* @return False if listener doesn't want to receive further events.
*/
public boolean handleEvent(FooEvent event);
}
public class Dispatcher {
private final List<FooListener> listeners;
public void dispatch(FooEvent event) {
Iterator<FooListener> it = listeners.iterator();
while (it.hasNext()) {
if (!it.next().handleEvent(event))
it.remove();
}
}
}
Update: Adding and removing other listeners from within a listener is slightly more problematic (and should set off even louder alarm bells) but you can follow a similar pattern: the listener should return information on what other listeners need to be added/removed and the dispatcher should act on that information.
However in this scenario you get quite a few edge cases:
- What should happen with the current event?
- Should it be dispatched to the newly added listeners?
- Should it be dispatched to the ones about to be removed?
- What if you're trying to remove something that is earlier in the list and the event has already been sent to it?
- Also, what if listener X adds listener Y and then listener X is removed? Should Y go with it?
All these problems come from the listener pattern itself and the basic assumption of it that all the listeners in the list will be independent of each other. And the logic to handle these cases should definitely go in the dispatcher and not in the listener.
Update 2: In my example I used a naked boolean for brevity but in real code I'd define a two-valued enum type to make the contract more explicit.