Chain of Responsibility @ C++

mkinoさんによる オブジェクト指向の言語比較論 中の、 Chain of Responsibility の C++ による実装 を自分なりに書き直してみました。

目的

記事のオリジナルの実装が批判されている点は、次の通り。

  1. リクエストの種類が増えるたびに、Handler クラスにハンドラを追加しなくてはいけない。
  2. リクエストとハンドラの対応付けを、コードの中でやらなくてはいけない。
  3. Handler を継承したクラスでは、すべてのハンドラの空実装が作られてしまう。

まず、メソッドでないもの(例えば文字列)を通して何らかのメソッドを得るためには、 C++では、絶対どこかに変換を行うコードが必要になってしまいます。 なので、2番目は無理。あきらめます。^^; 残りの二つを何とかしましょう。

以下の実装では、

  1. 基底である Handler クラスは、リクエストの種類を知る必要がない。 つまり、リクエストの種類が増えても、Handler クラスを変更する必要がない。
  2. リクエストの種類が増えてもすべてのオブジェクトを変更する必要がない。 ハンドラを実装するクラスに「ハンドラ関数本体」と 「どのリクエストをハンドルできるかの情報」を書き足すのみでよい。

となっています。

実装

// チェインのための Handler クラス
class Handler
{
private:
  // 次の handler
  Handler* successor_;

  // 'リクエスト'
  class Request {
    friend class Handler;
    virtual void sendTo( Handler* p )           const = 0;
    virtual bool sendableTo( const Handler* p ) const = 0;
  };
public:
  template <class Impl>
  class RequestGen : public Request {
    void sendTo( Handler* p ) const
      { dynamic_cast<Impl*>(p)->invoke(); }
    bool sendableTo( const Handler* p ) const
      { return dynamic_cast<const Impl*>(p)!=0; }
  };

  // コンストラクタ
  Handler( Handler* successor )
    : successor_(successor) {}

  // デストラクタ
  virtual ~Handler() {}

  // リクエストが受付可能かどうかのチェック
  bool hasHandler( const Request& req ) const
    { return req.sendableTo(this); }

  // リクエストを処理する
  void handleRequest( const Request& req )
  {
    if( req.sendableTo(this) ) {
      req.sendTo(this);
    }
    else if( successor_ ) {
      successor_->handleRequest(req);
    }
  }
};
// リクエスト 'HELP'
struct HelpHandler {
  void invoke() { help(); }
  virtual void help() = 0;
};
static Handler::RequestGen<HelpHandler> HELP_REQUEST;

// リクエスト 'PRINT'
struct PrintHandler {
  void invoke() { print(); }
  virtual void print() = 0;
};
static Handler::RequestGen<PrintHandler> PRINT_REQUEST;

// リクエスト 'PREVIEW'
struct PreviewHandler {
  void invoke() { preview(); }
  virtual void preview() = 0;
};
static Handler::RequestGen<PreviewHandler> PREVIEW_REQUEST;
// Button クラス
class Button : public Handler, public HelpHandler
{
public:
  Button( Handler* suc ) : Handler(suc) {}
  void help() {
    cout << "ButtonHelp" << endl;
  }
};
// Application クラス
class Application : public Handler, public PreviewHandler
{
public:
  Application( Handler* suc ) : Handler(suc) {}
  void preview() {
    cout << "AppPreview" << endl;
  }
};
// Dialog クラス
class Dialog : public Handler, public PrintHandler
{
public:
  Dialog( Handler* suc ) : Handler(suc) {}
  void print() {
    cout << "DialogPrint" << endl;
  }
};
int main()
{
  Application app(0);
  Dialog dialog(&app);
  Button button(&dialog);

  button.handleRequest(HELP_REQUEST);
  button.handleRequest(PRINT_REQUEST);
  button.handleRequest(PREVIEW_REQUEST);
}

検討

presented by k.inaba under NYSDL.