Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 2adb9fd4a5
Fetching contributors…

Cannot retrieve contributors at this time

file 202 lines (193 sloc) 5.842 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
#
# Implementation of the _Observer_ object-oriented design pattern. The
# following documentation is copied, with modifications, from "Programming
# Ruby", by Hunt and Thomas; http://www.ruby-doc.org/docs/ProgrammingRuby/html/lib_patterns.html.
#
# See Observable for more info.

# 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 <tt>@symbol</tt>. 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 "--- #{time.to_s}: Price below #@limit: #{price}\n"
# end
# end
# end
#
# class WarnHigh < Warner
# def update(time, price) # callback for observer
# if price > @limit
# print "+++ #{time.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
module Observable

  #
  # 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
  def add_observer(observer, func=:update)
    @observer_peers = {} unless defined? @observer_peers
    unless observer.respond_to? func
      raise NoMethodError, "observer does not respond to `#{func.to_s}'"
    end
    @observer_peers[observer] = func
  end

  #
  # Remove +observer+ as an observer on this object so that it will no longer
  # receive notifications.
  #
  # +observer+:: An observer of this Observable
  def delete_observer(observer)
    @observer_peers.delete observer if defined? @observer_peers
  end

  #
  # Remove all observers associated with this object.
  #
  def delete_observers
    @observer_peers.clear if defined? @observer_peers
  end

  #
  # Return the number of observers associated with this object.
  #
  def count_observers
    if defined? @observer_peers
      @observer_peers.size
    else
      0
    end
  end

  #
  # 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.
  #
  def changed(state=true)
    @observer_state = state
  end

  #
  # Returns true if this object's state has been changed since the last
  # #notify_observers call.
  #
  def changed?
    if defined? @observer_state and @observer_state
      true
    else
      false
    end
  end

  #
  # 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 <tt>*arg</tt>.
  # The changed state is then set to +false+.
  #
  # <tt>*arg</tt>:: Any arguments to pass to the observers.
  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

end
Something went wrong with that request. Please try again.