# The Observer Design Pattern in Ruby # An example of OOP design and implementation in an untyped language. # The Observer pattern sets up a one to many automatic interaction scheme # between objects. ## Interface description: # Each "observee" object contains a list of "observers". # When some event (change) occurs in an observee, each of its observer # objects is "notified". # When an observer is notified by an observee, it executes an "update" procedure # There are several different kinds of observers, each usually implemented # as a subclass. ## The observer pattern is a very commonly used schema of oop design as it ## allows objects to behave and interact autonomously ("let objects decide ## for themselves"). # In this example, our observers are stock investors and the observees are # individual stocks. Notification occurs when the price of a stock changes. # Each observer can respond differently to a notification via dynamic # dispatch. In fact, "dynamic" dispatch is the only form possible in # languages like Ruby, since there are no "static" types. ## observee class class Stock def initialize(sym,pr) # stock info consist of a symbol and price @symbol = sym @price = [pr] # prices are kept as a stack to remember history. @observers = [] # set initial empty array of observers. end def attach(investor) # add observer @observers.push(investor) # add new observer end def detach(investor) # remove observer @observers.delete(investor) # delete observer end def notify() # notify all observers for obsv in @observers obsv.update(self) # update observer, with pointer to self (this) end #for end def change(diff) # change is called with +/- value when price changes. newprice = diff + @price[@price.length-1] @price.push(newprice) notify() end def currentPrice() @price[@price.length - 1] end attr_reader :price, :symbol # make these readable properties end # class Stock #### observer superclass class Investor def initialize() @portfolio = {} # hash table record stock symbol and number of shares end def invest(stock,shares) stock.attach(self) @portfolio[stock.symbol] = shares end def dump(stock) # sell all shares stock.detach(self) @portfolio[stock.symbol] = 0 end def report() # print portfolio @portfolio.each_key {|x| print (@portfolio[x], " shares of ", x, "\n") } end ## the update function is left to subclasses, the default does nothing def update(stock) end end #class Investor ##### Concrete investor classes # a bear is a conservative investor class BearInvestor < Investor def initialize() super() end # A bear will analyse price fluxuations carefully. # The following function returns a -/+ value indicating buy, sell def analyze(st) # analyze price changes carefully diff = 0 # average change in price i = 0 while i < st.price.length - 1 diff += st.price[i+1] - st.price[i] i += 1 end #while diff end def update(st) # notify from stock st diff = analyze(st) if (diff>10) puts "BUY" @portfolio[st.symbol] += 100 # buy 100 more shares end if (diff < -100) puts "SELL EVERYTHING!" dump(st) end end #update end #bear # a bull is an agreesive investor class BullInvestor < Investor # default constructor auto-created # a bull investor will buy more stock whenever the price rises def update(st) curprice = st.currentPrice() prevprice = st.price[st.price.length - 2] if (curprice> prevprice) puts "BUY BUY BUY!" @portfolio[st.symbol] += 1000 end end # update end #bull ###### test - code like the following is supposedly what makes oop worthwhile intel = Stock.new("intc",19.8) amd = Stock.new("amd",7.04) microsoft = Stock.new("msft",24.85) apple = Stock.new("aapl",292.38) me = BearInvestor.new() you = BullInvestor.new() me.invest(intel,100) me.invest(apple,20) you.invest(amd,500) you.invest(microsoft,200) intel.change(+1.5) microsoft.change(-0.5) apple.change(+20) amd.change(+0.2) me.report() you.report() # How is type information (class that an object is an instance of) used here? # Can there be other uses of type information?