概要
Rubyによるデザインパターン第5章。
Observer Pattern。
Rubyによるデザインパターン5原則に則って理解する。
どんなパターンか
あるオブジェクトの状態に関心のあるオブジェクトに、都度通知を送る。
ニュースの発信源(Subject)とニュースの消費者(Observer)間に綺麗なインターフェイスを用意する。
Subject
あるニュースを配信するクラス
Observer
あるニュースを得ることに関心があるクラス
メリット
ニュースの発信者と受信者の間の依存関係を排除する。
問題のあるコード
従業員の給与の変化を経理部門に伝えるシステム
従業員クラス
class Employee
attr_reader :salary
attr_accessor :title, :name
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
経理部門クラス
class Payroll
def update(changed_employee) # Subjectオブジェクトを受け取る
puts "#{changed_employee.name}の給料が#{changed_employee.salary}ドルに上がりました!"
end
end
クラスの利用
payroll = Payroll.new
john = Employee.new('john', 'worker', 100, payroll)
john.salary = 200
=> johnの給料が200ドルに上がりました!
問題
もし経理部門以外にも通知したくなったら?
→今はEmployeeクラスに手を入れる必要がある。
本質的にはEmployeeに対する変更など何もないにも関わらず・・。
そこで、
変化する事項(「従業員の給与の変更」というニュースを誰が受け取るか)を、Employeeオブジェクトから分離する。
→必要なのは、Employeeオブジェクトの変化に関心のあるオブジェクトの一覧。
従業員クラス
class Employee
attr_reader :salary
attr_accessor :title, :name
def initialize(name, title, salary)
@name = name
@title = title
@salary = salary
@observers = []
end
def add_observer(observer)
@observers << observer
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
private
def notify_observers
@observers.each do |observer|
observer.update(self)
end
end
end
経理部門クラス
class Payroll
def update(changed_employee) # Subjectオブジェクトを受け取る
puts "#{changed_employee.name}の給料が#{changed_employee.salary}ドルに上がりました!"
puts "経理部門は#{changed_employee.name}に小切手を切ります!"
end
end
税務署員クラス
class Taxman
def update(changed_employee)
puts "#{changed_employee.name}の給料が#{changed_employee.salary}ドルに上がりました!"
puts "税務署員は#{changed_employee.name}に新しい税金請求書を送ります!"
end
end
クラスの利用
[4] pry(main)> john = Employee.new('john', 'worker', 100)
[5] pry(main)> john.add_observer(Payroll.new)
[6] pry(main)> john.add_observer(Taxman.new)
[7] pry(main)> john.salary = 200
johnの給料が200ドルに上がりました!
経理部門はjohnに小切手を切ります!
johnの給料が200ドルに上がりました!
税務署員はjohnに新しい税金請求書を送ります!
なかなか良くなった。
しかし、他のクラスにもサブジェクトの機能を持たせたくなった時、都度同じコードを書く必要がある。
モジュール化(1)
サブジェクトとしての機能をモジュール化し、ポータブルにする。
Subjectモジュール
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
モジュール利用(クラスのSubject化)
class Employee
include Subject
attr_reader :salary
attr_accessor :name, :address
def initialize(name, title, salary)
super() # Subject Moduleのinitializeを行う
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
old_salary = @salary
@salary = new_salary
if old_salary != new_salary # oldとnewが異なっている場合のみ通知
notify_observers
end
end
end
Employeeのinitializeメソッドの中でsuper()を呼び出すことに注意。
ここでSubjectモジュールのinitializeを呼び出している。
モジュール化(2)
実はRubyには、observableというobserver用Moduleが存在する。
モジュール利用(クラスのSubject化)
require 'observer'
class Employee
include Observable
attr_reader :salary
attr_accessor :name, :address
def initialize(name, title, salary)
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
old_salary = @salary
@salary = new_salary
if old_salary != new_salary # oldとnewが異なっている場合のみ通知
changed # changedメソッドの呼び出し
notify_observers
end
end
end
changedメソッドとは
Observableには、オブザーバへ冗長な通知を避けるために、
オブジェクトが本当に変更されたかどうかを示すBooleanフラグが存在する。
changedは、これをtrueに設定する。
notify_observersを呼び出すごとに、変更フラグはfalseへとリセットされる。
ブロックによるオブザーバー
ブロックによるオブザーバーによって簡潔なコードを実現する。
また、Observable(モジュール(2))はブロックをサポートしていないため、
独自実装であるモジュール(1)を拡張して実現してみる。
Subjectモジュール(ブロックをサポート)
module Subject
def initialize
@observers=[]
end
def add_observer(&observer) # blockを受け取り、procへ変換(&)してobserverとして登録
@observers << observer
end
def delete_observer(observer)
@observers.delete(observer)
end
def notify_observers
@observers.each do |observer|
observer.call(self) # observer(Proc)を呼び出す
end
end
end
モジュール利用(クラスのSubject化)
class Employee
include Subject
attr_accessor :salary, :name, :address
def initialize(name, title, salary)
super()
@name = name
@title = title
@salary = salary
end
def salary=(new_salary)
old_salary = @salary
@salary = new_salary
if old_salary != new_salary # oldとnewが異なっている場合のみ通知
notify_observers
end
end
end
呼び出し
[13] pry(main)> john = Employee.new('john', 'worker', 100)
[14] pry(main)> john.add_observer do |changed_employee| # observerブロックの登録
[14] pry(main)* puts("#{changed_employee.name}に新しい小切手を切ります!")
[14] pry(main)* end
[15] pry(main)> john.salary = 200
johnに新しい小切手を切ります!
まとめ
- 変わるもの(オブザーバー)と変わらないもの(サブジェクト)を分離して、変化に強い構造へ
- サブジェクトの機能自体はモジュール化してボータブル化
- ブロックによるオブザーバーの登録も可能
Strategyパターンとの比較
形は似ている
Observerパターン:サブジェクトがオブザーバーを呼び出す
Strategyパターン:コンテキストがストラテジを呼び出す
違いは目的
Observerパターン:サブジェクトで発生したイベントをオブザーバーへ通知する
Strategyパターン:コンテキストが、何か特定の処理を行うためにストラテジを呼び出す
以下へ続く
【Composite】 -世界は再帰的(部分は全体、全体は部分)-
http://qiita.com/kidachi_/items/6cb73b2bbc875e9bef6d