Observer パターン(オブザーバー・パターン)とは、プログラム内のオブジェクトに関するイベント(事象)を他のオブジェクトへ通知する処理で使われるデザインパターンの一種。

通知するオブジェクト側が、通知されるオブジェクト側に観測・観察(: observe)される形になることから、こう呼ばれる。

出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。

分散イベント処理システムの実装にも使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。

クラス図 編集

このパターンの基本は、イベントを通知される側の1つ以上のオブジェクト(オブザーバーまたはリスナーと呼ぶ)を、通知する側のオブジェクト(サブジェクトと呼ぶ)に登録することである。そして通知に使われるメソッドが、抽象メソッドになっていることが重要である。言語によっては、コールバック関数と通知対象コンテキストのペア、あるいはそれらをカプセル化した関数オブジェクト、またはデリゲートが使われる[1]

以下に、その構造をUMLクラス図で視覚化したものを示す。

 

各クラスの解説 編集

このパターンに登場する各インタフェースとインタフェースの実装クラスを、以下で解説する。

Subject 編集

イベントを通知するオブジェクト側のインタフェース。1つ以上のObserverすなわちイベントを通知されるオブジェクト側のインタフェースの登録・削除・通知のメソッド書式の体裁を提供する。

以下の抽象メソッドを持つ:

  • addObserver() - Subjectが持つ「通知を受け取るObserver群」に、新たなObserverを加える[注釈 1]
  • removeObserver() - addObserver()で追加されたオブジェクトを削除する[注釈 2]
  • notifyObservers() - Subjectが持つ「通知を受け取るObserver群」に、Observer.notify() を呼んでイベントを通知する。

上のUMLクラス図では、Subjectがインタフェースと実装クラスに分かれているが、パターン要件ではない。インタフェースを使わずクラスを直接実装することもある。

ConcreteSubject 編集

Subjectの実装クラス。通知対象であるObserver群を持つ。各Observerが受け取る通知に関する処理を司る。notifyObservers() を呼ぶと、Observer群の1つ以上にイベントを通知する。

Observer 編集

イベントを通知される側のインタフェース。以下の抽象メソッドを持つ:

  • notify() - Observerにとっては通知を受け取る処理、このメソッドを呼ぶSubjectにとっては通知を送る処理、と言える。このメソッドの個数や各書式は、通知内容により様々である。

ConcreteObserver 編集

Observerの実装クラス。

典型的用法 編集

  • ユーザーが何らかの操作をするなどの外部イベントを待つ。イベント駆動型プログラミングを参照。
  • あるオブジェクトの属性値の変化を待つ。なお、複数の属性値の変化でコールバック関数を呼び出すようにしているとイベントの連鎖的発生を引き起こす。
  • メーリングリストで、何らかのイベント(新製品情報など)があったとき、購読者リストに登録している人にメッセージを送る。

Observer パターンは Model View Controller (MVC) パラダイムの実装に使われることも多い。MVC では、モデルとビューの連携に Observer パターンが使われる。通常、コントローラーがモデルの変化を検出し、ビュー(オブザーバー)に通知する。

コード例 編集

Python 編集

以下のコードは Python 3.x で Observer パターンを記述したものである。引数を1つ受け取るupdate()メソッドを持つオブジェクトであれば、リスナーとして何でも受け付ける(ダック・タイピング)。なお、例ではリスナーの集合を保持するためにリストを使用しているため、同じオブジェクトの多重登録を許可する実装となっている。

class Listener:
    def __init__(self, name):
        self.name = name

    def update(self, event):
        print(self.name, "received event", event)

class Subject:
    def __init__(self):
        self.listeners = []

    def add_listener(self, listener):
        self.listeners.append(listener)

    def remove_listener(self, listener):
        self.listeners.remove(listener)

    def notify_listeners(self, event):
        for listener in self.listeners:
            listener.update(event)

subject = Subject()
listenerA = Listener("<listener A>")
subject.add_listener(listenerA)
listenerB = Listener("<listener B>")
subject.add_listener(listenerB)
# subject には2つのリスナーが登録されている。
subject.notify_listeners("<event 1>")

出力:

<listener A> received event <event 1>
<listener B> received event <event 1>

Java 編集

ロックを避けるため、CopyOnWriteArraySetを使用する例を示す。スレッドセーフにする必要がない、あるいはsynchronizedで同期するのであれば、HashSetTreeSetを使ってもかまわないが、コンテナの実装によっては順序が保証されず、リスナーを追加したときの順番でupdate()が呼ばれるとは限らない。

// Listener.java

public interface Listener {
    public void update(String event);
}
// Subject.java

import java.util.concurrent.CopyOnWriteArraySet;
import java.util.Set;

public class Subject {
    private final Set<Listener> listenerSet = new CopyOnWriteArraySet<Listener>();

    public void addListener(Listener listener) {
        listenerSet.add(listener);
    }

    public void removeListener(Listener listener) {
        listenerSet.remove(listener);
    }

    public void notifyListeners(String event) {
        for (Listener listener : listenerSet) {
            listener.update(event);
        }
    }
}
// Main.java

class ListenerImpl implements Listener {
    private final String name;

    public ListenerImpl(String name) {
        this.name = name;
    }

    @Override
    public void update(String event) {
        System.out.println(this.name + " received event " + event);
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Subject subject = new Subject();
        Listener listenerA = new ListenerImpl("<listener A>");
        subject.addListener(listenerA);
        Listener listenerB = new ListenerImpl("<listener B>");
        subject.addListener(listenerB);
        subject.notifyListeners("<event 1>");
    }
}

出力結果はPythonの例と同じである。

実装 編集

Observer パターンは各種ライブラリやシステムに実装されている。特にGUIツールキットには必ず含まれる。

脚注 編集

注釈 編集

  1. ^ 「登録する」動作を意味するregister()という名前が使われることもある。
  2. ^ 「登録解除する」動作を意味するunregister()という名前が使われることもある。

出典 編集

関連項目 編集

外部リンク 編集