GoF本 Observer
ねらい
- 1対多のオブジェクト依存関係において、1のオブジェクトの状態の変更を、多のオブジェクトすべてに自動で通知したい
AKA
- Dependents
- Publish-Subscribe
モチベーション
- MVCのMとVみたいな話
- 例) データの表現
- 表
- グラフ
- データが変わったら、自動的に表やグラフにも変更を反映したい
- 表やグラフはデータに依存している(dependents)
- 例) データの表現
- 上述の例では、データが
Subject
、表やグラフがObserver
Subject
に対して、Observer
は任意の個数登録されうる
つかいどころ
- 一方が他方に依存する、という構図に抽象化できる
- 抽象化することで、それぞれを独立して変更したり再利用したりできる
- あるオブジェクトの変更が別のオブジェクトの変更を誘起すること
- 通知の送信側は、受信側についてなんの仮定ももつべきでない
- つまり、疎結合にしたい
- 【補】buttonのclickイベント。
event引数のふくまれる座標情報等が、誰に受信されて、どう使われるか、知ったこっちゃない
登場人物
- Subject
- observerを知っている
- 任意の数
- observerの登録・抹消のインターフェースを定義
- observerを知っている
- Observer
- subjectの状態の変化の通知を受け取り、状態を追従するインターフェースをもつ
- ConcreteSubject
- ConcreteObserverの興味の対象である状態を保持する
- 状態が変化したらobserverに通知する
- ConcreteObserver
- concreteSubjectオブジェクトへの参照を保持する
- subjectオブジェクトに追従すべき状態を保持する
- subjectオブジェクトに状態を追従するインターフェースを実装する
クライアントコードからの利用
- ConcreteSubjectの状態が変化する
- ConcreteSubjectは状態の変化を通知する(
subject->Notify()
)- observerオブジェクトたちの状態を更新する(
object->Update()
)- concreteObserverオブジェクトたちは、subjectから状態の変化を通知されたら、情報を要求する(
subject->GetState()
) - concreteObserverオブジェクトたちは、subjectから情報を受け取り、状態を追従する
- concreteObserverオブジェクトたちは、subjectから状態の変化を通知されたら、情報を要求する(
- observerオブジェクトたちの状態を更新する(
結果
- SubjectとObserverとが疎結合になる
- 互いに抽象基底クラスしか意識しない
- 下位レイヤー(Subject)から上位レイヤー(Observer)への連絡のすべができる
- 密結合でやるとただの相互依存になるので駄目
- Subjectのやることは「observer全員に通知する」ことなので、動的に増減してもいいし不在でもいい
- 一見無害なメソッド呼び出しが、想定外の状態更新を引き起こすことがある
実装にあたり考えるべきこと
- subject -> observers のマッピング
- 1つのobserverが多数のsubjectsを購読する
- どのsubjectからの通知かを知る必要がある
- 誰が
subject->Notify()
を呼ぶ?- 下記2種類。トレードオフ
- subjectの状態が変化したときのhook
- 通知忘れがない
- 複数の状態を一度に更新した際、無駄通知がおこる
- クライアントコードから明示的に
- 複数の状態を一度に更新した際、通知を1回にまとめられる
- 通知忘れがある
- subjectの状態が変化したときのhook
- 下記2種類。トレードオフ
- dangling reference問題
- subjectとobserverとは相互に参照をもつ
- subjectを削除した場合、observerにその旨を伝える必要がある
- さもないと、observer->subject がdangling referenceになってしまう
- observerを消すのはNG
- 他のsubjectを購読している可能性がある
- subject->object がdangling referenceになってしまう
- 他のsubjectを購読している可能性がある
- Notification前時点で、subjectの状態は完全なものであること
- 半端な状態で
Notify()
してはいけない - overrideで崩れやすい
- 【補】
super.doSomething()
をメソッド冒頭でしか呼べない言語は多い doSomething()
の中でNotify()
していて、その後に状態の更新処理doAnotherThing()
を書いてしまうケースdoAnotherThing()
を実行して初めて状態は完全なものになるが、その前にすでにNotify()
してしまっている
- 【補】
- Template Method Patternを用い、
Notify()
をメソッド末尾に強制することで回避可能
- 半端な状態で
- あるobserver用の更新プロトコルを避ける
- 更新プロトコルの両極端
- pull
- 通知のみ。状態は自分で取得してくれ
- 【補】DOM Eventsで、event引数がリスナに渡ってこないようなイメージ
- push
- 通知時に、必要なくても情報を押し付ける
- 【補】DOM Eventsで、要らなくてもevent引数がリスナに渡ってくる
- pull
- pullは不便
- pushは、observerに仮定をもっているため、再利用性が損なわれる
- subject「observerはこういうデータを受け取るもののはずだ」
- 更新プロトコルの両極端
- 変更の種類を明示するインターフェース
- 効率面で有利
- subjectにobserverを登録する際、「どの変更を購読します」という情報も登録する
- subjectの状態が変化したら、当該変更のobserverにのみ通知する
- 【補】
EventTarget.addEventListener(type, listener, options)
のtype
引数に相当
- 効率面で有利
- 複雑な更新処理をカプセル化する
- subjectが複数あるようなケース
- あるsubjectの状態の変化が別のsubjectの状態の変化に波及する
- ナイーブに実装すると、状態追従処理が何度も呼ばれて冗長
- DAG: Directed Acyclic Graphで依存性を表現し、状態追従処理を1度で済ませることができる
- SubjectとObserverとを1つのインターフェースに
- 多重継承できない言語で有用
関連するパターン
- Mediator
- ChangeManagerは、Mediator PatternにおけるMediatorにあたる
- Singleton
- ChangeManagerは単一インスタンスでよく、グローバルな入口をもたせるためにもSingletonにするのがよい
英語
- reconcile
- 一致させる
- 本では、stateのinconsistencyを修正する的な意味合い
- 一致させる
- intact
- そのまま
- 「上位モジュールが下位モジュールにリクエストする」という構造を変えずに、という文脈で使用
- 処女とか童貞とかいう意味もあるみたい
- そのまま
- innocuous
- 無害な
- 「有害」とは、ある場所を触ったら関係ないモジュールが爆発するようなさまを指している
- 画像検索すると可愛い子犬が出てくる
- 無害な
- spurious
- 偽の、虚偽の
- 非嫡出子の
- 依存性がちゃんと定義されていなくて、状態の更新が望まれない形で行われることを表現
- aggravate
- 【他動詞】さらに悪化させる
- deteriorate, worsen, become worse などはいずれも自動詞(句)
- 【他動詞】さらに悪化させる
- deduce
- 推定する
- 名詞形は type deduction (型推論)などでおなじみ
- 推定する