Observerパターンとは、何らかのオブジェクトが変化したというニュースの発信者と受信者の間に綺麗なインタフェースを作るパターンのこと。
ニュースを持っているクラスをサブジェクトクラスと呼び、ニュースの受信者をオブザーバーと呼ぶ。
システムを構築する各部分が全体の状態に注意を払わなくてはならないような、高度に統合されたシステムの構築に関する問題。例えば、誰かの給料が変わった時に経理部門に知らせる必要のあるような人事システムを考えたい。
注目すべきは、発信源としての振る舞いがあること。
たとえば、Fredが昇給したら、彼のEmployeeオブジェクトは世界中に向けて「すみません、私に何かが起こりました!」と周囲に知らせる。
すると、Employeeオブジェクトと関係のあるオブジェクトがすべきことはただひとつ。「事前にEmployeeオブジェクトから通知がもらえるように登録されること」だけ。これにより、それらのオブジェクトはタイムリーに変更の情報を受け取る事ができるようになる。
# -*- encoding: utf-8 -*- # 愚直に給料の変更を経理部門に伝えるためのサンプルコード class Payroll def update(changed_employee) puts "#{changed_employee.name} no tameni kogitte wo kirimasu" puts "his saraly is #{changed_employee.salary} now" end end class Employee attr_reader :name, :title attr_reader :salary def initialize(name, title, salary, payroll) @name = name @title = title @salary = salary @payroll = payroll end def salary=(new_salary) @salary = new_salary @payroll.update(self) end end payroll = Payroll.new fred = Employee.new('Fred', 'Crane Operator', 30000, payroll) fred.salary = 35000
上述のコードは、経理部門への給与の変更通知をハードコーディングしている。
もしも、他のオブジェクトに対して、Fredの財政状態を通知する必要が出てきた場合、どうすればよいのだろうか。
変更の通知を受け取りたい部分は「変化しやすい事柄」であり、変化しない部分と分離しなくてはいけない。
class Payroll def update(changed_employee) puts "#{changed_employee.name} no tameni kogitte wo kirimasu" puts "his saraly is #{changed_employee.salary} now" end end class TaxMan def update(changed_employee) puts "Send new tex to #{changed_employee.name}" end end class Employee attr_reader :name, :title attr_reader :salary def initialize(name, title, salary) @name = name @title = title @salary = salary @observers = [] end def salary=(new_salary) @salary = new_salary #@payroll.update(self) notify_observers end def notify_observers @observers.each do |observer| observer.update(self) end end def add_observer(observer) @observers << observer end def delete_observer(observer) @observer.delete(observer) end end fred = Employee.new('Fred', 'Crane Operator', 30000.0) payroll = Payroll.new fred.add_observer(payroll) taxman = TaxMan.new fred.add_observer(taxman) fred.salary = 300000
rubyにおけるObseverパターンで必要になるのは、上述の例とほとんど変わらない。必要になるものは以下。
・オブザーバを保持するための配列
・配列を管理するための2つのメソッド(要素の追加、削除)
・変更が発生したときの通知用メソッド
オブザーバを監視するためのメソッドを分離して、継承するパターンを考えたい。
class Subject def initialize @observers = [] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end class Employee < Subject # ..... end
継承による問題点は、その時点のスーパークラス以外のクラスを基底クラスにする可能性を断ち切ってしまうこと
仮に、ドメインモデル上、すでに継承済みのクラスがあった場合、詰んでしまう。
この問題の解決方法がモジュール。
モジュールとは「クラス間で共有可能なメソッドと定数のパッケージ」。1つしか割り当てられないスーパークラスの場を奪うことはない。
module Subject def initialize @observers = [] end def add_observer(observer) @observers << observer end def delete_observer(observer) @observers.delete(observer) end def notify_observers @observers.each do |observer| observer.update(self) end end end class Employee include Subject attr_reader :name, :address attr_reader :salary def initialize(name, title, salary) super() @name = name @title = title @salary = salary end def salary=(new_salary) @salary = new_salary notify_observers end end fred = Employee.new('Fred', 'Crane Operator', 30000.0) payroll = Payroll.new fred.add_observer(payroll) taxman = TaxMan.new fred.add_observer(taxman) fred.salary = 300000
※ super() → 読み込んだモジュールのinitializeを呼び出す効果を持つ