Observerパターン
1. 目的
状態の変化時、関連するオブジェクトに状態変更を通知する機構が欲しい
- 状態変化が発生した際に関連する複数のオブジェクトに一斉に処理を行うよう通知を飛ばすような仕組みが必要な時がある
- GUIコンポーネントの状態変化の監視やセンサーやログの監視などが考えられる
2. 課題
機構として提供したいので通知対象のオブジェクトは任意に追加・削除したい
- 状態に対応した処理は多様であることが予想され、処理を行うオブジェクトは状況によって差し替え可能にしたい(プラガブルにしたい)
- その為、関連するオブジェクトは任意に追加したり削除したり仕組みが必要である
3. 解決策
通知を受けて処理を行うオブジェクトを定義する
class Subscriber:
def __init__(self, msg):
self.__msg = msg
def update(self):
print(self.__msg)
- 通知を受け取るオブジェクトとして
Subscriber
を定義
-
update()
メソッドが通知を受けた際に実行されるメソッド
通知を受けるオブジェクトを集約するオブジェクトを定義する
class Publisher:
def __init__(self):
self.__subscribers = []
def register(self, subscriber):
self.__subscribers.append(subscriber)
def unregister(self, subscriber):
self.__subscribers.remove(subscriber)
def notify(self):
for subscriber in self.__subscribers:
subscriber.update()
-
Subscriber
を集約するオブジェクトとしてPublisher
を定義
- 内部的に
Subscriber
を保存するリストを持ったオブジェクトで、register()
/unregister()
でSubscriber
の登録・削除を行う
- 通知を行う際は
notify()
メソッドが呼ばれ、Publisher
が保有するSubscriber
のupdate()
メソッドを呼び出す
使い方
pub = Publisher()
sub1 = Subscriber("わーい")
sub2 = Subscriber("うぇーい")
pub.register(sub1)
pub.register(sub2)
pub.notify() # => わーい うぇーい
pub.unregister(sub2)
pub.notify() # => わーい
4. メリット
通知を受けて処理を行うオブジェクトを自由に増やすことができる
-
Subscriber
クラスを継承したオブジェクトであればどのようなオブジェクトでもPublisher
に登録することができる為、処理に応じたクラスを作成することができる
- 無論、
Subscriber
をクラスではなく抽象クラスやインターフェイスとして定義してもよい(一般的にはそのようにするべきである)
通知を受けるクラスの責務が単純になる
-
Publisher
はSubscriber
を保持すること、通知を受けたら保持しているSubscriber
のメソッドを呼び出すことだけが責務であり、具体的な処理の内容に関与する必要がない
- その為、
Publisher
の受け持つ責務が単純なものになり、保守性・テスト容易性が担保される
5. デメリット
構成要素が増えるので複雑性が増加する
- 単純に関連オブジェクトのメソッドをコールするだけの場合に比べると構成要素が増加するので複雑性が増加する
- 採用する場合は本当にObserverパターンが必要なのか慎重に検討しなければならない
6. 注意事項
アプリケーションロジックの中で利用する機会は少ない
- 「状態変化を通知する仕組み」を提供する為のパターンである為、アプリケーションロジック(ビジネスロジック)の中でObserverパターンが必要になるケースは稀である
- 基本的にライブラリやフレームワークを作成する際に必要になるパターンであり、アプリケーションロジックでの利用は無駄な複雑性の増加に繋がるので避けることが推奨される
Appendix-1 サンプルコード
class Publisher:
def __init__(self):
self.__subscribers = []
def register(self, subscriber):
self.__subscribers.append(subscriber)
def unregister(self, subscriber):
self.__subscribers.remove(subscriber)
def notify(self):
for subscriber in self.__subscribers:
subscriber.update()
class Subscriber:
def __init__(self, msg):
self.__msg = msg
def update(self):
print(self.__msg)
if __name__ == '__main__':
pub = Publisher()
sub1 = Subscriber("わーい")
sub2 = Subscriber("うぇーい")
pub.register(sub1)
pub.register(sub2)
pub.notify() # => わーい うぇーい
pub.unregister(sub2)
pub.notify() # => わーい