http://ruby-doc.org/stdlib-2.6.1/libdoc/observer/rdoc/Observable.html

Observable 观察者

观察者模式也称作发布/订阅模式, 它提供一种简单的机制, 在它状态发生变化时来通知一组第三方资源.

机制

被观察者的类引入 Observable 模块, 该模块提供了管理相关观察者对象的方法.

要求可观察的对象:

  • 当变化发生后, 使用 #changed 方法来声明变化已发生
  • 调用 #notify_observers 方法传递通知的参数

观察者使用 add_observer 来订阅更新. 更新发生时, 被观察者使用 notify_observers 来触发通知, 观察者默认使用 update 来接受更新参数.

实例

以下示例很好地演示了这一点. 一个 Ticker 运行之后, 不停地周期性拉取 “APPL” 的股价. Warner 是一个股价的通用观察者, 当股价低于预设价, WarnLow 会打印警报, 当股价高于预设价, WarnHigh 会打印警报.

需要注意的是, 发布者与订阅者(被观察者和观察者)之间的合约不是显示的, 也不是强制执行的. Ticker 发布时间和价格, warners 接受到这个通知. 如果你不确定合约的正确性, 再没其他的警告通知了.

require 'observer'

# 模拟 API Client
class PriceAPI
  def self.fetch(stock_code:)
    sleep 0.010
    60 + rand(80)
  end
end

# 定期更新股价, 被观察者
class Ticker
  include Observable

  def initialize(stock_code:)
    @stock_code = stock_code
  end

  def run
    # 股价初始化
    last_price = nil

    loop do
      current_price = PriceAPI.fetch(stock_code: @stock_code)
      print "Current price: #{current_price}\n"
      if current_price != last_price
        changed and notify_observers(time: Time.now, price: current_price)
      end
      last_price = current_price

      sleep 0.5
    end
  end
end

# 对 Ticker 的观察者的抽象类
class Warner
  def initialize(observed_object:, price_limit:)
    @price_limit = price_limit
    observed_object.add_observer(self)
  end
end

# 低股价警报
class WarnerLow < Warner
  def update(time:, price:)
    if price < @price_limit
      print "--- #{time.to_s}: Price below #@limit: #{price}\n"
    end
  end
end

# 高股价警报
class WarnHigh < Warner
  def update(time:, price:)
    if price > @price_limit
      print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
    end
  end
end

ticker = Ticker.new(stock_code: 'APPL')
WarnerLow.new(observed_object: ticker, price_limit: 80)
WarnHigh.new(observed_object: ticker, price_limit: 120)
ticker.run

Output Demo

Current price: 72
--- 2019-03-04 15:08:23 +0800: Price below : 72
Current price: 101
Current price: 117
Current price: 72
--- 2019-03-04 15:08:25 +0800: Price below : 72
Current price: 88
Current price: 97
Current price: 83