datchの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

【デザインパターン】オブザーバパターン

どうも、海外出立直前でビクビクしています。
行き先では命までは取られないものの、観光客を狙ったスリ、詐欺が横行しているみたいなのでスられないに頑張ります。
ボスが一緒に来てくださるので、そこまで不安ではないのですが、やっぱり怖いですね。

さて、それでは今回もデザインパターンの【Observer】についてです。

参考書



さらっと読める「OREILLY Head First デザインパターン」を参考にしながら書いています。
しばらくはこいつを参考にデザインパターンについて書いていくよ!

Observerパターンって何ですか?



Observerパターンは、とあるオブジェクトの変更を関係するオブジェクトに通知するパターンです。

一応、こちらでもWikipedia大先生による解説を載せておきます。
Observer パターン - Wikipedia

Observer パターン(オブザーバ・パターン)とは、プログラム内のオブジェクトの状態を観察(observe)するようなプログラムで使われるデザインパターンの一種。出版-購読型モデルとも呼ばれる。暗黙的呼び出しの原則と関係が深い。
分散イベント処理システムの実装に主に使われる。言語によっては、このパターンで扱われる問題は言語が持つイベント処理構文で処理される。リアルタイムのアプリケーション配置の手段として興味深い機能である。

どういう仕組み?



ここでは参考書の中にある分かりやすい例を元に簡単に説明した後、具体的な説明を入れていきます。

簡単な例

とある職業仲介業者に登録した二人のエンジニアがいました。

エンジニア(Observer)を仲介業者(Subject)に登録する。
ロン(Observer)「私はJava開発職を探しています。5年間の経験があり…」
仲介業者(Subject)「Java開発者のリストに入れておくので、電話はしないでください。こちらから連絡します。」


更に違うエンジニア(Observation)を仲介業者(Subject)に登録する。
ジル(Observer)「私は多数のEJBシステムを書いた経験があり、あなたが担当しているJava開発に関する仕事に興味があります。」
仲介業者(Subject)「リストに通知しておきます。他のみなさんと同様に通知します。」


仲介業者(Subject)がリストに登録してあったエンジニア(Observer)に通知する。
仲介業者(Subject)「やあ、オブザーバ諸君、◯◯◯でJavaの職があるよ。飛びつきな!逃したらダメだ!絶対確実だよ!」
ロン(Observer)「ありがとう、すぐに履歴書を送ります。」
ジル(Observer)「この男は本当の愚か者だわ、彼は必要ないわ。自分で仕事を探します。」


仲介業者(Subject)がリストに登録してあったエンジニア(Observer)をリストから削除する。
ジル(Observer)「電話リストから私をはずしてください。自分で仕事を見つけました。」
仲介業者(Subject)「ああっー!!!よく聞きなさい、ジル。この町で二度と私が関係しているあ職には就けなくなるよ。電話リスからはずします!!!」

具体的な説明

先ほどのやりとりでもあったように、SubjectとObserverという2つのクラスがObserverパターンに存在します。

  1. Subject
    必要な情報をObserverに通知し、Observerを通知リストに登録したり、削除したりすることが出来ます。
  2. Observer
    Subjectから送られてきた情報を元に処理を行います。

イメージとしてはイベントドリブンモデルに近いものがありますね。

これ使って何が嬉しいの?



オブジェクトのデータに変更が加えられた時にそれが通知される仕組みになっており、SubjectとObserverをinterfaceとして実装することで依存性が減ります。
また、ある一定の情報の値を元に処理が決定されるものが多数作成されても(例えば、Viewer等)、関数一つで全てのObserverに通知することが可能になります。

簡易実装例


interface Subject
{
    registerObserver(Observer); // Observerの登録
    removeObserver(Observer);   // Observerの削除
    notifyObserver();           // Observerへの通知
}

interface Observer
{
    update(int);
}

class ConcreteSubject implements Subject
{
    ConcreteSubject()
    {
        observers = new ArraList(); // Observerの登録リストを初期化
    }

    registerObserver(Observer o)
    {
         observers.add(o);
    }

    removeObserver(Observer o)
    {
        int i = observers.indexOf(o);
        if(i >= 0)
        {
            observers.remove(i);
        }
    }

    notifyObservers()
    {
        for(int i = 0; i < observers.size(); ++i)
        {
            Observer o = (Observer)observers.get(i);
            o.update(information);
        }
    }

    // 何からしらの情報を受け取り、変更が加えられた時にObserverに通知する
    setInformation(string information)
    {
         this.information = information;
         notifyObservers();
    }
}

このようにしてSubject内でObserverを監視することで、変更が加えられる度に通知をすることが出来ます。
また、通知を行う時に条件付け(例えば、前の値よりも変化が小さい時には処理を行わない等)をすることで通知を制限したりも出来ます。

Javaで提供されているObserverパターン



実はJavaでも組み込みのObserverパターンが提供されています。

// このパッケージにObservable(Subject)とObserverが入っている
import java.util.Observable;
import java.util.Observer;

// intefaceではない点に注意
class Observable
{
    addObserver();     // regitObserver()と同義
    deleteObserver();  // removeObserver()と同義
    notifyObservers();
    setChange();       // この関数でisChangeフラグを立てないと、isChangeフラグが立っていない場合はnotfityObservers()が呼ばれても動作しない
}

// こちらは先ほどと同じようにinterfaceで提供されている
interface Observer
{
    update(Observable obs, Object arg);
}

こちらのJavaパッケージを使う場合は、ConcreteSubjectに対してgetterを用意してあげる必要があります。
なぜなら、ObserverクラスのupdateはSubjectから値を受け取る(Subjectからメッセージングされる)のではなく、Subjectから値を抽出することで変更した値を抽出しています。

*ただし、このObservableはクラスを継承(is-a)する関係になっているため、他のクラスを継承しているオブジェクトに適応できなかったりするので、注意が必要です。その為、複雑になった場合は自身でSubjectをinterfaceとして提供した方が、依存性も減らすことが出来ます。

おわりに



最後まで読んで頂きありがとうございました。
ある値を変更された時に、他のオブジェクトにもその変更を伝搬したい、というシチュエーションはかなり多いと思います。
皆さんも伝搬するオブジェクトが多くなった時は、Observerパターンを使うことで統一的な変更を行うことができるので、頭の隅にでも入れておきましょう!