勉強日記

チラ裏

GoF本 Chain of Responsibility

ねらい

  • リクエストの送信者と受信者との密結合をほぐす
    • 1つ以上のオブジェクトにリクエストの処理の機会を与える

モチベーション

  • 例) コンテキスト依存のヘルプ機能
    • UI上の任意の場所をクリックして、ヘルプを表示できるとする
  • ダイアログの中のボタンをクリックしたとする
    • ヘルプリクエストが送信される
    • ボタンにヘルプがあれば、ボタンについてのヘルプ表示
    • さもなくば、ボタンを含むダイアログについてのヘルプを表示
    • それもなければ、ダイアログを含むアプリケーションについてのヘルプを表示
  • ヘルプ表示リクエストがどのオブジェクトに受信されて処理されるかを明示的に決めない
  • リクエスト送信者と受信者とが疎結合化される

つかいどころ

  • 1つ以上のオブジェクトがリクエストを処理するかもしれない
    • 誰が処理するかあらかじめわからない
  • 受信者を明示的に決めずにリクエストを送信したい
  • リクエストを受信して処理するオブジェクトを動的に決めたい

登場人物

  • Handler
    • リクエストを処理するためのインタフェースを定義する
    • (オプショナル)「次のリクエスト処理候補」のリンクを定義する
      • ゲッタを用意しなくても、protectedメンバ変数でもいい
      • Compositeパターン等ですでにチェーンができていればそれを使っていい
  • ConcreteHandler
    • 処理する責務のあるリクエストを受信し処理する
    • 「次の人」(successor)にアクセスできる
    • そうでないリクエストはsuccessorに丸投げする
  • Client
    • リクエストを送信する

クライアントコードからの利用

  • Clientがリクエストを送信し、ConcreteHandlerが処理するまでリクエストは伝播する
    • だれも処理しないこともある

結果

  • オブジェクト間の結合が弱まる
    • リクエストの送信者や、リクエストを処理せずにsuccessorに丸投げするオブジェクトは、どのオブジェクトがリクエストを処理するかを知らずに済む
      • 「良しなに」処理されるであろう、ということだけを知っている
    • 結果、オブジェクト間の相互の結びつきが簡潔になる
      • 可能性のあるすべての受信者、ではなく、successorの参照1つだけ持っていればよい
  • オブジェクトに責務を割り振るうえで、柔軟性が高まる
    • 実行時にsuccessorのチェーンを変えたり追加したりすることで、リクエストの処理の責務を柔軟に変えられる
  • リクエストを誰かが受信・処理してくれる、という保証はない
    • ヘルプの例では、アプリケーションもリクエストをスルーしたら誰も処理しないことになる
    • successorのチェーンが途中で途切れていても同様

実装にあたり考えるべきこと

  • successorのチェーンの実装
    • Handlerに参照を新規追加する
      • ConcreteHandlerに持たせてもいい
    • 既存の参照を使う
      • Compositeパターンを使用している場合など
      • 空間の節約になる
      • 既存のオブジェクト構造と、successorのチェーンの構造とが一致しない場合、冗長なチェーンができることがある
        • ヘルプの例では、ボタンがlegendつきのfieldsetの中にある場合など
          • ダイアログ <>-- fieldset <>-- ボタン という構造
          • fieldsetにリクエストを送るのは冗長
  • successorにリクエストを丸投げする処理
    • Handler::HandleRequest(Request* request)のデフォルト実装はthis->_successor->HandleRequest(request)とし、適宜overrideする
  • リクエストの表現
    • Handler::HandleHelp() というようにハードコーディングする
      • Requestオブジェクト等は用意しない。メソッド呼び出しそのものがリクエス
      • 柔軟性に欠ける
    • Handler::HandleRequest(int request_code)というように、パラメータを受け取る
      • 柔軟
      • 分岐まみれになる
      • 型安全でない
    • Handler::HandleRequest(Request* request)というように、リクエストをオブジェクトにする
      • GoF本では、型フィールドでswitch-caseするやり方が提示されている
        • (所感)Visitorパターンのほうがいいと思う
      • Handlerに一塊の引数を渡せる
// GoF p.228をもとに少し補ったもの

enum RequestKind {
    REQUEST_NULL,
    REQUEST_HELP,
    // ....
};



void Handler::HandleRequest (Request* theRequest) {
    switch(theRequest->GetKind()) {
        case REQUEST_HELP:
            // 危険なダウンキャスト
            HandleHelp((HelpRequest*) theRequest);
            break;
        //...
    }
}

// virtual
RequestKind Request::GetKind() {
    return REQUEST_NULL; 
}

// override
RequestKind HelpRequest::GetKind() {
    return REQUEST_HELP; 
}
// ぼくはこうしたほうがいいと思う
// Visitor Pattern

void Handler::HandleRequest (Request* theRequest) {
    theRequest->HandledBy(this);
}


// virtual
void Request::HandledBy(Handler* handler) {
}

// override
void HelpRequest::HandledBy(Handler* handler) {
    handler->HandleHelp(this);
}

関連するパターン

  • Composite
    • 併用されること多し
    • 親ノードがsuccessorになる