プログラムの設計力向上のため『Rubyによるデザインパターン』を読んでおり、気になるデザインパターンを、1つずつまとめています。
今回は、オブザーバーパターンについてまとめました。
デザインパターン記事一覧
【Rubyによるデザインパターン】テンプレートメソッドパターンのメモ - Qiita
【Rubyによるデザインパターン】ファクトリーメソッドパターンのメモ - Qiita
【Rubyによるデザインパターン】ストラテジーパターンのメモ - Qiita
【Rubyによるデザインパターン】コマンドパターンのメモ - Qiita
【Rubyによるデザインパターン】オブザーバーパターンのメモ - Qiita <- 本記事
【Rubyによるデザインパターン】シングルトンパターンのメモ - Qiita
オブザーバーパターンについて
何らかのオブジェクトの状態が変化したとき(イベントが発生した時)に、依存関係にあるすべてのオブジェクトに自動的にその変化をその変化を通知するパターンです。
状態が変化するオブジェクトを持つクラスを被監視者(Subject)、状態変化を検知するクラスを 監視者(Observer) として定義します。
サンプルコード
2020年11月23日更新
(コメント欄でサンプルコードがオブザーバーパターンとは違うのではないか?というありがたいご指摘をいただき、全面的に見直しました。ご指摘感謝します!)
どのようなWebサイトでも「会員」がいるケースが多いと思います。今回はこの会員の「会員ステータス」の更新に際して、メール通知やログ出力することを想定します。
とはいえ、実際ににメール送信処理を実装するのは大変なので、 puts メソッドで代用しています。
ちなみに、会員ステータスは初期値の bronze から silver, gold と変遷する想定です。
まずはオブザーバーパターン適用前からです。
class User
attr_reader :name
attr_accessor :status
def initialize(name)
@name = name
@status = 'bronze'
end
end
class UserLogger
def update(user)
puts "LOG INFO: #{Time.now}-- #{user.name}の会員ステータスを #{user.status} に更新"
end
end
class UserMailer
def update(user)
puts "メール送信処理(#{user.name}の会員ステータスを「#{user.status}」を更新しました。"
end
end
実行時はこのとおりです。
user = User.new('ユーザー')
user_logger = UserLogger.new
user_mailer = UserMailer.new
user.status = 'silver'
user_logger.update(user)
user.status = 'gold'
user_logger.update(user)
user_mailer.update(user)
# LOG INFO: 2020-11-23 14:56:07 +0900-- ユーザーの会員ステータスを silver に更新
# LOG INFO: 2020-11-23 14:56:07 +0900-- ユーザーの会員ステータスを gold に更新
# メール送信処理(ユーザーの会員ステータスを「gold」を更新しました。
会員ステータス更新処理時には、更新後に user_logger や user_mailer の updateメソッドを呼び出しています。
ログ出力やメール送信を担うオブジェクト以外にも更新を通知するような場合に、呼び出し時の処理が複雑になってしまうことが問題となります。
次は、オブザーバーパターンを適用して書き換えた場合です。
class User
attr_reader :name, :status
def initialize(name)
@name = name
@status = 'bronze'
@observers = []
end
def status=(status)
@status = status
notify_observers
end
def notify_observers
@observers.each {|observer| observer.update(self) }
end
def add_observer(*observers)
@observers.concat(observers)
end
def delete_observer(observer)
@observers.delete(observer)
end
end
class UserLogger
def update(user)
puts "LOG INFO: #{Time.now}-- #{user.name}の会員ステータスを #{user.status} に更新"
end
end
class UserMailer
def update(user)
puts "メール送信処理(#{user.name}の会員ステータスを「#{user.status}」を更新しました。"
end
end
実行結果はこのとおりです。
user = User.new('ユーザー')
user_logger = UserLogger.new
user_mailer = UserMailer.new
user.add_observer(user_logger)
user.status = 'silver'
user.add_observer(user_mailer)
user.status = 'gold'
# LOG INFO: 2020-11-23 14:56:07 +0900-- ユーザーの会員ステータスを silver に更新
# LOG INFO: 2020-11-23 14:56:07 +0900-- ユーザーの会員ステータスを gold に更新
# メール送信処理(ユーザーの会員ステータスを「gold」を更新しました。
add_observer メソッドによって user に user_logger と user_mailer を監視者として追加しておくことで、会員ステータスの更新のたびに、監視者に対して update を呼び出す必要がなくなりました。
さらに、 今回は使用していませんが delete_observer メソッドによって監視者の削除が容易となっています。
オブザーバーパターンの適用例
Active Record は after_create や after_update 便利なメソッドを備えています。(オブジェクトのライフサイクルにフックを掛ける仕組みをコールバックと読んでいますね。)
このようにRails には オブザーバーパターンを反映した設計がいくつもあることがわかります。
おわりに
オブザーバーパターンはイベント処理を分離するというパターンです。もともとRails でも取り入れられていることを知り、Railsの有用性を再認識しました。
普段何気なく使っているフレームワークの設計意図を知れるのも、デザインパターンを学ぶ意義の1つですね。