概要
Chain of Responsibility(責任の連鎖)パターンは、リクエストの送信者と受信者を疎結合にするための設計手法である。複数の処理オブジェクトをチェーン状に連結し、リクエストを順次渡していく。各オブジェクトはリクエストを自身で処理するか、チェーン上の次のオブジェクトに委譲する仕組みを持つ。
適用場面
- リクエストを処理可能なオブジェクトが複数存在し、実行時にどれが担当するかを動的に決定したい場合
- 明示的な受信者を指定せずに、複数のオブジェクトのいずれかにリクエストを処理させたい場合
- リクエストを処理するハンドラの集合を実行時に柔軟に変更・再構成したい場合
実装例:承認フローの構築
ここでは、経費精算の承認プロセスを例に実装する。金額に応じて、主任、部長、社長のいずれかが承認を行うチェーンを構築する。
1. リクエストモデルの定義
public class PurchaseOrder
{
public double Amount { get; }
public string Item { get; }
public PurchaseOrder(double amount, string item)
{
Amount = amount;
Item = item;
}
}
2. 抽象ハンドラの定義
次のハンドラを保持し、チェーンを構築するためのメソッドを提供する。処理メソッドは抽象メソッドとして定義する。
public abstract class Approver
{
protected Approver NextApprover { get; private set; }
// メソッドチェーンで次の承認者を設定できるようにする
public Approver RegisterNext(Approver next)
{
NextApprover = next;
return next;
}
public abstract void ProcessRequest(PurchaseOrder order);
}
3. 具象ハンドラの実装
各承認者は自己の承認権限(金額の閾値)を持ち、条件を満たさない場合は次の承認者へリクエストを回す。
// 主任(1万円以下を承認)
public class Manager : Approver
{
public override void ProcessRequest(PurchaseOrder order)
{
if (order.Amount <= 10000)
{
Console.WriteLine($"{GetType().Name}が承認: {order.Item} (金額: {order.Amount}円)");
}
else if (NextApprover != null)
{
NextApprover.ProcessRequest(order);
}
}
}
// 部長(10万円以下を承認)
public class Director : Approver
{
public override void ProcessRequest(PurchaseOrder order)
{
if (order.Amount <= 100000)
{
Console.WriteLine($"{GetType().Name}が承認: {order.Item} (金額: {order.Amount}円)");
}
else if (NextApprover != null)
{
NextApprover.ProcessRequest(order);
}
}
}
// 社長(全額承認)
public class President : Approver
{
public override void ProcessRequest(PurchaseOrder order)
{
Console.WriteLine($"{GetType().Name}が承認: {order.Item} (金額: {order.Amount}円)");
}
}
4. クライアントからの呼び出し
public class Application
{
public static void Main()
{
// チェーンの組み立て
Approver manager = new Manager();
var director = manager.RegisterNext(new Director());
director.RegisterNext(new President());
// テストデータの作成と実行
var orders = new List<PurchaseOrder>
{
new PurchaseOrder(4500, "文房具"),
new PurchaseOrder(65000, "ノートPC"),
new PurchaseOrder(350000, "サーバー機器")
};
foreach (var order in orders)
{
manager.ProcessRequest(order);
}
}
}