module Observable

觀察者模式(也稱為發佈/訂閱)提供了一個簡單的機制,讓一個物件在狀態改變時通知一組有興趣的第三方物件。

機制

發出通知的類別會混入 Observable 模組,此模組提供管理關聯觀察者物件的方法。

可觀察物件必須

觀察者使用 Observable#add_observer 訂閱更新,此方法也會指定透過 notify_observers 呼叫的方法。notify_observers 的預設方法是 update。

範例

以下範例清楚地示範了這一點。當 Ticker 執行時,會持續接收其 @symbol 的股票 PriceWarner 是價格的常駐觀察者,並示範了兩個 warner,分別是 WarnLowWarnHigh,如果價格低於或高於其設定的限制,它們會印出警告。

update 回呼函數允許 warner 在未被明確呼叫的情況下執行。系統會設定 Ticker 和多個觀察者,而觀察者會執行其職責,而頂層程式碼無需干預。

請注意,發布者和訂閱者(可觀察物件和觀察者)之間的合約並未宣告或強制執行。Ticker 發布時間和價格,而 warner 會接收這些資料。但如果您不確保合約正確,沒有其他東西可以警告您。

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

與程序搭配使用

#notify_observers 方法也可以與 +proc+ 一起使用,方法是將 :call 用作 func 參數。

以下範例說明了 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 新增為此物件上的觀察者。這樣它才能接收通知。

觀察者

將收到變更通知的物件。

函式

符號 命名當此 可觀察 發生變更時將呼叫的方法。

此方法必須傳回 true 以供 observer.respond_to? 使用,且當呼叫 notify_observers 時將收到 *arg,其中 *arg 是此 可觀察 傳遞給 notify_observers 的值

# File 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
已變更(狀態=true) 按一下以切換來源

設定 此物件的已變更狀態。僅當已變更的 狀態true 時才會傳送通知。

狀態

布林值,表示此 可觀察 的已變更狀態。

# File lib/observer.rb, line 194
def changed(state=true)
  @observer_state = state
end
已變更?() 按一下以切換來源

如果此物件的狀態自上次 notify_observers 呼叫後已變更,則傳回 true。

# File lib/observer.rb, line 202
def changed?
  if defined? @observer_state and @observer_state
    true
  else
    false
  end
end
觀察者數量() 按一下以切換來源

傳回與此物件關聯的觀察者數量。

# File lib/observer.rb, line 180
def count_observers
  if defined? @observer_peers
    @observer_peers.size
  else
    0
  end
end
刪除觀察者(觀察者) 按一下以切換來源

移除 觀察者 作為此物件的觀察者,使其不再收到通知。

觀察者

可觀察 的觀察者

# File lib/observer.rb, line 166
def delete_observer(observer)
  @observer_peers.delete observer if defined? @observer_peers
end
刪除觀察者() 按一下以切換來源

移除與此物件關聯的所有觀察者。

# File lib/observer.rb, line 173
def delete_observers
  @observer_peers.clear if defined? @observer_peers
end
通知觀察者(*arg) 按一下以切換來源

如果 此物件的已變更狀態為 true,則通知觀察者狀態已變更。

這會呼叫 add_observer 中指定的函式,傳遞 *arg。然後,已變更狀態會設定為 false

*arg

傳遞給觀察者的任何引數。

# File 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