Deep Side of Java〜Java 言語再入門 第3回 〜 クラス設計とデザインパターン

やさしいデザインパターン

Observer






Observer

Observer は、非同期に起きるイベントを処理するのに良く使われるデザインパターンである。なので、AWTのようなGUIツールキットを使うに当って、このデザインパターンを理解していることが条件になるし、また、非同期にイベントが起きるスレッド間の情報伝達にも重要な役割を果たす。要するにシグナルハンドラやコールバック関数の概念をオブジェクト指向言語で実装したのがこの Observer であると考えれば良い。

C言語では関数ポインタを使って、シグナルハンドラやコールバック関数を実現する。しかし、Java では関数ポインタは存在しない。なので、関数を引数として渡すことはできずに、その代わりに引数としてクラスのインスタンスを渡す。そのクラスに特定の名前のメソッドがあれば、コールバック関数と同じように使うことができるのである。これが Observer のアイデアである。

void my_handler( int signo ){
    write( 1, "got SIGINT!!!\n", 14 );
    signal( SIGINT, my_handler );
}

int main( ) {
    signal( SIGINT, my_handler );
    while( 1 );
}   

Cで単純に Ctrl+C 割り込みをトラップするコードは上のようなものである。signal(2) は特定のシグナルに対して、それが起きたときに実行される関数を登録する機能を持っている。だから Ctrl+C が押されると、my_handler() が実行される。これをクラスによって疑似的に(ホントは Java は UNIX シグナルを実装していない)書き直して見よう。

class SetSignal implements SignalHandler {
    Signal sig;
    public static void main( String [] args ) {
        new SetSignal();
    }
    void SetSignal( ) {
        sig = new Signal( SIGINT );
        sig.addHandler( this );
        while( true );
    }
    void signalHandler( int signo ) { 
        System.out.println( "got SIGINT!!!" ); 
    }
}

ここでポイントになるのは、SignalHandler interface である。これは次のように定義されている。

public interface SignalHander {
    public void signalHandler( int signo );
}

つまり、SignalHander interface は、それを implements したクラスに、signalHandler() という名のメソッドがあることを保証する。だから、確実に signalHandler() が存在するクラスを Signal クラスの addHandler() メソッドの引数として渡せるのである。

では Signal クラスの addHandler() メソッドはどういう風に定義すればよいのだろう。これはいわゆるマルチキャスト(1つのシグナルに対して複数のハンドラを登録できる)が可能であるように実現するのが普通である。だから addHandler() はただ1つの SignalHandler 型変数に引数を代入するのではなく、Vector 型インスタンス変数に保存すべきである。

public class Signal {
    private Vector handlers = new Vector();
    private int sigNo;
    public Signal( int n ) {
        sigNo = n;
    }
    public synchronized void addHandler( SignalHandler sig ) {
        if( sig == null ) {
        /* 引数値が null なら登録されているハンドラを捨てる
           うっかり null を渡してしまうこともないわけでないので、
           あまり良くない実装であるが... */
            handlers = new Vector(();
        } else {
            /* 単に Vector に登録するだけ */
           handlers.add( sig );
        }
    }

    /* Ctrl+C が押されると起動されて、それを扱うハンドラを呼び出す */
    /* synchronized メソッドを使っていることに注意。この関数実行中は
       handlers の内容の変更は行われ得ない。 */
    private synchronized void sendSignal( ) {
        for( int i = 0; i < handlers.size(); i++ ) {
            SignalHandler at = (SignalHander)handlers.elementAt( i );
            at.signalHandler( sigNo );
        }
   }
}

この実装では、複数のシグナルハンドラを登録できることは自明であろう。この Observer パターンは、AWT で各種イベント(マウス・キーボード)を処理している。また、スレッド間で通信を行う時にこの Observer を使うべきであることは言うまでもない。例えば、タイマーのスレッドを実装し、タイムアウトの時に通知されるべきメソッドを登録する、別なサーバと通信するスレッドを作り、データの読み込みが発生すれば、やはりそれを処理する別スレッドのメソッドを登録するなどの応用がある。

今の段階では synchronized 文については神経質にならなくてもよいが、より詳しい解説は「スレッドって何?〜mutex」を参照されたい。



copyright by K.Sugiura, 1996-2006