第56章:Chain of Responsibility ①:流れを“通す”設計🔗
ねらい 🎯✨
- 処理を「小さく分けて」「順番に流す」設計で、変更に強くする感覚をつかむよ🙂🔗
- 途中で止めたり(バリデーションで終了🚫)、差し替えたり(ログだけ追加📝)が“自然に”できる形を作れるようにするよ💪
- そして何より…「巨大if/switch」「巨大メソッド」を“増えにくい形”にするのがゴールだよ🧠🌸
到達目標 🏁✨
-
Chain of Responsibility(CoR)が解く困りごとを、自分の言葉で1分で説明できる🙂🗣️
-
「使う/使わない」の判断を、具体条件で言える(例:順序が大事・途中で止めたい・処理が増えがち…など)✅
-
“最小構成”のイメージが描ける🧩
- Request(依頼):流したいデータ
- Handler(処理係):小さな仕事
- Next(次の人):渡す先
手順 🧭🔗
1) まず「流したい処理」を“箇条書き”にする 📝
例:注文確定の流れ(シンプル版)🛒
- 入力チェック(Validation)✅
- 在庫チェック(Stock)📦
- 支払い(Payment)💳
- 通知(Notification)🔔
ここで重要なのは、順番がある&途中で止めたい可能性があることだよ🚦
2) CoRの2つの流し方を知る 🔗✨
A. “止める”型(クラシックCoR)🚧
- あるHandlerが「自分が処理できる!」なら処理して終了
- できなければ次へ➡️
B. “パイプライン”型(今どき実務でよく見る)🌊
- 各Handlerが「前処理→次へ→後処理」できる
- 途中で止める(次へ渡さない)もできる
- これが ASP.NET CoreのMiddleware の考え方に近いよ🌐✨(リクエストは“Request delegateの列”として順に呼ばれる)(Microsoft Learn)
この教材ではまず B(パイプライン型) をメインに掴むと超わかりやすいよ🙂🔗
3) “Request(流す箱)”を用意する 📦✨
注文処理なら、最低限こんなイメージでOK👇
- 注文ID
- エラー(あれば止める)
- 途中の結果(支払い済みフラグ等)
4) Handlerの形を“軽く”決める 🧩⚙️
実務でよく見る形は2つ:
-
Middleware風(nextを引数に持つ)
- 「次を呼ぶかどうか」をその場で決めやすい✨
-
Nextをプロパティ/コンストラクタで持つ(連結型)
- 直感的にチェーンを作れる🙂
どっちもOK。ここでは理解しやすい “連結型” をまず置くね🔗
5) 最小コードで「流れる」を体験する 🧪✨

using System;
using System.Threading;
using System.Threading.Tasks;
public sealed class OrderFlowContext
{
public required string OrderId { get; init; }
public bool IsValid { get; set; } = true;
public bool IsPaid { get; set; }
public string? Error { get; set; }
public void Fail(string message)
{
IsValid = false;
Error = message;
}
}
public interface IOrderFlowHandler
{
Task HandleAsync(OrderFlowContext ctx, CancellationToken ct);
}
public abstract class OrderFlowHandlerBase : IOrderFlowHandler
{
private readonly IOrderFlowHandler? _next;
protected OrderFlowHandlerBase(IOrderFlowHandler? next) => _next = next;
public async Task HandleAsync(OrderFlowContext ctx, CancellationToken ct)
{
// すでに失敗してたら、以降は流さない(早期終了)🚫
if (!ctx.IsValid) return;
await HandleCoreAsync(ctx, ct);
if (!ctx.IsValid) return;
if (_next is not null)
await _next.HandleAsync(ctx, ct);
}
protected abstract Task HandleCoreAsync(OrderFlowContext ctx, CancellationToken ct);
}
public sealed class ValidateOrderHandler : OrderFlowHandlerBase
{
public ValidateOrderHandler(IOrderFlowHandler? next) : base(next) { }
protected override Task HandleCoreAsync(OrderFlowContext ctx, CancellationToken ct)
{
if (string.IsNullOrWhiteSpace(ctx.OrderId))
ctx.Fail("OrderId が空だよ😵");
return Task.CompletedTask;
}
}
public sealed class PayHandler : OrderFlowHandlerBase
{
public PayHandler(IOrderFlowHandler? next) : base(next) { }
protected override Task HandleCoreAsync(OrderFlowContext ctx, CancellationToken ct)
{
// ここではダミーで「支払い成功」にしてみる💳✨
ctx.IsPaid = true;
return Task.CompletedTask;
}
}
public static class Demo
{
public static async Task RunAsync()
{
// Validate → Pay の順で流す🔗
IOrderFlowHandler flow =
new ValidateOrderHandler(
new PayHandler(next: null));
var ctx = new OrderFlowContext { OrderId = "ORD-001" };
await flow.HandleAsync(ctx, CancellationToken.None);
Console.WriteLine($"IsValid={ctx.IsValid}, IsPaid={ctx.IsPaid}, Error={ctx.Error}");
}
}
ポイントはここだよ👇🙂
- 呼び出し側(Demo)は、処理の中身を知らない(並べるだけ)✨
- 途中で
ctx.Fail(...)したら 後続が呼ばれない🚫 - Handlerは“小さい責務”に分割される🧩
6) 「実務の定番」に脳内で接続する 🌐📨
このパターン、.NET周りだとめちゃ身近👇
-
ASP.NET Core Middleware:リクエスト処理が“delegateの列”として順に呼ばれる(まさにチェーン)(Microsoft Learn)
- Middlewareを書くときは
RequestDelegateを受け取り、Invoke/InvokeAsyncで次へ渡す形が基本だよ(Microsoft Learn)
- Middlewareを書くときは
-
HttpClientのDelegatingHandler:
SendAsyncで前後処理して、base.SendAsyncで内側へ渡す(チェーン)(Microsoft Learn)- さらに
IHttpClientFactoryでもメッセージハンドラーを組み立てて使うのが定番になってるよ🧰(Microsoft Learn)
- さらに
そして最新の .NET は .NET 10(LTS) が公開されていて、ASP.NET Core も 10 系のドキュメントが整ってるよ📚✨(Microsoft for Developers) (LTS/STSのリズムも「毎年11月・偶数がLTS」って方針が明確だよ)(Microsoft)
よくある落とし穴 🕳️⚠️
- “何でも屋ハンドラ”が生まれる 😵
- 「検証も支払いもログも…全部ここ!」みたいになると終わる…
- 目安:Handlerの名前が「Process」「Execute」みたいに雑になったら危険🚨
- 順序依存が強くなりすぎる 🔁
- 「このHandlerは必ずあれより前」みたいなのが増えると読みづらい
- 対策:順序が大事な理由をコメント1行でも残す📝(後で消してOK)
- Contextが“ゴミ箱”化する 🗑️
- なんでも
ctxに詰め込むと、結局つらい - 対策:Contextに入れるのは「チェーン全体で共有したい最小限」だけ✨
- 例外/失敗のルールが曖昧 🌧️
- 「失敗は例外? ctx.Fail? 戻り値?」が混ざると地獄
- 対策:この章ではまず ctx.Failで止める に寄せて統一🙂
演習(10〜30分)🧪💖
お題:注文処理に「在庫チェック」を足して、失敗したら止める📦🚫
StockCheckHandlerを追加するOrderIdが"OUT-OF-STOCK"のときだけctx.Fail("在庫ないよ😢")- チェーンを
Validate → StockCheck → Payに並べ替える "OUT-OF-STOCK"で実行して、Payが走らないのを確認する✨
AI補助を使うなら、こんな感じで“範囲を狭く”お願いするのがコツだよ🤖✍️
- 「
OrderFlowHandlerBaseの流儀(Failで止める)に合わせてStockCheckHandlerだけ作って」 - 「余計な汎用フレームワーク化はしないで」←これ超大事😇
チェック ✅🌸
- 「処理を順に流して、途中で止められる」設計だと言える🙂🔗
- 呼び出し側が if/switchを持たず、並べるだけになってる✨
- Handlerが“小さな責務”になってる🧩
- 失敗時のルール(今回は
ctx.Fail)がブレてない🚫 - 追加の処理(例:ログ)を“差し込めそう”な気がする📝✨