模块 Observable

观察者模式(也称为发布/订阅模式)提供了一种简单的机制,当一个对象的状态发生变化时,它会通知一组感兴趣的第三方对象。

机制

通知类混入 Observable 模块,该模块提供了管理相关观察者对象的方法。

可观察对象必须

  • 断言它已 #changed

  • 调用 #notify_observers

观察者使用 Observable#add_observer 订阅更新,该方法还指定了通过 notify_observers 调用的方法。 notify_observers 的默认方法是 update。

示例

下面的示例很好地演示了这一点。一个 Ticker(行情跳动器)在运行时,会不断接收其 @symbol 的股票 Price(价格)。一个 Warner(警告器)是价格的通用观察者,并演示了两个警告器,一个 WarnLow(低价警告器)和一个 WarnHigh(高价警告器),如果价格分别低于或高于其设定的限制,则会打印警告。

update 回调允许警告器在不被显式调用的情况下运行。系统设置了 Ticker 和几个观察者,并且观察者无需顶层代码干预即可履行其职责。

请注意,发布者和订阅者(可观察对象和观察者)之间的契约没有声明或强制执行。 Ticker 发布时间和价格,警告器接收到这些信息。但是,如果您不确保您的契约正确,则没有其他方法可以警告您。

require "observer"

class Ticker          ### Periodically fetch a stock price.
  include Observable

  def initialize(symbol)
    @symbol = symbol
  end

  def run
    last_price = nil
    loop do
      price = Price.fetch(@symbol)
      print "Current price: #{price}\n"
      if price != last_price
        changed                 # notify observers
        last_price = price
        notify_observers(Time.now, price)
      end
      sleep 1
    end
  end
end

class Price           ### A mock class to fetch a stock price (60 - 140).
  def self.fetch(symbol)
    60 + rand(80)
  end
end

class Warner          ### An abstract observer of Ticker objects.
  def initialize(ticker, limit)
    @limit = limit
    ticker.add_observer(self)
  end
end

class WarnLow < Warner
  def update(time, price)       # callback for observer
    if price < @limit
      print "--- #{time.to_s}: Price below #@limit: #{price}\n"
    end
  end
end

class WarnHigh < Warner
  def update(time, price)       # callback for observer
    if price > @limit
      print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
    end
  end
end

ticker = Ticker.new("MSFT")
WarnLow.new(ticker, 80)
WarnHigh.new(ticker, 120)
ticker.run

产生

Current price: 83
Current price: 75
--- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
Current price: 90
Current price: 134
+++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
Current price: 134
Current price: 112
Current price: 79
--- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79

与 Proc 的用法

#notify_observers 方法还可以通过使用 :call 作为 func 参数来与 +proc+ 一起使用。

以下示例说明了 lambda 的使用

require 'observer'

class Ticker
  include Observable

  def run
    # logic to retrieve the price (here 77.0)
    changed
    notify_observers(77.0)
  end
end

ticker = Ticker.new
warner = ->(price) { puts "New price received: #{price}" }
ticker.add_observer(warner, :call)
ticker.run

常量

VERSION

公共实例方法

add_observer(observer, func=:update) 点击切换源代码

observer 添加为此对象的观察者。以便它将接收通知。

observer

将接收更改通知的对象。

func

符号,命名当此 Observable 发生更改时将被调用的方法。

此方法必须为 observer.respond_to? 返回 true,并且当调用 notify_observers 时将接收 *arg,其中 *arg 是此 Observable 传递给 notify_observers 的值。

# File observer-0.1.2/lib/observer.rb, line 153
def add_observer(observer, func=:update)
  @observer_peers = {} unless defined? @observer_peers
  unless observer.respond_to? func
    raise NoMethodError, "observer does not respond to `#{func}'"
  end
  @observer_peers[observer] = func
end
changed(state=true) 点击切换源代码

设置此对象的更改状态。仅当更改的 statetrue 时,才会发送通知。

state

布尔值,指示此 Observable 的更改状态。

# File observer-0.1.2/lib/observer.rb, line 194
def changed(state=true)
  @observer_state = state
end
changed?() 点击切换源代码

如果自上次 notify_observers 调用以来此对象的状态已更改,则返回 true。

# File observer-0.1.2/lib/observer.rb, line 202
def changed?
  if defined? @observer_state and @observer_state
    true
  else
    false
  end
end
count_observers() 点击切换源代码

返回与此对象关联的观察者的数量。

# File observer-0.1.2/lib/observer.rb, line 180
def count_observers
  if defined? @observer_peers
    @observer_peers.size
  else
    0
  end
end
delete_observer(observer) 点击切换源代码

从此对象中删除 observer 作为观察者,以便它不再接收通知。

observer

Observable 的观察者

# File observer-0.1.2/lib/observer.rb, line 166
def delete_observer(observer)
  @observer_peers.delete observer if defined? @observer_peers
end
delete_observers() 点击切换源代码

删除与此对象关联的所有观察者。

# File observer-0.1.2/lib/observer.rb, line 173
def delete_observers
  @observer_peers.clear if defined? @observer_peers
end
notify_observers(*arg) 点击切换源代码

如果此对象的更改状态为 true,则通知观察者状态发生更改。

这将调用 add_observer 中命名的方法,并传递 *arg。然后将更改状态设置为 false

*arg

传递给观察者的任何参数。

# File observer-0.1.2/lib/observer.rb, line 218
def notify_observers(*arg)
  if defined? @observer_state and @observer_state
    if defined? @observer_peers
      @observer_peers.each do |k, v|
        k.__send__(v, *arg)
      end
    end
    @observer_state = false
  end
end