Almost in any application, even the simplest one, we’ll encounter a use for events. Events follow the Observer-Observable design pattern. However, how should this be implemented?
Clearly, the simplest solution is the following:
public interface MyEventListener {
void performMyEvent(MyClass sender);
}
public class MyClass {
private Collection<MyEventListener> listeners;
public void addMyEventListener(MyEventListener l) {
listeners.add(l);
}
public void removeMyEventListener(MyEventListener l) {
listeners.remove(l);
}
protected void fireMyEvent() {
for (MyEventListener l : listeners)
l.performMyEvent(this);
}
}
This way, listeners can implement the MyEventListener interface, and register/unregister themselves through the addMyEventListener and removeMyEventListener methods. MyClass will call fireMyEvent when it sees fit, and the listeners will perform their actions as needed.
Sounds great. But, what if inside one of the listeners’ performMyEvent method, it removes itself from being a listener by calling removeMyEventListener? An exception will be thrown, because the list was changed while being iterated!
A simple solution to this is adding a small map, to map the register/unregister requests done while the event is being fired:
public class MyClass {
private Map<MyEventListener, Boolean> delayedListeners;
private boolean isDelayed;
public void addMyEventListener(MyEventListener l) {
if (isDelayed) delayedListeners.put(l, true);
else
listeners.add(l);
}
// the remove method looks the same only places 'false' as the Boolean value
protected void fireMyEvent() {
isDelayed = true;
for (MyEventListener l : listeners)
l.performMyEvent(this);
isDelayed = false;
handleDelayed();
}
}
This fix to the code makes it more robust. When an event is fired, the isDelayed flag (set to false in the class’ constructor) is set to true. This will ensure that any registration/unregistration will not really occur, but rather be stored for later handing in the delayedListeners map.
The delayedListeners is then handled by the handleDelayed method: The method will iterate through the values of the delayedListeners map and for every listener marked with true, it will add it to the list, while for every listener marked with false it will remove it from the list, effectively doing what the code calling to the original registartion/unregistration methods intended, but at a safer time.
Note that this implementation is not synchronized: Two threads might, in a certain circumstance, accidently cause an addition to the delayed listeners map while the event is past firing the event. This can be prevented by synchronizing the three methods to some arbitary object, which might as well be the delayed listeners map or listeners collection themselves.
I have implemented a small class which encapsulates the behaviour of listener collections as described above, having a simple interface for adding and removing listeners and firing events by using an calling interface specified at construction, and I will post its code later.