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 |
|
---|
113 | #
|
---|
114 | # Implements the Observable design pattern as a mixin so that other objects can
|
---|
115 | # be notified of changes in state. See observer.rb for details and an example.
|
---|
116 | #
|
---|
117 | module Observable
|
---|
118 |
|
---|
119 | #
|
---|
120 | # Add +observer+ as an observer on this object. +observer+ will now receive
|
---|
121 | # notifications.
|
---|
122 | #
|
---|
123 | def add_observer(observer)
|
---|
124 | @observer_peers = [] unless defined? @observer_peers
|
---|
125 | unless observer.respond_to? :update
|
---|
126 | raise NoMethodError, "observer needs to respond to `update'"
|
---|
127 | end
|
---|
128 | @observer_peers.push observer
|
---|
129 | end
|
---|
130 |
|
---|
131 | #
|
---|
132 | # Delete +observer+ as an observer on this object. It will no longer receive
|
---|
133 | # notifications.
|
---|
134 | #
|
---|
135 | def delete_observer(observer)
|
---|
136 | @observer_peers.delete observer if defined? @observer_peers
|
---|
137 | end
|
---|
138 |
|
---|
139 | #
|
---|
140 | # Delete all observers associated with this object.
|
---|
141 | #
|
---|
142 | def delete_observers
|
---|
143 | @observer_peers.clear if defined? @observer_peers
|
---|
144 | end
|
---|
145 |
|
---|
146 | #
|
---|
147 | # Return the number of observers associated with this object.
|
---|
148 | #
|
---|
149 | def count_observers
|
---|
150 | if defined? @observer_peers
|
---|
151 | @observer_peers.size
|
---|
152 | else
|
---|
153 | 0
|
---|
154 | end
|
---|
155 | end
|
---|
156 |
|
---|
157 | #
|
---|
158 | # Set the changed state of this object. Notifications will be sent only if
|
---|
159 | # the changed +state+ is +true+.
|
---|
160 | #
|
---|
161 | def changed(state=true)
|
---|
162 | @observer_state = state
|
---|
163 | end
|
---|
164 |
|
---|
165 | #
|
---|
166 | # Query the changed state of this object.
|
---|
167 | #
|
---|
168 | def changed?
|
---|
169 | if defined? @observer_state and @observer_state
|
---|
170 | true
|
---|
171 | else
|
---|
172 | false
|
---|
173 | end
|
---|
174 | end
|
---|
175 |
|
---|
176 | #
|
---|
177 | # If this object's changed state is +true+, invoke the update method in each
|
---|
178 | # currently associated observer in turn, passing it the given arguments. The
|
---|
179 | # changed state is then set to +false+.
|
---|
180 | #
|
---|
181 | def notify_observers(*arg)
|
---|
182 | if defined? @observer_state and @observer_state
|
---|
183 | if defined? @observer_peers
|
---|
184 | for i in @observer_peers.dup
|
---|
185 | i.update(*arg)
|
---|
186 | end
|
---|
187 | end
|
---|
188 | @observer_state = false
|
---|
189 | end
|
---|
190 | end
|
---|
191 |
|
---|
192 | end
|
---|