DPDesign Pattern Quest
パターン一覧/Chain of Responsibility
#13 / 23
B振る舞いDIFF

責任の連鎖

Chain of Responsibility

リクエストのリレー走者。誰かが処理を引き受けるまで、次の人にバトンが渡されていく。

Intent · 目的

リクエストの送信者と受信者を切り離し、複数の処理者を順番に並べて『誰が処理するか』を実行時に決める。各処理者は『自分が処理できる/しない』を判断し、しなければ次の人に流す。

Motivation · 動機

Web リクエストを受けて返すまでに、認証チェック → レート制限 → ロギング → ビジネスロジック…と何段もの処理を順に通したい。これを利用側のメソッド1つに全部書くと、巨大な if が積み重なって読めなくなる。1責務=1ハンドラに切り分け、`auth → rate → log → core` のように繋げれば、追加・削除・順序変更が1行で済む。役所のたらい回し(笑)も、関数の世界では洗練された設計手法に見えてくる。

適用場面

  • 1リクエストに対して多段の処理を順次適用したい場面(Webミドルウェア、イベントフィルタ)。
  • 2処理者が動的に増減する(プラグイン的に挿入したい)。
  • 3送信側を、誰がどう処理するかから切り離したい場合。

概念図

サンプルコード

abstract class Handler {
  private next: Handler | null = null;
  setNext(h: Handler){ this.next = h; return h; }
  handle(req: string){
    if (!this.check(req)) return;
    this.next?.handle(req);
  }
  protected abstract check(req: string): boolean;
}

class Auth extends Handler {
  protected check(req: string){
    if (!req.includes("token")){ console.log("denied"); return false; }
    console.log("auth ok"); return true;
  }
}
class RateLimit extends Handler {
  protected check(){ console.log("rate ok"); return true; }
}
class Log extends Handler {
  protected check(req: string){ console.log("log:", req); return true; }
}

const chain = new Auth();
chain.setNext(new RateLimit()).setNext(new Log());
chain.handle("GET /api token=abc");
Pros · メリット
  • 送信側と受信側が疎結合になる。送信側は「とりあえず流す」だけ。
  • 処理者の追加・削除・順序変更がワンタッチ。新しいチェックを足すのが怖くなくなる。
  • 1ハンドラ=1責務でテストしやすい。
Cons · デメリット
  • 誰も拾わなかったリクエストが静かに闇に消える、というバグが起きやすい。終端処理を忘れずに。
  • チェーンが深くなると『どこで止まったか』を追うのが面倒。
  • ループ的なミス(A → B → A)を作るとスタックオーバーフローまっしぐら。

関連パターン

⚔ ready to test

理解度を確かめる時間だ

3 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →