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
-
#add_observer(observer, func = :update) ⇒ Object
Add +observer+ as an observer on this object.
-
#changed(state = true) ⇒ Object
Set the changed state of this object.
-
#changed? ⇒ Boolean
Returns true if this object's state has been changed since the last #notify_observers call.
-
#count_observers ⇒ Object
Return the number of observers associated with this object.
-
#delete_observer(observer) ⇒ Object
Remove +observer+ as an observer on this object so that it will no longer receive notifications.
-
#delete_observers ⇒ Object
Remove all observers associated with this object.
-
#notify_observers(*arg) ⇒ Object
Notify observers of a change in state if this object's changed state is +true+.
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.
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_observers ⇒ Object
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_observers ⇒ Object
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 |