In this post, I am going to use this observable module to implement a program that allow me follow stocks prices, and also calculate the average over time.
Assume that the stock market allow me to read current stock price with a function call "get_current_price(stock_symbol)". With this method, I can implement a simple class, SimpleStockWatcher, to read the current price and calculate the average as follow:
Assume that the stock market allow me to read current stock price with a function call "get_current_price(stock_symbol)". With this method, I can implement a simple class, SimpleStockWatcher, to read the current price and calculate the average as follow:
class SimpleStockWatcher
def initialize(symbol)
@symbol = symbol
@sum = 0
@count = 0
end
def update
price = get_current_price(@symbol)
@sum += price
@count += 1
show(price)
end
def show(price)
puts "Symbol #{@symbol}"
puts " Time: #{Time.now}"
puts " Current Price: #{price}"
puts " Average: #{@sum/@count}"
end
end
If I want to monitor Bank of America (BOA), and Google (GOOG), I implement the following loop:stocks = [ SimpleStockWatcher("BOA"), SimpleStockWatcher("GOOG") ]
loop do
stocks.each(&:update)
sleep(1)
end
This loop will print the stocks price and average very second.Now, I want to add more analysis, say 30 or 100 days moving average. No problem, I add more code into the SimpleStockWatcher. Then every second we get the stock prices, average, 30-day moving average, and 100-days moving average.
Let get a bit more complicate, I want to show only current price and 30-days moving average for stock "BOA". For "GOOG", I want to show current price, the average, and 100-days moving average. How do I satisfy this requirement?
Look carefully, StockWatcher is doing two things. One is get current price for symbols. For any stock, StockWatcher does this in the same way. Another part of StockWatcher is doing different analysis on the price. This part is different from stock to stock. So the analysis are parts of StockWatcher need to be able to change. If you follow my previous posts, you know we need to separate the analysis parts from StockWatcher. In this case, I separate the analysis parts into CurrentPriceAnalyzer, AverageAnalyzer, MovingAverageAnalyzer, etc. Each analyzer is responsible for only one analysis.
However, there is a specific relation between the StockWatcher and these analyzer objects. Basically, when the stock price change, the StockWatcher will notify any analyzers. Here, where the observer pattern come in. The pattern is already implemented as a module within ruby standard library, called Obervable, and I am going to use it to implement a solution to this problem.
Now, StockWatcher has only responsibility to get the stock price and notify the analyzers if the price change. Most of the implementation is taking care of by including the Observable Module:
require "observer"
class StockWatcher
include Observable
def initialize(symbol)
@symbol = symbol
@price = 0.0
end
def update
read_current_price
notify_observers(Time.now, @symbol, @price)
end
def read_current_price
next_price = get_current_price(@symbol)
if @price != next_price
changed
@price = next_price
end
end
end
Observable Module provides methods changed and notify_observers which used in the StockWatcher. The changed method need to be called when the price change. This will set an internal stage and allow the notify_observers to send notification to registered observers.
The notify_observers pass it arguments to the observers when the notification happen. The observers have to implement a method that receive the same list of argument. For example, the CurrentPrinceAnalyzer implement update method to receive the notification from StockWatcher, as shown:
class CurrentPriceAnalyzer
def update(time, symbol, price)
puts "#{time} #{symbol}: price #{price}"
end
end
And that all it need for CurrentPriceAnalyzer. It's only show the current price.On the other hard, the AveragePriceAnalyzer need to do a little bit more:
class AveragePriceAnalyzer
def initialize
@sum = 0.0
@count = 0
end
def average
@sum/@count
end
def show(time, symbol)
puts "#{time} #{symbol}: average #{average}"
end
def update(time, symbol, price)
@sum += price
@count += 1
show(time, symbol)
end
end
It's keep the sum and count how many time it's get notify, and calculate average from these internal stage.Finally I can implement the loop to get update stock price, and do analysis, as follow:
boa = StockWatcher.new("BOA")
goog = StockWatcher.new("GOOG")
current = CurrentPriceAnalyzer.new
boa_avg = AveragePriceAnalyzer.new
goog_avg = AveragePriceAnalyzer.new
thirty = MovingAverageAnalyzer.new(30)
hundred = MovingAverageAnalyzer.new(100)
boa.add_observer(current, :update)
boa.add_observer(boa_avg, :update)
boa.add_observer(thirty, :update)
goog.add_observer(current, :update)
goog.add_observer(goog_avg, :update)
goog.add_observer(hundred, :update)
loop do
boa.update
goog.update
sleep(1)
end
The method add_observer, which also provided by Observable Module, is called to add an observers to the subject, a StockWatcher object. It's also tell the subject which method to call when the observers need to be notify.
In this case, the BOA stock watcher object has 3 analyzers, current, boa_avg, and thirty. All implement update method for notification. Similarly, Google stock watcher also has 3 analyzers, current, goog_avg and hundred. Notice that the boa and goog can not use the same instance for AveragePriceAnalyzer, because it's has internal stage specific for each stock.
Again, it's come down to the word change. We still have to identify what need to be changed and separate that part out of the object. However, with the specific relation between subject and observers, we can easily identify what part need to be separated out (and become observers). The relation also provide a clear pattern, and it is clear enough to be generalized and implemented into a module. All we need to do are, include and know how to use them.
No comments:
Post a Comment