JAvaでのオブザーバーモード2

19368 ワード

『JAVAとモード』のオブザーバーモード
閻宏博士の『JAVAとモード』では、観察者(Observer)モードをこのように記述しています.
オブザーバ・モードは、オブジェクトの動作モードであり、パブリッシュ・サブスクリプション・モード、モデル・ビュー・モード、ソース・リスナー・モード、またはDependents・モードとも呼ばれます.
オブザーバ・モードは、複数のオブザーバ・オブジェクトがトピック・オブジェクトを同時にリスニングできるように、一対多の依存関係を定義します.このトピックオブジェクトは、ステータスが変化すると、すべてのオブジェクトに自動的に更新できるように通知されます.オブザーバモードの構造
1つのソフトウェアシステムには様々なオブジェクトが含まれており、喜びの森が様々な生物に満ちているように見えます.一面の森の中で、各種の生物は互いに依存して制約して、1つの生物の鎖を形成します.1つの生物の状態の変化は他のいくつかの生物の相応の行動をもたらし、すべての生物は他の生物の相互作用の中にある.
同様に、あるソフトウェアシステムは、あるオブジェクトの状態が変化したときに、ある他のオブジェクトに対応する変化を要求することが多い.これを行う設計案は多いが,システムの多重化を容易にするためには,低結合度の設計案を選択すべきである.オブジェクト間の結合を減らすことはシステムの多重化に有利であるが,デザイナーはこれらの低結合度のオブジェクト間で行動の協調一致を維持し,高度な協力を保証する必要がある.観察者モードはこの要求を満たす様々な設計案の中で最も重要なものである.
以下では,単純な例示的な実装を例に,観察者モードの構造について論じる.
オブザーバーモードの役割は次のとおりです.
●抽象トピック(Subject)ロール:抽象トピックロールは、すべてのオブジェクトに対する参照を1つの集計(例えば、ArrayListオブジェクト)に保存し、各トピックには任意の数のオブジェクトが存在してもよい.抽象トピックは、オブザーバーオブジェクトを追加および削除するインタフェースを提供します.抽象トピックロールは、オブザーバー(Observable)ロールとも呼ばれます.
●特定テーマ(ConcreteSubject)キャラクター:関連状態を特定の観察者オブジェクトに保存する;特定のトピックの内部状態が変化した場合,登録されたすべての観察者に通知する.具体的なテーマキャラクタは、具体的なオブジェクトキャラクタとも呼ばれます.
●抽象オブザーバー(Observer)ロール:すべての特定のオブザーバーにインタフェースを定義し、トピックの通知を受けたときに自分を更新します.このインタフェースを更新インタフェースと呼びます.
●特定オブザーバー(ConcreteObserver)ロール:トピックの状態と適切な状態を格納します.特定のオブザーバーキャラクタは、抽象オブザーバーキャラクタが要求する更新インタフェースを実現し、自身の状態とトピックの状態像を調和させる.必要に応じて、特定のオブザーバーキャラクタは、特定のトピックオブジェクトへの参照を保持できます.ソースコード
抽象トピックロールクラス
  
public abstract class Subject {
    /** *              */
    private    List<Observer> list = new ArrayList<Observer>();
    /** *         * @param observer       */
    public void attach(Observer observer){

        list.add(observer);
        System.out.println("Attached an observer");
    }
    /** *         * @param observer       */
    public void detach(Observer observer){

        list.remove(observer);
    }
    /** *              */
    public void nodifyObservers(String newState){

        for(Observer observer : list){
            observer.update(newState);
        }
    }
}

特定のトピックの役割クラス
public class ConcreteSubject extends Subject{

    private String state;

    public String getState() {
        return state;
    }

    public void change(String newState){
        state = newState;
        System.out.println("     :" + state);
        //      ,       
        this.nodifyObservers(state);
    }
}

抽象オブザーバキャラクタクラス

public interface Observer {
    /** *      * @param state       */
    public void update(String state);
}

オブザーバーの役割クラス

public class ConcreteObserver implements Observer {
    //      
    private String observerState;

    @Override
    public void update(String state) {
        /** *         ,             */
        observerState = state;
        System.out.println("   :"+observerState);
    }

}

クライアントクラス
public class Client {

    public static void main(String[] args) {
        //      
        ConcreteSubject subject = new ConcreteSubject();
        //       
        Observer observer = new ConcreteObserver();
        //              
        subject.attach(observer);
        //         
        subject.change("new state");
    }

}

実行結果は次のとおりです.
実行時に、このクライアントはまず特定のトピッククラスのインスタンスとオブザーバオブジェクトを作成します.次に、トピックオブジェクトのattach()メソッドを呼び出し、このオブジェクトをトピックオブジェクトに登録します.つまり、トピックオブジェクトの集約に追加します.
このとき,クライアントはトピックのchange()メソッドを呼び出し,トピックオブジェクトの内部状態を変更する.トピックオブジェクトは、ステータスが変化したときにスーパークラスのnotifyObservers()メソッドを呼び出し、登録されたすべてのオブジェクトに通知します.プッシュモデルとプルモデル
観察者モードでは,プッシュモデルとプルモデルの2つの方式に分けられる.
●プッシュモデル
トピックオブジェクトは、オブジェクトが必要かどうかにかかわらず、オブジェクトにトピックの詳細をプッシュします.プッシュされた情報は、通常、トピックオブジェクトのすべてまたは一部のデータです.
●引き型
トピックオブジェクトは,観察者に通知する際に,わずかな情報しか伝達しない.観察者がより具体的な情報を必要とする場合、観察者がアクティブにトピックオブジェクトに取得し、観察者がトピックオブジェクトからデータを引くことに相当する.一般的にこのモデルの実装では,トピックオブジェクト自体をupdate()法で観察者に渡すことで,観察者がデータを取得する必要がある場合に,この参照で取得できる.
上記の説明によれば、前述の例が典型的なプッシュモデルであることが判明し、次にプルモデルの例を示す.
モデルを引く抽象観察者類
プルモデルは、通常、トピックオブジェクトをパラメータとして渡します.
public interface Observer {
    /** *      * @param subject       ,               */
    public void update(Subject subject);
}

モデルを引く具体的な観察者類
public class ConcreteObserver implements Observer {
    //      
    private String observerState;

    @Override
    public void update(Subject subject) {
        /** *         ,             */
        observerState = ((ConcreteSubject)subject).getState();
        System.out.println("      :"+observerState);
    }

}

モデルの抽象トピッククラスを引く
ラモデルの抽象的主題クラスの主な変化はnodifyObservers()法である.観察者にループ通知するとき,すなわち観察者のupdate()メソッドをループ呼び出したときに伝達されるパラメータが異なる.
public abstract class Subject {
    /** *              */
    private    List<Observer> list = new ArrayList<Observer>();
    /** *         * @param observer       */
    public void attach(Observer observer){

        list.add(observer);
        System.out.println("Attached an observer");
    }
    /** *         * @param observer       */
    public void detach(Observer observer){

        list.remove(observer);
    }
    /** *              */
    public void nodifyObservers(){

        for(Observer observer : list){
            observer.update(this);
        }
    }
}

モデルの特定のトピッククラスを引く
プッシュモデルと比較して,観察者に通知する方法を呼び出す際にパラメータを伝達する必要がなくなる点が変化した.
public class ConcreteSubject extends Subject{

    private String state;

    public String getState() {
        return state;
    }

    public void change(String newState){
        state = newState;
        System.out.println("     :" + state);
        //      ,       
        this.nodifyObservers();
    }
}

2つのモードの比較
■プッシュモデルは、対象物が観察者に必要なデータを知っていると仮定する.ラモデルはテーマオブジェクトが観察者に具体的にどのようなデータが必要なのか分からず、仕方がない場合は、いっそ自分を観察者に伝え、観察者に自分で必要に応じて値を取るようにします.
■プッシュモデルは、観察者のupdate()メソッドが必要に応じて定義されたパラメータであるため、考慮されていない使用状況を両立させることができない可能性があるため、観察者オブジェクトの多重化が困難になる可能性がある.これは、新しい状況が発生した場合、新しいupdate()方法を提供するか、いっそ観察者を再実現する可能性があることを意味する.モデルを引くと、update()メソッドのパラメータがトピックオブジェクト自体であるため、基本的にはトピックオブジェクトが伝達できる最大のデータ集合であり、基本的には様々な状況のニーズに適応することができる.
JAVAが提供する観察者モードのサポート
JAVA言語のjava.utilライブラリでは、ObservableクラスとObserverインタフェースが提供され、JAVA言語の観察者モードのサポートを構成しています.Observerインタフェース
このインタフェースはupdate()メソッドのみを定義し,被観察者オブジェクトの状態が変化すると,被観察者オブジェクトのnotifyObservers()メソッドがこのメソッドを呼び出す.
public interface Observer {

    void update(Observable o, Object arg);
}

Observableクラス
被験者クラスはjava.util.Observableクラスのサブクラスである.JAva.util.Observableは、Observableのサブクラスに対する2つの非常に重要な方法を提供します.1つはsetChanged()であり、もう1つはnotifyObservers()です.第1の方法setChanged()が呼び出されると、観察者オブジェクトの状態が変化したことを示す内部タグ変数が設定されます.2つ目はnotifyObservers()で、このメソッドが呼び出されると、登録されたすべてのオブジェクトのupdate()メソッドが呼び出され、これらのオブジェクトが自分を更新できるようになります.
public class Observable {
    private boolean changed = false;
    private Vector obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
    obs = new Vector();
    }

    /** *                  */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }

    /** *                 */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
    notifyObservers(null);
    }

    /** *         (  hasChanged      true) *                ,      update()   *   this arg     */
    public void notifyObservers(Object arg) {

        Object[] arrLocal;

    synchronized (this) {

        if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /** *          */
    public synchronized void deleteObservers() {
    obs.removeAllElements();
    }

    /** *  “   ”   true */
    protected synchronized void setChanged() {
    changed = true;
    }

    /** *  “   ”   false */
    protected synchronized void clearChanged() {
    changed = false;
    }

    /** *            */
    public synchronized boolean hasChanged() {
    return changed;
    }

    /** * Returns the number of observers of this <tt>Observable</tt> object. * * @return the number of observers of this object. */
    public synchronized int countObservers() {
    return obs.size();
    }
}

このクラスは被観察者オブジェクトを表し、テーマオブジェクトと呼ばれることがある.1つの観察者オブジェクトは、Observerインタフェースを実装するオブジェクトである複数の観察者オブジェクトを有することができる.観察者が変化すると、ObservableのnotifyObservers()メソッドが呼び出され、このメソッドはすべての特定の観察者のupdate()メソッドを呼び出し、すべての観察者が自分の更新を通知される.観察者モードに対するJAVAのサポートの使い方
ここでは、JAVAが提供する観察者モードのサポートをどのように使用するかを説明する非常に簡単な例を示します.この例では、観察対象をWatchedと呼ぶ.観察者の対象はWatcherと呼ばれていますWatchedオブジェクトはjava.util.Observableクラスから継承されます.Watcherオブジェクトにはjava.util.Observerインタフェースが実装されています.もう1つのTestクラスがクライアントの役割を果たしています.ソースコード
被観察者Watchedクラスソースコード
public class Watched extends Observable{

    private String data = "";

    public String getData() {
        return data;
    }

    public void setData(String data) {

        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
        }
        notifyObservers();
    }


}

オブザーバクラスソースコード
public class Watcher implements Observer{

    public Watcher(Observable o){
        o.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {

        System.out.println("      :" + ((Watched)o).getData());
    }

}

クラスソースコードのテスト
public class Test {

    public static void main(String[] args) {

        //        
        Watched watched = new Watched();
        //       ,          
        Observer watcher = new Watcher(watched);
        //         
        watched.setData("start");
        watched.setData("run");
        watched.setData("stop");

    }

}

Testオブジェクトは、まずWatchedオブジェクトとWatcherオブジェクトを作成します.Watcherオブジェクトの作成時にWatchedオブジェクトをパラメータとして入力します.その後、TestオブジェクトはWatchedオブジェクトのsetData()メソッドを呼び出し、Watchedオブジェクトの内部状態の変化をトリガする.Watchedオブジェクトは、登録されたWatcherオブジェクト、すなわちそのupdate()メソッドを呼び出すことをさらに通知する.