#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 問のクイズで、 このパターンを身体に染み込ませよう。