Search code examples
oopscalaobserver-pattern

How can I create this kind of interface for an observer system?


addListener[FooEvent] { e => println("Got a FooEvent") }
dispatchEvent(new FooEvent())

Is this even possible? I'd need to be able to convert the type parameter in the addListener method to a string and store the listeners in a map (eventtype -> function) or something. Then for dispatchEvent I'd need to determine the string type for the parameter to retrieve the listeners for that event.

I'm unsure of the implementation but I'd really like to use this kind of interface instead of having to declare 2 methods for each event type on an observable object. Any ideas?


Solution

  • Yes it is possible and quite straight forward. You have to remember though, that you need to hold on to the reference of a function you pass in if you want to remove it from the listeners again. What you could do is something like this:

    import collection.mutable.ArrayBuffer
    
    val listeners = ArrayBuffer[FooEvent => Unit]()
    
    def addListener(f: FooEvent => Unit) = {
      listeners += f
      f
    }
    
    def removeListener(f: FooEvent => Unit): Unit = {
      listeners -= f
    }
    
    def dispatchEvent(e: FooEvent) = listeners.foreach(f => f(e))
    

    addListener returns the function so you can store it in val for removing it later:

    val listener = foo.addListener { e => println(e) }
    foo.dispatchEvent(FooEvent(3))
    foo.removeListener(listener)
    

    If you want to supply the event type as a type parameter you have to additionally take an implicit Manifest or ClassManifest parameter:

    import collection.mutable.{ArrayBuffer, HashMap}
    
    val listeners = HashMap[Class[Any],ArrayBuffer[Any]]()
    
    def addListener[A](f: A => Unit)(implicit mf: ClassManifest[A]): A => Unit = {
      listeners.getOrElseUpdate(mf.erasure.asInstanceOf[Class[Any]], ArrayBuffer[Any]()) += f
      f
    }
    
    def dispatch[A](e: A)(implicit mf: ClassManifest[A]) =
      for {
        evListeners <- listeners.get(mf.erasure.asInstanceOf[Class[Any]])
        listener <- evListeners
      } listener.asInstanceOf[A => Unit] apply e
    }
    

    And using it:

    addListener[FooEvent] { e => println(e.x) }
    dispatch(FooEvent(3))