1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rubyによるデザインパターン】オブザーバーパターンのメモ

Last updated at Posted at 2020-11-22

プログラムの設計力向上のため『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つですね。

1
1
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?