The Artima Developer Community
Sponsored Link

Ruby Buzz Forum
Patterns in Ruby: Observer Pattern

0 replies on 1 page.

Welcome Guest
  Sign In

Go back to the topic listing  Back to Topic List Click to reply to this topic  Reply to this Topic Click to search messages in this forum  Search Forum Click for a threaded view of the topic  Threaded View   
Previous Topic   Next Topic
Flat View: This topic has 0 replies on 1 page
Christopher Williams

Posts: 88
Nickname: sgtcoolguy
Registered: Apr, 2006

Christopher Williams is a Ruby, Rails and Java programmer
Patterns in Ruby: Observer Pattern Posted: Nov 2, 2006 1:32 PM
Reply to this message Reply

This post originated from an RSS feed registered with Ruby Buzz by Christopher Williams.
Original Post: Patterns in Ruby: Observer Pattern
Feed Title: Late to the Party
Feed URL: http://cwilliams.textdriven.com/xml/rss20/feed.xml
Feed Description: Ruby. Rails. Stuff.
Latest Ruby Buzz Posts
Latest Ruby Buzz Posts by Christopher Williams
Latest Posts From Late to the Party

Advertisement

Another easy to implement pattern in Ruby is the Observer pattern. The Observer pattern is a publish/subscribe mechanism where an objects can register to be notified of state changes (or observe changes) on another observed object. This pattern may often become refactored into a more general event framework (where objects fire events off into queues to which there are listeners subscribed).

The basic implementation

Here's a look at a simple ruby implementation:

class Observable
  def initialize
    @listeners = []
  end

  def register_listener(listener)
    @listeners << listener
  end

  def unregister_listener(listener)
    @listeners.remove(listener)
  end

  def run
    notify_listeners("Hello!")
  end

  protected
  def notify_listeners(event)
    @listeners.each {|l| l.notify(event) }
  end
end

class Listener
  def initialize(observable)
    observable.register_listener(self)
  end

  def notify(event)
    puts "Notified of '#{event}'"
  end
end

observable = Observable.new
listener = Listener.new(observable)
observable.run                        #=> Notified of 'Hello!'

The pattern itself in this form is pretty general. So general, in fact, that there is a module mixin of Observer inside the standard ruby library (observer.rb). There's some good documentation in there, and it provides a simpler path to this implementation:

require "observer"

class TV
  include Observable
  def initialize(channel)
    @channel = channel
  end

  def up
    @channel += 1
    changed
    notify_observers(@channel)
  end
end

class ChannelWatcher
  def initialize(tv)
    tv.add_observer(self)
  end

  def update(channel)
    puts "Changed channel to #{channel}"
  end
end

tv = TV.new(160)
watcher = ChannelWatcher.new(tv)
tv.up                              #=> Changed channel to 161

Please be aware that the API is a little different from my initial example.

Moving towards events

Both of the above implementations rely on a generic observer pattern, but the Observer pattern can often evolve into a simple event mechanism. The difference is that instead of firing a generic event object via a generic notify method, the move towards events uses unique method names, and filters events to notify only those interested in the type of event occurring.

Let's see a TV example where we move towards a more specialized event firing version of the pattern:

require "observer"

class TV
  def initialize(channel)
    @channel = channel
    @listeners = []
  end

  def add_listener(listener)
    @listeners << listener
  end

  def up
    @channel += 1
    @listeners.each {|l| l.channel_increased(@channel) }
  end

  def down
    @channel -= 1
    @listeners.each {|l| l.channel_decreased(@channel) }
  end
end

class ChannelUpWatcher
  def initialize(tv)
    tv.add_listener(self)
  end

  def channel_increased(channel)
    puts "Changed channel to #{channel}"
  end

  def channel_decreased
    # do nothing...
  end
end

tv = TV.new(160)
watcher = ChannelUpWatcher.new(tv)
tv.up                              #=> Changed channel to 161

In this instance we can fire off events for surfing the Tv upwards or downwards (in channels) separately, though we still register listeners into a generic pool, and listeners are expected to contain both event methods. Variations of this can be done to register listeners into sub-groups upon registration by calling unique methods names for each registration, or by passing in a Filter object that can be used to filter to the events the listener cares about. In filtering at registration we can avoid listeners having to implement every event firing method and minimize the number of events fired off.

One illustration of this observer based event model is the Java Swing events API.

Extending the pattern towards this event firing mechanism even further we'd likely move into using queues and firing events off to the queues themselves rather than directly to observers. Observers would then become subscribers to the queues.

Using blocks and procs

The Observer pattern as described above is the typical pattern followed in most languages without closures, lambdas or functors. In Ruby we have the ability to throw around closures/blocks so we can take the pattern a little further.

Let's revisit our original implementation, but let's add the ability to register the callback function to be performed upon notification.

class Observable
  def initialize
    @listeners = []
  end

  def register_listener(&blk)
    @listeners << blk
  end

  def unregister_listener(&blk)
    @listeners.remove(blk)
  end

  def run
    notify_listeners("Hello!")
  end

  protected
  def notify_listeners(event)
    @listeners.each {|l| l.call(event) }
  end
end

class Listener
  def initialize(observable)
    observable.register_listener {|event| "Notified of '#{event}'"}
  end
end

observable = Observable.new
listener = Listener.new(observable)
observable.run                        #=> Notified of 'Hello!'

This is the model illustrated in the Tk bindings for Ruby - you can see examples of usage in Programming Ruby's section on binding events in Tk. That section and their section on blocks as closures begin to broach the how closures capture the context in which they were defined - allowing for some very interesting and complex behavior in using blocks and procs as event or observer callbacks (allowing you to refer to objects available at the scope of the block definition, not when the callback/block execution occurs).

Read: Patterns in Ruby: Observer Pattern

Topic: About that Rails API Previous Topic   Next Topic Topic: Jamis is on fire and The Tao of Rails

Sponsored Links



Google
  Web Artima.com   

Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use