观察者模式
本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 44 课,共 57 课。
观察者模式允许对象订阅另一个对象(主题
class Thermometer
def initialize
@observers = []
end
def subscribe(observer)
@observers << observer
end
def temperature=(value)
@temperature = value
@observers.each { |o| o.update(value) }
end
end
class Display
def update(value)
puts "display: #{value} C"
end
end
class Alarm
def update(value)
puts "alarm: too hot!" if value > 30
end
end
t = Thermometer.new
t.subscribe(Display.new)
t.subscribe(Alarm.new)
t.temperature = 25
t.temperature = 35输出:
display: 25 C
display: 35 C
alarm: too hot!观察者只需要一个 update 方法:鸭子类型。每个观察者都可以是完全不同的类,而且添加新的观察者时无需更改主体:
class Logger2
def update(value)
@history = (@history || []) << value
end
end
thermometer.subscribe(Logger2.new) # 被观察对象没有变化观察者也可以是代码块。主题将它们存储为过程对象,并使用 call 调用它们。unsubscribe 可以再次移除观察者:
class Button
def initialize
@handlers = []
end
def on_click(&handler)
@handlers << handler
handler
end
def unsubscribe(handler)
@handlers.delete(handler)
end
def click
@handlers.map { |h| h.call("clicked") }
end
end
b = Button.new
b.on_click { |e| "A got #{e}" }
second = b.on_click { |e| "B got #{e}" }
p b.click
b.unsubscribe(second)
p b.click输出:
["A got clicked", "B got clicked"]
["A got clicked"]主题决定要发送什么。传递主题本身可以让观察者读取所需内容;传递事件名称可以让一个观察者响应多种变化:
@observers.each { |o| o.update(self) } # 观察者读取 subject.price
@observers.each { |o| o.update(:sold_out, self) } # 观察者检查事件挑战
简单完成主题类 Stock 和两个观察者。Stock.new(symbol) 有 subscribe(observer) 和 update_price(value),后者会存储价格,并按订阅顺序返回每个观察者的 update(stock) 中非 nil 的回复。PriceDisplay#update 返回 AAPL now 12.50。当价格从不高于限价变为高于限价时,PriceAlert.new(limit) 返回 ALERT AAPL above 100.00,否则返回 nil。
第一行输入是代码和提醒限价。提供的代码会订阅一个显示器和一个提醒器,然后打印每行 set price 对应的回复。
你的代码应写在 stock.rb、price_display.rb 和 price_alert.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。
亲手试试
require_relative 'stock'
require_relative 'price_display'
require_relative 'price_alert'
# 提供的输入输出代码:保持原样
input = []
while (line = gets)
input << line.chomp
end
symbol, limit = input[0].split
stock = Stock.new(symbol)
stock.subscribe(PriceDisplay.new)
stock.subscribe(PriceAlert.new(limit.to_f))
input.drop(1).each do |line|
stock.update_price(line.split[1].to_f).each { |reply| puts reply }
end
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Ruby 在线编译器