kanizaのブログ

コンピュータ、ソフトウェア、映画、音楽関連や家族のことなど、思いついたことを書きます。

Genericsで汎用のオブザーバ管理クラス

電車の中でなんとなく「こーいうクラスあったら便利かも」と思いついたので書いておく。

Observerパターンを実装するとき、監視される側(Subject)ではオブザーバインスタンスをコレクションで管理したり、通知のたびにそのコレクションを走査してメソッドを呼出したりするコードを書かなくてはいけない。その労力を軽減するために PropertyChangeSupport のようなクラスがある。

そのへんをオブザーバの型にかかわらず汎用的に扱うためのクラスが下の ObserverSupport。notifier()メソッドが、呼出されたメソッドをそのまま全オブザーバに転送する Proxy を返す点がポイント。自分で直接 Proxy クラスを使うコード書いたのってはじめてだな。

package kani;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

public class ObserverSupport<O> implements Serializable {

    // 走査中に remove されてもいいように CopyOnWrite の実装を用いる
    private transient Set<O> observers = new CopyOnWriteArraySet<O>();

    private final Class<O> observerType;

    // Generics 対応の static ファクトリ。
    public static <O> ObserverSupport<O> create(Class<O> observerType) {
        return new ObserverSupport<O>(observerType);
    }
    
    public ObserverSupport(Class<O> observerType) {
        this.observerType = observerType;
    }

    public final void addObserver(O observer) {
        observers.add(observer);
    }

    public final void removeObserver(O observer) {
        observers.remove(observer);
    }

    public final O notifier() {
        // メソッド呼出しを observers 内の全インスタンスに転送する Proxy を返す。
        return (O) Proxy.newProxyInstance(observerType.getClassLoader(),
                new Class<?>[] { observerType }, new InvocationHandler() {

                    public Object invoke(Object proxy, Method method,
                            Object[] args) throws Throwable {
                        for (O observer : observers) {
                            method.invoke(observer, args);
                        }
                        return null;
                    }
                });
    }

    // デシリアライズ時に observers を初期化する
    private void readObject(ObjectInputStream in) throws IOException,
            ClassNotFoundException {
        in.defaultReadObject();
        observers = new CopyOnWriteArraySet<O>();
    }
}

使用例は次のような感じ。実行すると、mainで登録した2つのオブザーバのメソッドがちゃんと呼出されることが確認できる。

package kani;

import java.io.Serializable;

public class ObserverSupportSample {

    // サンプルの Observer インタフェース
    public static interface SampleObserver {
        void somethingHappned(String thing);
    }

    // サンプルの Subject  クラス
    public static class SampleSubject implements Serializable {
        private ObserverSupport<SampleObserver> observerSupport = ObserverSupport
                .create(SampleObserver.class);
        
        public void addSampleObserver(SampleObserver observer) {
            observerSupport.addObserver(observer);
        }

        public void removeSampleObserver(SampleObserver observer) {
            observerSupport.removeObserver(observer);
        }
        
        public void bigThingHapppened() {
            // ループを回さずに任意の引数でオブザーバに通知できる。
            observerSupport.notifier().somethingHappned("BigThing");
        }
    }

    public static void main(String[] args) {
        SampleSubject subject = new SampleSubject();
        subject.addSampleObserver(new SampleObserver() {

            public void somethingHappned(String thing) {
                System.out.println("Hello "+thing);
            }});
        subject.addSampleObserver(new SampleObserver() {

            public void somethingHappned(String thing) {
                System.out.println("Goodbye "+thing);
            }});
        
        subject.bigThingHapppened();
    }
}

どうでしょうね。便利かな。