Observer
Rubyによるデザインパターン生活3日目。in 市ヶ谷。
今日はObserverパターンを取り上げます。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 12人 クリック: 193回
- この商品を含むブログ (58件) を見る
Observerってのは「観測者」っていう意味が一番しっくりくるかな。
あるオブジェクトの状態変化を観測し、
変化が発生したことを知らせるのが目的です。
著者のラス・オルセンさんも言ってるけど、
Observerとなるクラスが観測者にあたるんだけど、
実際に観測者に変化を通知するのは、観測対象となるオブジェクト(Observable)なります。
#その辺でObserverパターンのネーミングは若干微妙な気もします。
そんな話はさておき、今回登場するクラスは以下のとおりです。
- Employee
- 従業員クラス。名前、役職、給料といった状態をもつ。状態が変化したら観測者(Observer)へ通知する。
- Payroll
- 給与明細クラス。従業員の給料の変化などを観測するObserverクラス。
- TaxMan
- 税金クラス。従業員の給料の変化などを観測するObserverクラス。
つづいてソースコードです。
まず、Employeeクラス。
class Employee attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) @name = name @title = title @salary = salary @observers = [] end def salary=(new_salary) @salary = new_salary 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) @observers.delete(observer) end end
インスタンス変数@observersがポイント。
ここに観測者(Observer)となるオブジェクトを保持します。
@observersへの追加にはadd_observerメソッドを使います。
状態であるsalaryが変化した場合、notify_observersメソッドが呼び出されるので、
結果として観測者クラス(Observer)のupdateメソッドが呼び出されます。
つづいて、観測者(Observer)となるPayrollクラスです。
class Payroll def update(changed_employee) puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end end
変更通知用のupdateメソッドを持っています。
引数には観測対象であるEmployeeオブジェクトを受け取ります。
つづいてTaxManクラス。こちらもObserverクラスになります。
class TaxMan def update(changed_employee) puts "#{changed_employee.name}に新しい税金の請求書を送ります。" end end
Payrollクラスと同じくupdateメソッドを実装しています。
実行用のプログラムです。
fred = Employee.new("Fred", "Crane Operator", 30000.0) payroll = Payroll.new fred.add_observer(payroll) taxman = TaxMan.new fred.add_observer(taxman) fred.salary = 35000.0
そんで、実行結果です。
Fredのために小切手を切ります。 給料は35000.0です。 Fredに新しい税金の請求書を送ります。
以上が一般的なObserverパターンの流れです。
Payroll、TaxMan以外の観測者(Observer)クラスが追加された場合も、同様に処理できます。
ここまでのコードでまずいところは、
Employeeクラスの中に観測対象者(Observable)として状態や振る舞いが実装されてしまっているところ。
Employeeクラスが読みにくいものになっています。
なんで、コードを分離してあげます。
観測対象者(Observable)に該当するコードを抽出します。
今回はSubject(観測対象)という意味あいのクラスとして抽出します。
が、Subjectクラスをスーパークラスとして抽出してしまうと、
継承関係の制約(単一継承しかできない)を受けるためよろしくありません。
なんで、Rubyに用意されているモジュール機能を使います。
Rubyは複数のモジュールをインクルード可能です。
モジュールはJavaのインタフェースに近いような?気がします。
#実装つきのインタフェースってかんじ?
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
Employeeクラス。Subjectモジュールをインクルードしています。
class Employee include Subject attr_reader :name attr_accessor :title, :salary def initialize(name, title, salary) super() @name = name; @title = title @salary = salary end def salary=(new_saraly) @salary = new_saraly notify_observers end end
以上がObserverパターンのRubyっぽいモジュールを使った方法になります。
ちなみにRubyのライブラリの中にもObservableモジュールが用意されています。
話が少し変わりますが、
RubyでObserverパターンを実現する場合、
コードブロックをObserverクラスに見立てて処理することもできます。
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.call(self) end end end
add_observer(&observer)メソッドの引数がコードブロックの参照になっているのと、
notify_observersメソッドの中でObserverとなるコードブロックを実行しています。
呼び出しもとのプログラムはこんなかんじになります。
require "employee" fred = Employee.new("Fred", "Crane Operator", 30000.0) fred.add_observer do |changed_employee| puts "#{changed_employee.name}のために小切手を切ります。" puts "給料は#{changed_employee.salary}です。" end fred.salary = 35000.0
fred.add_observerの引数にコードブロックを渡しています。
これがObserverとして機能します。
実行結果はさきほどと同じ。
Fredのために小切手を切ります。 給料は35000.0です。
以上で、Observerパターンおしまい。
ちなみに、コードを読んで、ブログにまとめると大体2時間くらいかかります。
それが勉強になっていると信じてあと10コくらいのパターンの紹介を続けます。
あと、クラス図を簡単に書けるツール(Webで使える)あればいいなー。と。
おしまい。