Feb 11 2007

IO Language Event Dispatcher

Callbacks are a great way of keeping different Objects loosely coupled, without having to hard code direct references or calls between collaborators. Languages that support blocks/closures offer an elegant and flexible alternative to approaches of the Java interface style.

EventDispatcher := Object clone do(

  listeners := Map clone

  subscribe := method(event, callback,
    listeners hasKey(event) ifFalse(listeners atPut(event, List clone))
    listeners at(event) append(callback)
  )

  notify := method(event, args,
    if(listeners hasKey(event),
      listeners at(event) foreach(callback, callback call(args))
    )
  )
)

The above EventDispatcher Object comes with two methods, subscribe and notify. The most interesting bit is how subscribe maps registered callbacks to their respective events. Upon notification (the notify method), every callback registered with the given event will execute.

factory := EventDispatcher clone do(
  produceWidget := method(color,
    "I am creating a #{color} widget" interpolate println
    notify("new_widget", color)
  )
)

The code above signifies that every time the Factory produces a Widget, it will notify any listener interested in monitoring the production of new widgets. Interestingly, the factory knows nothing about listeners. IO scores some extra, not visible in this example, points by supporting Multiple Inheritance (sounds scary, but it's closer to Ruby's Mixins, rather than C++). Any ol' Object can have the EventDispatcher's functionality available by adding EventDispatcher to it's list of Protos.

Following is an example Listener that registers its intentions of being notified about the creation of new Widgets with the Factory.

widgetCounter :=  Object clone do(

  counts := Map clone

  factory subscribe("new_widget", block(color,
      counts hasKey(color) ifFalse(counts atPut(color, 0))
      counts atPut (color, counts at(color) nextInSequence)
      "#{counts at (color)} #{color} widget(s)
		created since I started listening" interpolate println
    )
  )
)

Now, if the factory produces a few Widgets...

factory produceWidget("blue")
factory produceWidget("green")
factory produceWidget("blue")
factory produceWidget("blue")
factory produceWidget("red")

widgetCounter keeps track of the them, in respects to their colors...

~/Desktop $ io EventDispatcher.io
I am creating a blue widget
1 blue widget(s) created since I started listening
I am creating a green widget
1 green widget(s) created since I started listening
I am creating a blue widget
2 blue widget(s) created since I started listening
I am creating a blue widget
3 blue widget(s) created since I started listening
I am creating a red widget
1 red widget(s) created since I started listening

The example was inspired and is a port of the recipe described in chapter 7.11 Coupling Systems Loosely with Callbacks found in O'Reilly's Ruby Cookbook by Lucas Carlson and Leonard Richardson