Observerパターン
サンプル①
コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| class Payroll
def update(changed_employee)
puts "#{changed_employee.name}のために小切手を切ります!"
puts "彼の給料はいま#{changed_employee.salary}です!"
end
end
class Employee
attr_reader :name
attr_accessor :salary
def initialize(name, salary, payroll)
@name = name
@salary = salary
@payroll = payroll
end
def salary=(new_salary)
@salary = new_salary
@payroll.update(self)
end
end
|
結果
1
2
3
4
5
6
7
| payroll = Payroll.new
jo = Employee.new('Jo', 30000, payroll)
jo.salary = 35000
# =>
Joのために小切手を切ります!
彼の給料はいま35000です!
|
これはPayroll
のupdate
だけ指定されていて汎用的ではない。
サンプル②
コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| class Payroll
def update(changed_employee)
puts "#{changed_employee.name}のために小切手を切ります!"
puts "彼の給料はいま#{changed_employee.salary}です!"
end
end
class Employee
attr_reader :name
attr_accessor :salary
def initialize(name, salary)
@name = name
@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
|
結果
1
2
3
4
5
6
7
8
9
10
| jo = Employee.new('Jo', 30000)
payroll = Payroll.new
jo.add_observer(payroll)
jo.salary=35000
# =>
Joのために小切手を切ります!
彼の給料はいま35000です!
|
このコードに新しいクラスを出してそのクラスも適用してみると、
1
2
3
4
5
| class TaxMan
def update(changed_employee)
puts "#{changed_employee.name}に新しい税金の請求書を送ります!"
end
end
|
1
2
3
4
5
6
7
8
9
| tax_man = TaxMan.new
jo.add_observer(tax_man)
jo.salary = 90000
# =>
Joのために小切手を切ります!
彼の給料はいま90000です!
Joに新しい税金の請求書を送ります!
|
サンプル③(モジュール化)
コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| 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, :salary
def initialize(name, salary)
super()
@name = name
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
|
結果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| jo = Employee.new('Jo', 30000)
payroll = Payroll.new
tax_man = TaxMan.new
jo.add_observer(payroll)
jo.add_observer(tax_man)
jo.salary = 90000
# =>
Joのために小切手を切ります!
彼の給料はいま90000です!
Joに新しい税金の請求書を送ります!
|
サンプル④(コードブロック)
RubyでObservable
というライブラリを提供してくれるけど、Observable
はコードブロックをサポートしてくれない。
次のコードはコードブロックに対する対応になる。
コード
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| 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
class Employee
include Subject
attr_reader :name, :salary
def initialize(name, salary)
super()
@name = name
@salary = salary
end
def salary=(new_salary)
@salary = new_salary
notify_observers
end
end
|
結果
1
2
3
4
5
6
7
8
9
10
11
12
| jo = Employee.new('Jo', 30000)
jo.add_observer do |changed_employee|
puts "#{changed_employee.name}のために小切手を切ります!"
puts "彼の給料はいま#{changed_employee.salary}です!"
end
jo.salary = 90000
# =>
Joのために小切手を切ります!
彼の給料はいま90000です!
|
RailsのActiveRecord::Observer
RailsではActiveRecord::Observer
を提供している。
rails-observers
というGem
を通して使用できる。
インストール
or
1
| $ gem install rails-observers
|
使い方
name
カラムを持っているUser
というモデルがあるとしてこのUser
モデルに対するObserver
を作成してみる。
1
| $ rails g observer user
|
すると、app/models/user_observer.rb
というルートで生成される。
そのファイルにクラスを定義する。
1
2
3
4
5
| class UserObserver < ActiveRecord::Observer
def after_save(user)
puts "#{user.name}が保存されました。"
end
end
|
あと、application.rb
に設定する。
1
| config.active_record.observers = :user_observer
|
observer
が複数の場合、
1
| config.active_record.observers = [:user_observer, :team_observer]
|
のようにもできる。
結果
1
2
3
4
5
| user = User.new(name: 'Jo')
user.save
# =>
Joが保存されました。
|
その他
1
2
3
4
5
6
7
| class NotificationsObserver < ActiveRecord::Observer
observe :comment, :like
def after_create(record)
# notifiy users of new comment or like
end
end
|
observe
を指定すると指定されたモデルのみobserve
することもできる。
終わり
RubyのObserverパターンとActiveRecord::Observer
を調べながら
Railsで提供しているライブラリActiveRecord::Callbacks
と少し似ていると思った。
参考
本 - Rubyによるデザインパターン
https://github.com/rails/rails-observers