オブザーバーパターンエンジンの自作を検討しよう

ユーザーインタフェースつきのアプリケーションを作成する上で、オブザーバーパターンは必須だろう。あるボタンが押されたときに、「このクラスのあの値とあの値を更新して、あれを再計算して、こっちのウィンドウのここんとこの表示も更新して」、なんてやるときに、ボタンが押されたそのコールバックの中にそれを全て直接記述するのはあまりにも効率が悪いし、同じような機能の他のボタンについても全部それをやるのだろうか、プログラムの作り方としてこれでいいのだろうかと、不安な気持ちになってくる。
そこでオブザーバーパターンの登場なわけだ。オブザーバーパターンのイメージは、あるオブジェクトが「声をあげる」と、あらかじめ設定された全てのオブジェクトにそれが「聞こえる」というイメージだ。ボタンが押されたとこのコールバックの中では、「ボタンが押されましたよ」と声をあげるだけ。すると、その特定の「聞き手」たちが、それを受けて、「あのボタンが押されたので私はこれをしなくちゃ」「私は表示を更新してと」といった具合にアクションを起こす、という感じだ。さらに、声を上げるだけではなく、関連する情報を一緒に流したりということができるわけだ。(人間の世界だと声そのものが言葉となり情報を載せているわけであるが)
オブザーバーパターンを実現するために、世の中には、

  • Javaのイベント機構。(イベントソースと、イベントリスナーからなる。)
  • Qtのシグナルスロット(関数のインタフェースを解析みたいなことする。そこまでやらんでも、、)
  • Boost Signal(使ったことはない)
  • sigslot、他無数(ググったらあったっていうだけ)

などなど、いろいろなライブラリが存在する。.NETのイベントとデリゲートなんかもその類だろう。つまりオブザーバー・パターンを実現するためのしくみはたくさんある。
なのだが、別に他人が作ったライブラリなんて使わなくても自分で作ってしまえばそれで終わりなだけのしくみなのである。最も簡単なオブザーバーパターンを実現するためのしくみ、先ほど人間が声を上げるアナロジーを述べたが、、それは文字列を使うことだ。簡単な実装のしくみを考えてみよう。

class SendReceive
{
public:
  static void Send( string&, void* );
  virtual void Receive( string&, void* );
public:
  static vector< SendReceive* > m_sendreceive_vec;
};

つまり、ものすごく話を簡単にして言ってしまえば、こういうのがあればそれだけでかなりたいていの場合は十分なのである。
プログラムの任意の場所からコールされた、

SendReceive::Send( "help", this );
// thisにあたる部分は実際には何にでもなり得る。それは特定の情報を保持している構造体へのポインタかも知れないし、あるいはint、あるいはそれ自体が文字列かも知れない。いずれにしてもそこに何が入っているか、Sendの呼び出す者と、Receive()の実装者がわかっているので、適切な型にreinterpret_castして、利用されるのである。

によって、m_sendreceiveに登録されている、全てのSendReceiveオブジェクトのReceive()関数 が呼ばれるというしくみなのである。(つまりSend()の中にそういうコードを書いておけばいいだけだ!)
あとはSendReceiveを継承したクラスの個々のオブジェクトの中で、"help"という文字列に興味があるかないか、あればどんな処理をするのか、といったことは、各々のReceive()のオーバーライドの中でコードを記述すればよい。Send関数から通知を受けたい全てのクラスは、SendReceiveを継承し、m_sendreceive_vecに、己自身を追加すればよいだけだ。
しくみの骨格に関する説明はこんなところだ。これをベースに気の利いたメソッドや工夫をして、いろんなことを突き詰めて、例えば特定のオブジェクト群のみにSendしたりとか、しかもReceive()は、Receive()ではなくて任意の関数を呼べるようにしたり、もっとスピードや効率を高めたり、簡単に使えるようにしたり、いろいろ作りこんで行くと、世にあるシグナルスロット系のライブラリに行き着く。
以上の話でSendを呼ぶ側もしくはその実装がSubject、Sendから呼ばれるReceiveを実装する側、なおかつ己自身がm_sendreceive_vecに登録されている側がObserverもしくはリスナーということに、この場合はなる。
私はUIを持つアプリケーションをずっとMFCで書いてきているが、上記の

static vector< SendReceive* > m_sendreceive_vec;

の代わりに、

static multimap< string, SendReceive* > m_db;

なるものを用意した、もちろん上記説明で登場したSendReceiveよりは多少気の利いた、いろいろなメソッドを記述している。手製の簡単なオブザーバーパターンエンジンとして、イベントドリブンのアプリケーションの全てを記述し続けている。勿論Send先として特定のオブジェクト群を選ぶことはできないし、Receive()関数にあたるインタフェースも、任意ではなく固定だが、何ら遜色はないし、問題を感じたこともないし、十分にこと足りている。

後日記:
更に、

delegate void RxTx( String^, void* )
static map< string, RxTx > m_rxtx_db; // デリゲートだからmultimapでなくてmapでよい

などとしてやれば、C++/CLIのデリゲートを使った、上記アドバタイズ機構(即ち相手先のインスタンスを特定しない無差別文字列シグナリングをこう呼んでみたぞ)も実現可能だ。