Module: Observable

Defined in:
opal/stdlib/observer.rb

Overview

The Observer pattern (also known as publish/subscribe) provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.

== Mechanism

The notifying class mixes in the +Observable+ module, which provides the methods for managing the associated observer objects.

The observers must implement a method called +update+ to receive notifications.

The observable object must:

  • assert that it has +#changed+
  • call +#notify_observers+

=== Example

The following example demonstrates this nicely. A +Ticker+, when run, continually receives the stock +Price+ for its @symbol. A +Warner+ is a general observer of the price, and two warners are demonstrated, a +WarnLow+ and a +WarnHigh+, which print a warning if the price is below or above their set limits, respectively.

The +update+ callback allows the warners to run without being explicitly called. The system is set up with the +Ticker+ and several observers, and the observers do their duty without the top-level code having to interfere.

Note that the contract between publisher and subscriber (observable and observer) is not declared or enforced. The +Ticker+ publishes a time and a price, and the warners receive that. But if you don't ensure that your contracts are correct, nothing else can warn you.

require "observer"

class Ticker ### Periodically fetch a stock price. include Observable

def initialize(symbol)
  @symbol = symbol
end

def run
  lastPrice = nil
  loop do
    price = Price.fetch(@symbol)
    print "Current price: #{price}\n"
    if price != lastPrice
      changed                 # notify observers
      lastPrice = price
      notify_observers(Time.now, price)
    end
    sleep 1
  end
end

end

class Price ### A mock class to fetch a stock price (60 - 140). def Price.fetch(symbol) 60 + rand(80) end end

class Warner ### An abstract observer of Ticker objects. def initialize(ticker, limit) @limit = limit ticker.add_observer(self) end end

class WarnLow < Warner def update(time, price) # callback for observer if price < @limit print "--- #timetime.to_s: Price below #@limit: #price\n" end end end

class WarnHigh < Warner def update(time, price) # callback for observer if price > @limit print "+++ #timetime.to_s: Price above #@limit: #price\n" end end end

ticker = Ticker.new("MSFT") WarnLow.new(ticker, 80) WarnHigh.new(ticker, 120) ticker.run

Produces:

Current price: 83 Current price: 75 --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75 Current price: 90 Current price: 134 +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134 Current price: 134 Current price: 112 Current price: 79 --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79

Instance Method Summary collapse

Instance Method Details

#add_observer(observer, func = :update) ⇒ Object

Add +observer+ as an observer on this object. so that it will receive notifications.

+observer+:: the object that will be notified of changes. +func+:: Symbol naming the method that will be called when this Observable has changes.

     This method must return true for +observer.respond_to?+ and will
     receive <tt>*arg</tt> when #notify_observers is called, where
     <tt>*arg</tt> is the value passed to #notify_observers by this
     Observable

126
127
128
129
130
131
132
# File 'opal/stdlib/observer.rb', line 126

def add_observer(observer, func=:update)
  @observer_peers = {} unless defined? @observer_peers
  unless observer.respond_to? func
    raise NoMethodError.new("observer does not respond to `#{func.to_s}'", func.to_s)
  end
  @observer_peers[observer] = func
end

#changed(state = true) ⇒ Object

Set the changed state of this object. Notifications will be sent only if the changed +state+ is +true+.

+state+:: Boolean indicating the changed state of this Observable.


167
168
169
# File 'opal/stdlib/observer.rb', line 167

def changed(state=true)
  @observer_state = state
end

#changed?Boolean

Returns true if this object's state has been changed since the last

notify_observers call.

Returns:


175
176
177
178
179
180
181
# File 'opal/stdlib/observer.rb', line 175

def changed?
  if defined? @observer_state and @observer_state
    true
  else
    false
  end
end

#count_observersObject

Return the number of observers associated with this object.


153
154
155
156
157
158
159
# File 'opal/stdlib/observer.rb', line 153

def count_observers
  if defined? @observer_peers
    @observer_peers.size
  else
    0
  end
end

#delete_observer(observer) ⇒ Object

Remove +observer+ as an observer on this object so that it will no longer receive notifications.

+observer+:: An observer of this Observable


139
140
141
# File 'opal/stdlib/observer.rb', line 139

def delete_observer(observer)
  @observer_peers.delete observer if defined? @observer_peers
end

#delete_observersObject

Remove all observers associated with this object.


146
147
148
# File 'opal/stdlib/observer.rb', line 146

def delete_observers
  @observer_peers.clear if defined? @observer_peers
end

#notify_observers(*arg) ⇒ Object

Notify observers of a change in state if this object's changed state is +true+.

This will invoke the method named in #add_observer, passing *arg. The changed state is then set to +false+.

*arg:: Any arguments to pass to the observers.


191
192
193
194
195
196
197
198
199
200
# File 'opal/stdlib/observer.rb', line 191

def notify_observers(*arg)
  if defined? @observer_state and @observer_state
    if defined? @observer_peers
      @observer_peers.each do |k, v|
        k.send v, *arg
      end
    end
    @observer_state = false
  end
end