/tools/Ruby/lib/ruby/1.8/observer.rb

http://github.com/agross/netopenspace · Ruby · 192 lines · 67 code · 4 blank · 121 comment · 3 complexity · fd35b15939bd094f29376e0a90b14e2b MD5 · raw file

  1. #
  2. # observer.rb implements the _Observer_ object-oriented design pattern. The
  3. # following documentation is copied, with modifications, from "Programming
  4. # Ruby", by Hunt and Thomas; http://www.rubycentral.com/book/lib_patterns.html.
  5. #
  6. # == About
  7. #
  8. # The Observer pattern, also known as Publish/Subscribe, provides a simple
  9. # mechanism for one object to inform a set of interested third-party objects
  10. # when its state changes.
  11. #
  12. # == Mechanism
  13. #
  14. # In the Ruby implementation, the notifying class mixes in the +Observable+
  15. # module, which provides the methods for managing the associated observer
  16. # objects.
  17. #
  18. # The observers must implement the +update+ method to receive notifications.
  19. #
  20. # The observable object must:
  21. # * assert that it has +changed+
  22. # * call +notify_observers+
  23. #
  24. # == Example
  25. #
  26. # The following example demonstrates this nicely. A +Ticker+, when run,
  27. # continually receives the stock +Price+ for its +@symbol+. A +Warner+ is a
  28. # general observer of the price, and two warners are demonstrated, a +WarnLow+
  29. # and a +WarnHigh+, which print a warning if the price is below or above their
  30. # set limits, respectively.
  31. #
  32. # The +update+ callback allows the warners to run without being explicitly
  33. # called. The system is set up with the +Ticker+ and several observers, and the
  34. # observers do their duty without the top-level code having to interfere.
  35. #
  36. # Note that the contract between publisher and subscriber (observable and
  37. # observer) is not declared or enforced. The +Ticker+ publishes a time and a
  38. # price, and the warners receive that. But if you don't ensure that your
  39. # contracts are correct, nothing else can warn you.
  40. #
  41. # require "observer"
  42. #
  43. # class Ticker ### Periodically fetch a stock price.
  44. # include Observable
  45. #
  46. # def initialize(symbol)
  47. # @symbol = symbol
  48. # end
  49. #
  50. # def run
  51. # lastPrice = nil
  52. # loop do
  53. # price = Price.fetch(@symbol)
  54. # print "Current price: #{price}\n"
  55. # if price != lastPrice
  56. # changed # notify observers
  57. # lastPrice = price
  58. # notify_observers(Time.now, price)
  59. # end
  60. # sleep 1
  61. # end
  62. # end
  63. # end
  64. #
  65. # class Price ### A mock class to fetch a stock price (60 - 140).
  66. # def Price.fetch(symbol)
  67. # 60 + rand(80)
  68. # end
  69. # end
  70. #
  71. # class Warner ### An abstract observer of Ticker objects.
  72. # def initialize(ticker, limit)
  73. # @limit = limit
  74. # ticker.add_observer(self)
  75. # end
  76. # end
  77. #
  78. # class WarnLow < Warner
  79. # def update(time, price) # callback for observer
  80. # if price < @limit
  81. # print "--- #{time.to_s}: Price below #@limit: #{price}\n"
  82. # end
  83. # end
  84. # end
  85. #
  86. # class WarnHigh < Warner
  87. # def update(time, price) # callback for observer
  88. # if price > @limit
  89. # print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
  90. # end
  91. # end
  92. # end
  93. #
  94. # ticker = Ticker.new("MSFT")
  95. # WarnLow.new(ticker, 80)
  96. # WarnHigh.new(ticker, 120)
  97. # ticker.run
  98. #
  99. # Produces:
  100. #
  101. # Current price: 83
  102. # Current price: 75
  103. # --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
  104. # Current price: 90
  105. # Current price: 134
  106. # +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
  107. # Current price: 134
  108. # Current price: 112
  109. # Current price: 79
  110. # --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79
  111. #
  112. # Implements the Observable design pattern as a mixin so that other objects can
  113. # be notified of changes in state. See observer.rb for details and an example.
  114. #
  115. module Observable
  116. #
  117. # Add +observer+ as an observer on this object. +observer+ will now receive
  118. # notifications.
  119. #
  120. def add_observer(observer)
  121. @observer_peers = [] unless defined? @observer_peers
  122. unless observer.respond_to? :update
  123. raise NoMethodError, "observer needs to respond to `update'"
  124. end
  125. @observer_peers.push observer
  126. end
  127. #
  128. # Delete +observer+ as an observer on this object. It will no longer receive
  129. # notifications.
  130. #
  131. def delete_observer(observer)
  132. @observer_peers.delete observer if defined? @observer_peers
  133. end
  134. #
  135. # Delete all observers associated with this object.
  136. #
  137. def delete_observers
  138. @observer_peers.clear if defined? @observer_peers
  139. end
  140. #
  141. # Return the number of observers associated with this object.
  142. #
  143. def count_observers
  144. if defined? @observer_peers
  145. @observer_peers.size
  146. else
  147. 0
  148. end
  149. end
  150. #
  151. # Set the changed state of this object. Notifications will be sent only if
  152. # the changed +state+ is +true+.
  153. #
  154. def changed(state=true)
  155. @observer_state = state
  156. end
  157. #
  158. # Query the changed state of this object.
  159. #
  160. def changed?
  161. if defined? @observer_state and @observer_state
  162. true
  163. else
  164. false
  165. end
  166. end
  167. #
  168. # If this object's changed state is +true+, invoke the update method in each
  169. # currently associated observer in turn, passing it the given arguments. The
  170. # changed state is then set to +false+.
  171. #
  172. def notify_observers(*arg)
  173. if defined? @observer_state and @observer_state
  174. if defined? @observer_peers
  175. for i in @observer_peers.dup
  176. i.update(*arg)
  177. end
  178. end
  179. @observer_state = false
  180. end
  181. end
  182. end