listener & mediator



以下「やね本1」から引用します(^^;




リスナというのは、listener、“ラジオのリスナー”のように傾聴者のことを言うのですが、言ってみれば御用聞きで、コールバックするのに使います。たとえば、GUI的なボタン用のリスナは次のようになるでしょう。

枠-1
//  ボタンが押された時の通知用ハンドラ
class CGUIButtonEventListener {
public:
    //  ボタンの入力によって、以下のイベントが発生
    virtual void OnRBClick(){}  //  右ボタンクリック
    virtual void OnLBClick(){}  //  左ボタンクリック
    virtual void OnRBDown(){}   //  右ボタン押し下げ
    virtual void OnLBDown(){}   //  左ボタン押し下げ
    virtual void OnRBUp(){}     //  右ボタン押し上げ
    virtual void OnLBUp(){}     //  左ボタン押し上げ
          (以下略)


このクラスを派生させて、ボタンが押されたときの動作を書いていきます。たとえば、

枠-2
class CMyButtonListener : public CGUIButtonEventListener {
    virtual void OnRBDown(){
    }   //  右ボタン押し下げ時の動作を定義
};


と書きます。あとは、CMyButtonListenerのオブジェクトをnewして、CGUIButtonEventListenerのスマートポインタとして渡して、コールバックしてもらえば良いわけです。

って、ここでこんな疑問の声が聞こえてきそうです。「CMyButtonListenerにコールバックできることはわかったよ。しかし、僕が本当にコールバックしたいのは、CMyButtonListenerのメンバ関数ではないんだよ。たとえば、ボタンが押されたのを検知したら、このコールバックの設定したクラスのメンバ関数やメンバ変数にアクセスしたいんだ」と。

なるほど。ごもっとも。ごもっとも。これは、デザインパターンで言うmediator(仲裁者)パターンを使えば良いでしょう。詳しくは、推薦図書(⇒第8章)の文献4に譲るとして、実際のプログラムとしては、

list-1
template <class T>
class mediator {
public:
    mediator(T* pT=NULL) : m_pT(pT) {}
    void SetOutClass(T*pT) { m_pT = pT;}
    T& GetOutClass() const { return *m_pT;}
private:
    T*  m_pT;
};

#define outer GetOutClass()
    //  これを用意しておけば、
    //  outerというキーワードをあたかも外部クラスを指定する
    //  演算子のように使うことが出来る


というようになります。さきほどの場合ならば、このmediatorクラスと、GUIボタンのリスナCGUIButtonEventListenerとから多重継承して、CMyButtonListenerクラスを派生させます。

枠-3
class CMyButtonListener : public CGUIButtonEventListener , public mediator<CMyClass>{
    CMyButtonListener(CMyClass *p) : mediator<CMyClass>(p) {}

    virtual void OnRBDown(){
    //  右ボタン押し下げ時の動作を定義

        outer.member_of_myclass = true;
        //   ↑ CMyClassのメンバにアクセスしている!

    }
};


そして、このリスナのオブジェクトを、CMyClass内でどこかに渡したいとすれば

枠-4
    smart_ptr<CGUIButtonEventListener> p(new CMyButtonLintener(this));
    pButtonDrawClass -> SetActionListener( p );


というように渡します。言うまでもなく、class CMyClassは、

枠-5
    friend class mediator<CMyClass>;


とfriend設定しておいたほうが便利です。

読者のみなさんからは「mediatorだとか偉そうに言って、よく見たら、アクセスしたいオブジェクトへのポインタ渡してるだけかよ!」って言われそうですが、実際その通りです(笑) Javaの内部クラス(Inner Class)も、実際はこういう実装になっています。

というかC++では、この内部クラスという概念が無いので、かわりに上のようにしてリスナから、外部のクラスにアクセスしようと思えば、そのオブジェクトへのポインタが必要になるわけです。

このように、listener+mediatorというのは、ペアにして使用されることが多いのです。たとえば、あるクラスから派生させる目的として、「いくつかの純粋仮想関数をオーバーライドしたい」だけということが良くあります。

このような場合、オーバーライドしたい関数だけをリスナクラスとして外部に出してしまえば、派生をする必要が無くなります。 そのリスナクラスと、もとのクラスのmediatorとから多重継承をすれば、元のクラス(のpublicなメンバ)にアクセスできます。