- Chain of Responsibility の実装の比較-
C++ による実装

C++ による実装例だ。幾通りか見てみよう。「メンバ関数ポインタをリクエストに使う」と「メンバ関数テンプレートを使う」は、メールでソースコードをいただきました。ありがとうございました。

リクエストを数値で指定する

まずは、もっとも単純な例を。リクエストを整数値で与えて、それを判定してハンドラを呼び出す機構だ。ここでは、リクエストを処理するための共通クラス Handler を利用してみた。チェインの中のクラスは、Handler クラスを継承して、必要なハンドラをオーバーライドして使うんだ。

リクエスト定数

/* リクエストを表す定数 */
enum {
  HELP_REQUEST = 1, 
  PRINT_REQUEST = 2, 
  PREVIEW_REQUEST = 4, 
};

Handler クラス

/* チェインのための Handler クラス */
class Handler
{
private:
  /* 次の handler */
  Handler* _successor;
  /* 受付可能なリクエスト */
  int _requests;
public:
  /* コンストラクタ */
  Handler(Handler* successor, int requests) : 
    _successor(successor), _requests(requests) {}
  
  /* リクエストが受付可能かどうかのチェック */
  bool hasHandler(int request)
    { return request & _requests; }
  
  /* リクエストを処理する */
  void handleRequest(int request);
  
  /* リクエストのハンドラ */
  virtual void help() {}
  virtual void print() {}
  virtual void preview() {}
};

/* リクエストを処理する */
void Handler::handleRequest(int request)
{
  /* リクエストが受付可能かチェックする */
  if (hasHandler(request)) {
    /* ハンドラを呼び出す */
    switch (request) {
    case HELP_REQUEST: help(); break;
    case PRINT_REQUEST: print(); break;
    case PREVIEW_REQUEST: preview(); break;
    }
  }
  else {
    /* チェインをたぐって、 */
    /* 次の handler にリクエストを投げる */
    if (_successor) {
      _successor->handleRequest(request);
    }
  }
}

Button クラス

/* Button クラス */
class Button : public Handler
{
public:
  /* コンストラクタ */
  Button(Handler* successor, int requests) : 
    Handler(successor, requests) {}
  /* help ハンドラ */
  virtual void help();
};

/* help ハンドラ */
void Button::help()
{
  /* help リクエストの処理 */
}

Dialog クラス

/* Dialog クラス */
class Dialog : public Handler
{
public:
  /* コンストラクタ */
  Dialog(Handler* successor, int requests) : 
    Handler(successor, requests) {}
  /* print ハンドラ */
  virtual void print();
};

/* print ハンドラ */
void Dialog::print()
{
  /* print リクエストの処理 */
}

Application クラス

/* Application クラス */
class Application : public Handler
{
public:
  /* コンストラクタ */
  Application(Handler* successor, int requests) : 
    Handler(successor, requests) {}
  /* preview ハンドラ */
  virtual void preview();
};

/* preview ハンドラ */
void Application::preview()
{
  /* preview リクエストの処理 */
}

main()

int main(int argc, const char* argv[])
{
  /* それぞれのインスタンスを作る */
  Application app(NULL, PREVIEW_REQUEST);
  Dialog dialog(&app, PRINT_REQUEST);
  Button button(&dialog, HELP_REQUEST);
  
  /* help リクエストを処理する */
  button.handleRequest(HELP_REQUEST);
  /* print リクエストを処理する */
  button.handleRequest(PRINT_REQUEST);
  /* preview リクエストを処理する */
  button.handleRequest(PREVIEW_REQUEST);
}

とりあえず、これで望み通りの動作はする。しかしこの実装だと、ほかの言語と比べると、欠点が多いんだよね。

なんでこんなことになってしまうかというと、原因は 2 つ。1 つは、C++ ではメンバ関数の有無が調べられない。だから、勢いすべてのメンバ関数を実装、なんてことになってしまう。もう 1 つは、自分の知らない関数を呼び出すことができないこと。このため、Handler クラスはすべてのハンドラをあらかじめ知っておかなくてはならない。

メンバ関数ポインタをリクエストに使う

(実装ファイル:memberFuncPointer.cpp

ということで、次はメンバ関数ポインタを使ってみた例だ。リクエストとしてメンバ関数をポインタを使う。こんな風に。

Handler クラス

/* チェインのための Handler クラス */
class Handler
{
public:
  typedef void (Handler::*request_id)();

private:
  /* 次の handler */
  Handler* _successor;

public:
  /* コンストラクタ */
  Handler(Handler* successor) : 
    _successor(successor) {}
  
  /* リクエストを処理する */
  void handleRequest(request_id request);
  
  /* リクエストのハンドラ */
  virtual void help();
  virtual void print();
  virtual void preview();
};

リクエスト定数

/* リクエストを表す定数 */
const Handler::request_id 
  HELP_REQUEST    = &Handler::help;
const Handler::request_id 
  PRINT_REQUEST   = &Handler::print;
const Handler::request_id 
  PREVIEW_REQUEST = &Handler::preview;

このように、Handler クラスのハンドラのメンバ関数ポインタを、リクエストとして使うんだ。これを使うと、リクエストとハンドラを結びつける handlerRequest() は、次のようになる。ただしこの方法だと、それぞれのデフォルトハンドラを実装する必要が出てきてしまうんだ。その実装の中でチェインをたぐる動作をすることになる。たとえば、デフォルトの help ハンドラは次のような感じ。

Handler::handleRequest()

/* リクエストを処理する */
void Handler::handleRequest(request_id request)
{
  (this->*request)();
}

Handler::help()

/* デフォルトの help ハンドラ */
void Handler::help()
{
  if (_successor) {
    _successor->handleRequest(HELP_REQUEST);
  }
}

完全なソースコードは、ファイルを参照してくれ。この実装だと、さっきの問題点のうち「リクエストとハンドラの対応付けを、コードの中でやらなくてはいけない」という点は解消される。ただし、

という、新たな問題点が生じることになってしまう。さっきはリクエストを数値で与えたけど、今度はメンバ関数ポインタだからね。

メンバ関数テンプレートを使う

(実装ファイル:memberFuncTemplate.cpp

じゃあ、これはどうよ、ってんで出てきたのが、次の実装。これは、動的にリクエストとハンドラを増やすことと、同じ機能を実現することを目的に書かれたものだ。どうするかっていうと、handleRequest をあらゆるリクエストを受け付ける、メンバ関数テンプレートとして定義する。そして、渡されたタイプによって、呼び分ける handlerRequestHelper() っていう関数を作るんだ。この中で、ハンドラを検索する。

Handler::handleRquest(), Handler::handerRequestHelper()

/* リクエストを処理する */
template<typename T> 
void Handler::handleRequest(T request)
{
    handleRequestHelper(
      request, 
      int_to_type<is_same_type<request_id, T>::value>());
}

///request の型が void (Handler::*)() の場合
template<typename T> 
void Handler::handleRequestHelper(T request, int_to_type<true>)
{
    (this->*request)();
}

///request の型が void (Handler::*)() 以外の場合
template<typename T> 
void Handler::handleRequestHelper(T request, int_to_type<false>)
{
    /*
       注意). T が引数無しのメンバ関数ポインタ型( R (Obj::*)() )
            以外ならコンパイル不可
    */
    typename type_traits<T>::object_type* obj = 
    dynamic_cast<typename type_traits<T>::object_type*>(this);
    if(obj)
        (obj->*request)();
    else
        defaultHandler<T, const char*>(request, "Unknown");
}

あらたにハンドラを登録するには、次のマクロを使う。

リクエスト特性定義用マクロ

/* リクエスト特性定義用マクロ */
#define REGISTER_REQUEST_TRAITS(RESULT, HANDLER, REQUEST)                 \
template<>                                                                \
class Handler::RequestTraits<RESULT (HANDLER::*)(), &HANDLER::REQUEST>    \
{                                                                         \
public :                                                                  \
    typedef RESULT (HANDLER::*request_type)();                            \
    static const request_type           identifier;                       \
    static const char                   name[];                           \
};                                                                        \
const Handler::RequestTraits<RESULT (HANDLER::*)(),                       \
        &HANDLER::REQUEST>::request_type                                  \
Handler::RequestTraits<RESULT (HANDLER::*)(),                             \
        &HANDLER::REQUEST>::identifier = &HANDLER::REQUEST;           \
const char                                                                \
Handler::RequestTraits<RESULT (HANDLER::*)(),                             \
        &HANDLER::REQUEST>::name[]     = "" #REQUEST;

詳細は実装のファイルを見てくれ。正直な話、「よくやったな〜」という気持ちと、「ここまでやるか?」という気持ちが半々だ。こうなると、これが C++ の言語特性を生かした実装か、と言われてもちょっと微妙な感じも。


いろいろ見てきたけど、生粋の C++ 使いの人には不満を感じるところもあるかもしれない。「問題、問題っていうけど、そもそも動的なリクエストのハンドラの追加って意味あるの?」とか。まぁ、ちょっと待って。ここでの目的は、Chain of Responsibility の実装の議論ではなく、これを題材にした各言語の比較なので。他の言語での実装と見比べて、C++ の特徴を感じてください。


[Home] [Download] [Archives] [BBS] [Cocoa Programming Tips 1001] [Core Foundation の秘密] [Safari Developer News] [はじめてのブラウザのつくり方] [Sketch BP] [スクリーンセイバーを作ろう] [Objective-C 最適化] [Authorization API 完全理解] [Mac OS X Programming Books Review] [オブジェクト指向の言語比較論]

mailto: mkino@xd5.so-net.ne.jp