Ruby2.5.1のObservableモジュールを写経した
場所
~/.rbenv/versions/2.5.1/lib/ruby/2.5.0/observer.rb
大きな流れ
①Subject(変更する側)からadd_observerを呼び出してConcreateObserver(変更通知を受け取る側)のObjectとインターフェイス(デフォルトは:update)を登録する ②changed(true)を呼び出し変更フラグを立てる。これがtrueでなければnotify_observersは動かない(return falseされる) ③notify_observersを呼び出し、Observerの:updateメソッドが呼ばれる
呼び出し側のコード
require 'observer' class Employee include Observable attr_reader :name, :title, :salary def initialize(name, title, salary) @name = name @title = title @salary = salary #①add_observerメソッド → Employeeの変更を通知したいObserverObjectを注入する add_observer(Payroll.new) add_observer(TaxMan.new) end def salary=(new_salary) @salary = new_salary #changed(state=true)を書くと、notify_observersが呼ばれる。 # つまり明示的に通知するかどうかを示せる changed(true) # ②observer_stateをtrueに変更する(これがfalseだとnotify_observerを呼んでも通知されない) notify_observers(self) # ③登録したObserverObjectに通知をする end end class Payroll def update(changed_employee) puts "給料あがった#{changed_employee.salary}" end end class TaxMan def update(changed_employee) puts "#{changed_employee.name} 請求書" end end john = Employee.new('John', 'Senior Vice President', 5000) john.salary = 6000
写経したObservableのコード
module Observable 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 #①{ ObserverObject, :update } を配列にセットする end #.... 省略(delete_observer, count_observersなど補助的メソッド) def changed(state=true) #②変更がなされたかどうかのステータス @observer_state = state end def changed? if defined? @observer_state and @observer_state true else false end end 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 end
感想
実装がとてもシンプル。初中級社のソースコードリーディングの練習に良いかも。 2.5.1のdefined便利。(nilガードとかではなく、変数やメソッドが定義されるかどうかを判別できる)