Monday, April 20, 2009

Scala Listener Manager

Java uses the Listener pattern, a version of Publish-Subscribe, to decouple the processing of events from their generation. This is a nice pattern, but I was annoyed by the amount of boilerplate I had to add to my Java code every time I had a publishing class that needed to manage listeners.

As a subscriber, the Java code for adding a listener is not bad: just call the publisher's addListener method, typically passing an anonymous inner class that defines the callback method. But on the publisher side, you have to implement addListener, removeListener and fireEvent methods for the published event.

In Scala, all of that publisher boilerplate to manage listeners can be replaced by three words. Sweet! And the inner class boilerplate in the subscriber disappears also.

I implemented a Scala trait called ListenerManager to be used by a publisher. This trait defines the methods to add and remove listeners, and to fire off the published events:
trait ListenerManager[E] { type L = (E) => Unit private var listeners: List[L] = Nil private var listenersLock = new Object() /** True if the listener is already in our list. */ private def isListener(listener:L) = listeners.exists(_==listener) /** Add a listener to our list if it is not already there. */ def addListener(listener:L) = listenersLock.synchronized { if (!isListener(listener)) listeners = listener :: listeners } /** Remove a listener from our list. If not in the list, ignored. */ def removeListener(listener:L):Unit = listenersLock.synchronized { listeners = listeners.filter(_!=listener) } /** Apply the given closure to all of the listeners in the list. */ protected def fireEvent(event:E) = listenersLock.synchronized { listeners.foreach(_.apply(event)) } }
To add the ability to a class to publish an event, just add with ListenerManager[MyEvent] (these are the three words mentioned above) to the defining class line for that class. This will define the addListener and removeListener methods for subscribers to call, and the fireEvent method for the publisher to call.

Let's take a look at the above code in more detail.

The type [E] in the ListenerManager trait declaration is the type of the event to be published. The publisher class would typically define a one-line case class for that event type that lists the data of interest. For example:
case class ButtonEvent(source:Button, action:Integer)
The first line of the body of ListenerManager defines the type of the listeners we are managing, which is a function that takes an instance of our event and does not return a value. In the subscriber, the code to add a listener passes in a function of this type, which can be specified in-line as a closure:
publisher.addListener((x:ButtonEvent)=>(println("Button info:"+x)))
The call to println could be replaced by a call to a local method that does more complicated processing of the event.

Next we define a list of listeners and the add and remove methods to manipulate that list. Our private isListener method, used to ensure that a listener does not get into the list more than once, is only used in one place so could have been done in-line, but has been written separately to add a bit of clarity to the code.

Last is the fireEvent method that publishes the event. For each listener in our list, it calls that listener's apply method, which is how to invoke a function when given it as an object.

The addListener, removeListener and fireEvent methods are synchronized in case the application is using separate threads for the publisher and each subscriber. We can't lock on the listeners list because we recreate that each time a listener is added and removed, and to minimize locking contention we would rather not lock on the publishing object, so we add another object (listenersLock) just for locking the methods that access the listener list, and we synchronize on that object.

Synchronizing the fireEvent method means that any subscriber calling addListener or removeListener will block until all of the callbacks are done. We could choose not to synchronize fireEvent to prevent that blocking, and that method will still work because once it picks up the listeners list and starts iterating through it, it will continue to use that list even if someone else calls addListener or removeListener. However, doing so would mean it would be possible for a listener to be called after the subscriber has called removeListener, which is probably more of an issue than blocking when adding or removing a listener while callbacks are happening. We will assume that listeners will follow the standard guideline that they should keep processing during the callback to a minimum, and that it they need to do a lot of work based on a callback, they will implement a mechanism to do that work in another thread so as not to use up a lot of time on the publisher's thread.

For the subscriber, using ListenerManager saves the boilerplate of an inner class. For the publisher, using ListenerManager saves a lot more, making it possible to set up publication just by defining a case class for the published event, adding the with declaration to the class definition, and adding calls to the fireEvent method at publication points. Pretty easy.

I could have implemented the ListenerManager trait to take an instance of a callback class with an action method, as is done in Java. Or, to take more of a Scala approach, I could have switched from using the Listener pattern as is used in Java to an Actor-based Publish/Subscribe implementation. For this particular case I chose a simple path down the middle, keeping the basic Java Listener concept but eliminating the boilerplate.