単一コンポーネントでは処理しきれない複数の判断ロジックを順次実行したいケースは、システム設計において頻繁に発生します。経費精算の承認フロー、ログフィルタリング、APIリクエストの前処理パイプラインなどが典型的な例です。このような「要求を順に渡していく」アーキテクチャを実現するのが、デザインパターンの一つであるチェーン・オブ・レスポンシビリティ(CoR)です。
このパターンでは、各処理ノードが自身の职责範囲内で条件を満たすか判定し、適合しない場合は次のノードへ依頼を委譲します。最終的に誰かが処理を行うか、チェーン末端でデフォルトのエラー処理として終了する仕組みです。形状は単純な連結リストに限らず、木構造や並列分岐にも適応可能です。
直感的実装における課題
承認権限が階層化したシステムを初歩的な実装で作成した場合、以下のようなコードになることが想定されます。
public class DirectApprovalFlow {
public static void main(String[] args) {
int budgetRequest = 1500;
if (budgetRequest <= 800) {
approveByJunior("Tanaka", budgetRequest);
} else if (budgetRequest <= 3500) {
approveByMiddle("Sato", budgetRequest);
} else if (budgetRequest <= 10000) {
approveByExecutive("Yamada", budgetRequest);
} else {
System.out.println("拒否:超過限度額のため");
}
}
private static void approveByJunior(String emp, int amt) { /* ... */ }
// 他_roleのメソッド定義...
}このアプローチは直ちに機能しますが、拡張性と保守性に深刻な欠陥を抱えます。新たな役職を追加したり、金額閾値を変更したりする場合、メインルーチンを直接改修する必要が生じます。これはオープン・クロースド原則に違反しており、分岐ロジックが肥大化し、デバッグとテストが困難になるリスクが高いです。
責任連鎖構造への変換
問題解決のため、各ハンドラが「自身での処理可否」と「次ノードへの委譲」を内部で完結させます。
抽象基底クラスと具象ハンドラを実装し、クライアント側からチェーン構築を分離します。
public abstract class ApprovalHandler {
protected String handlerId;
protected ApprovalHandler successor;
public ApprovalHandler(String id) {
this.handlerId = id;
}
// コマンドチェイン形式での登録サポート
public ApprovalHandler chainTo(ApprovalHandler next) {
this.successor = next;
return next;
}
// 核心処理エントリポイント(子クラスでの呼び出し順序破坏を防ぐ為final)
public final void processRequest(int amount) {
if (isWithinAuthority(amount)) {
executeApproval();
} else if (successor != null) {
delegateSuccessor(amount);
} else {
rejectRequest();
}
}
// 子クラスで実装すべき抽象メソッド群
protected abstract boolean isWithinAuthority(int amount);
protected abstract void executeApproval();
protected void delegateSuccessor(int amount) {
successor.processRequest(amount);
}
protected void rejectRequest() {
System.out.println(handlerId + ":上限超過により申請却下");
}
}具体ハンドラの実装例
各役職ごとに閾値と動作を定義します。注意点は、processRequest メソッドが final として定義されているため、子クラスが委譲フローを制御できず、確実にチェーンの進行を保証できる点です。
public class JuniorStaff extends ApprovalHandler {
public JuniorStaff(String name) { super(name); }
@Override protected boolean isWithinAuthority(int amount) { return amount <= 800; }
@Override protected void executeApproval() { System.out.println("[Junior] " + handlerId + " が承認しました。"); }
}
public class MiddleManager extends ApprovalHandler {
public MiddleManager(String name) { super(name); }
@Override protected boolean isWithinAuthority(int amount) { return amount <= 3500; }
@Override protected void executeApproval() { System.out.println("[Manager] " + handlerId + " が承認しました。"); }
}
public class ExecutiveCeo extends ApprovalHandler {
public ExecutiveCeo(String name) { super(name); }
@Override protected boolean isWithinAuthority(int amount) { return amount <= 10000; }
@Override protected void executeApproval() { System.out.println("[CEO] " + handlerId + " が承認しました。"); }
}クライアント側の利用パターン
呼び出し側は具体的な承認者を意識せず、チェーンの起点となるハンドラに対してリクエストを投げるだけで済みます。これにより送信部と処理部が完全に疎結合化され、呼び出しコードが非常に簡潔になります。
public class WorkflowRunner {
public static void main(String[] args) {
// 責任チェーンの構成
ApprovalHandler root = new JuniorStaff("Taro")
.chainTo(new MiddleManager("Hanako"))
.chainTo(new ExecutiveCeo("Kenji"));
// リクエストの投入(閾値変更による挙動確認)
root.processRequest(600);
root.processRequest(1500);
root.processRequest(7000);
root.processRequest(25000);
}
}設計上の利点と適用時の留意点
このパターンを採用することで、新規ノードの追加が既存コードへ影響を与えずに行えます。また、動的なチェーン再構築も可能になり、ビジネス要件の変化に対応しやすいアーキテクチャになります。
一方で、すべてのステップを通らなければいけない工業製品の組立ラインのような「強制逐次処理」には適していません。CoR は「条件適合時に早期停止・脱出」することを前提としています。適用際は、そのワークフローが「選択的委譲」なのか「全工程必須」なのかを明確にし、必要に応じてコマンドパターンやパイプラインパターンと組み合わせて検討する必要があります。