コードの品質向上のため、Rubyでデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第3弾です。(毎週1つが目標です!)
前回の記事(ストラテジーパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com
この本で紹介されているサンプルコードをそのまま使うのは面白くないので、オリジナルのコードで説明しています。
今回はObserverパターンについてまとめました。
Observerパターンとは
あるオブジェクトの状態が変化した時に、そのオブジェクトが変化したことを知る必要があるオブジェクトに通知をするデザインパターンです。
Rubyによるデザインパターン では次のように説明されていました。
GoFは「何らかのオブジェクトが変化した」というニュースの発信者と消費者の間に綺麗なインターフェイスを作るアイデアを、Observerパターンと呼んでいます。
(GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)
自らが変化したことを通知するオブジェクトのことを「Subject」(話題になっている事柄)と呼び、Subjectの通知を受け取るオブジェクトのことを「Observer」(観察者)と呼ぶことから、Observerパターンと名付けられました。
言葉での説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。
サンプルコード
商品価格が変更された時に、様々な処理を行う必要があるプログラムを考えてみます。(ECサイトなどで実際によくあるケースだと思います)
まずは、商品価格が変わった時に、ユーザへの通知のみを行うプログラムを、Observerパターンを使わずに実装してみます。
class Item attr_reader :name, :price, :notification def initialize(name, price, notification) @name = name @price = price @notification = notification end def price=(new_price) @price = new_price notification.update(self) # 通知の処理は、Notificationクラスに委譲している end end class Notification def update(changed_item) puts "#{changed_item.name}の値段が#{changed_item.price}円になったよ!" end end
商品価格が変わった時に呼ばれる price= メソッドの中で、Notificationクラスのupdateメソッドに通知処理を委譲しています。
次のように実行します。
Itemクラスのインスタンスを作る際に、Notificationクラスのインスタンスを渡す必要があります。
item = Item.new('ダンベル', 3000, Notification.new) item.price = 2500
実行結果は、次のようになります。
ダンベルの値段が2500円になったよ!
ここで、価格変更をした時に、ユーザへの通知のみではなく、配送方法を変える処理も必要になった場合を考えてみましょう。
先ほどのコードに追加で、Itemクラスのインスタンス作成時に配送方法を変える処理を行うクラスのインスタンスを渡し、price= メソッド内で新しく作ったクラスに配送方法変更の処理を委譲することで実現できると思います。
しかしこのやり方だと、ItemクラスはNotificationクラスや新しく追加したクラスのことを知る必要があり(それぞれのクラスのどのメソッドを呼び出す必要があるかなど)、価格変更した後に行う処理を追加する度に、Itemクラスを修正する必要が出てきてしまいます。
商品の価格変更をした後に行うというだけで、直接商品とは関係ない処理を追加するにも関わらず Itemクラスの修正が必要になってしまうのは保守性の観点で良くないと言えるでしょう。(商品の価格変更した後の処理を追加する際に、Itemクラスを壊してしまうおそれがあります)
このコードを、Observerパターンを使って書き換えてみます。
class Item attr_reader :name, :price, :observers def initialize(name, price) @name = name @price = price @observers = [] end def price=(new_price) @price = new_price notify_observers end def notify_observers observers.each do |observer| observer.update(self) # Observerそれぞれに処理を委譲している end end def add_observer(observer) observers << observer end end class Notification def update(changed_item) puts "#{changed_item.name}の値段が#{changed_item.price}円になったよ!" end end class DeliveryMethod def update(changed_item) puts "#{changed_item.name}の値段によって配送方法を分ける処理を書くよ" end end
Itemクラスが自らに変化があったことを通知するSubjectクラスで、Notificationクラス・DeliveryMethodクラスが通知を受け取るObserverクラスになっています。
Itemクラスに @observers というインスタンス変数を作り、その中に価格変更後に通知を受け取るObserverクラスのインスタンスを格納することで、Itemクラスは @observers に価格変更があったことを通知することだけを知っていればよく、Notification, DeliveryMethod クラスについて知る必要がなくなりました。
次のように実行します。
add_observer メソッドで、observer を追加しています。
item = Item.new('ダンベル', 3000) item.add_observer(Notification.new) item.add_observer(DeliveryMethod.new) item.price = 2500
実行結果は、次のようになります。
ダンベルの値段が2500円になったよ! ダンベルの値段によって配送方法を分ける処理を書くよ
価格変更時に新たな処理を加えたい場合は、observerを追加するだけでよく、Itemクラスを修正する必要がなくなりました。
また、Rubyの標準ライブラリには、Observerパターンを実装するためのObservableモジュールがあり、このモジュールをSubjectクラスにincludeすることで、Observerパターンをシンプルに実装することができます。
先ほどのコードを、Observableモジュールを使って書き換えると次のようになります。
require 'observer' class Item include Observable attr_reader :name, :price, :observers def initialize(name, price) @name = name @price = price end def price=(new_price) @price = new_price changed # changedメソッドを呼ぶことで、オブジェクトに変更があることを伝えている notify_observers(self) end end class Notification def update(changed_item) puts "#{changed_item.name}の値段が#{changed_item.price}になったよ!" end end class DeliveryMethod def update(changed_item) puts "#{changed_item.name}の値段によって配送方法を分ける処理を書くよ" end end
先ほどと同じ呼び出し方で、同じ実行結果になります。
おわりに
ここまで読んでいただきありがとうございます。
前回書いたStrategyパターンの記事を読んでいただいた方は気づいたかもしれませんが、ObserverパターンはStrategyパターンと少し似ている気がするなぁと思っていたら、Rubyにおけるデザインパターンに次の記載がありました。
ObserverパターンとStrategyパターンは少し似ています。どちらも、あるオブジェクトが、他のオブジェクトを呼び出すという特徴があります。ほとんどの場合、違いは目的という一点だけです。Observerパターンの場合、発信側のオブジェクトで発生しているイベントを他のオブジェクトに伝えています。Strategyパターンの場合、何かの処理を行うためにオブジェクトを取得します。
TemplateメソッドパターンとStrategyパターンも同じ目的のために、違う手段で実装していましたが、デザインパターン同士で似ているところなどの関連があって面白いですね。
違いを認識しておくことで、それぞれのデザインパターンへの理解がより深まりそうです。
Rubyによるデザインパターン の中では、従業員の給与が変わった時に、経理部門や税務署員に通知するというサンプルコードを用いて説明がされていて非常に分かりやすかったので、ご興味ある方は是非合わせてご覧ください。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 13人 クリック: 220回
- この商品を含むブログ (66件) を見る
次回は、Compositeパターンをまとめます。どんなパターンか楽しみです。
来週も頑張ります!
(追記)
Compositeパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com