はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回から振る舞いに関するパターンに入ります。
最初に紹介するのはChain of Responsibility(責任の連鎖)パターンです。
解決したい課題
「1つの要求(リクエスト)を、複数の候補の中から適切な処理者(ハンドラー)に処理させたい」が、「どの処理者が処理すべきか」を、要求を送る側が事前に決め打ちしたくないという場面があります。
例えば、経費申請の承認フローを考えます。
金額が少額なら「係長」が承認し、一定額を超えると「部長」、さらに高額なら「役員」が承認する、というように、金額に応じて承認者が変わるとします。
これをif文で愚直に書くと、承認者の追加・順序変更のたびに、申請処理側のコードを修正する必要が出てきます。
Chain of Responsibilityパターンは、複数の処理者を鎖(チェーン)のようにつなぎ、要求を先頭の処理者から順番に渡していき、処理できる処理者が現れた時点で処理することで、この問題を解決します。
要求を送る側は、どの処理者が実際に処理するかを一切気にする必要がありません。
クラス構成
- Handler(ハンドラー): 要求を処理するメソッドと、次のHandlerへの参照を持つ、共通のインターフェース(または抽象クラス)
- ConcreteHandler(具体的なハンドラー): 実際の処理を行う、具体的なHandlerの実装。自分で処理できない場合は、次のHandlerに要求を渡す
Javaでの実装例
経費申請の承認フローを例に実装してみます。
Handler:承認者共通の抽象クラス
public abstract class Approver {
protected Approver next; // 次の承認者への参照(チェーンの次の輪)
public Approver setNext(Approver next) {
this.next = next;
return next; // メソッドチェーンでチェーンを組み立てやすくするため、nextを返す
}
public abstract void approve(ExpenseRequest request);
}
申請内容を表すクラス
public class ExpenseRequest {
private final String title;
private final int amount;
public ExpenseRequest(String title, int amount) {
this.title = title;
this.amount = amount;
}
public String getTitle() {
return title;
}
public int getAmount() {
return amount;
}
}
ConcreteHandler:具体的な承認者たち
public class TeamLeadApprover extends Approver {
@Override
public void approve(ExpenseRequest request) {
if (request.getAmount() <= 50_000) {
System.out.println("[係長] " + request.getTitle() + "(" + request.getAmount() + "円) を承認しました");
} else if (next != null) {
System.out.println("[係長] 自分の権限を超えるため、次の承認者へ回します");
next.approve(request);
}
}
}
public class ManagerApprover extends Approver {
@Override
public void approve(ExpenseRequest request) {
if (request.getAmount() <= 300_000) {
System.out.println("[部長] " + request.getTitle() + "(" + request.getAmount() + "円) を承認しました");
} else if (next != null) {
System.out.println("[部長] 自分の権限を超えるため、次の承認者へ回します");
next.approve(request);
}
}
}
public class ExecutiveApprover extends Approver {
@Override
public void approve(ExpenseRequest request) {
// 役員は上限なく承認できる想定
System.out.println("[役員] " + request.getTitle() + "(" + request.getAmount() + "円) を承認しました");
}
}
利用側のコード:チェーンの組み立てと実行
public class Main {
public static void main(String[] args) {
Approver teamLead = new TeamLeadApprover();
Approver manager = new ManagerApprover();
Approver executive = new ExecutiveApprover();
// チェーンを組み立てる: 係長 → 部長 → 役員
teamLead.setNext(manager).setNext(executive);
// 申請する側は、先頭(teamLead)にだけ要求を渡せばよい
teamLead.approve(new ExpenseRequest("文房具購入", 5_000));
System.out.println();
teamLead.approve(new ExpenseRequest("研修費用", 150_000));
System.out.println();
teamLead.approve(new ExpenseRequest("新規サーバー導入", 1_000_000));
}
}
実行結果:
[係長] 文房具購入(5000円) を承認しました
[係長] 自分の権限を超えるため、次の承認者へ回します
[部長] 研修費用(150000円) を承認しました
[係長] 自分の権限を超えるため、次の承認者へ回します
[部長] 自分の権限を超えるため、次の承認者へ回します
[役員] 新規サーバー導入(1000000円) を承認しました
申請する側のコードは、常にteamLead.approve(request)という同じ呼び出し方をしているだけです。
金額に応じてどの承認者が最終的に処理するかは、チェーンの内部でバケツリレーのように決まっていきます。
承認者の追加(例えば「係長」と「部長」の間に「課長」を挟む)や、承認金額の上限変更も、Approverの実装クラスとチェーンの組み立て部分だけを修正すればよく、申請側のコードには一切影響しません。
使用場面
- 複数の候補者(処理者)の中から、条件に応じて実際に処理する者を動的に決めたい場合
- 承認フロー、例外処理のハンドラーチェーン、イベントのバブリング(GUIにおけるクリックイベントの伝播)など
- ミドルウェア・フィルターチェーン(Webフレームワークにおけるリクエストの前処理・認証・ログ記録などを、複数の処理を順番に通していく仕組み)
- 「要求を処理する責任者」を、要求を送る側と処理する側で疎結合に保ちたい場合
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | 要求を処理すべき相手を、要求を送る側が事前に決め打ちしたくない |
| 実現方法 | 複数の処理者を鎖状につなぎ、処理できる処理者が現れるまで順番に要求を渡していく |
| メリット | 要求を送る側は、実際にどの処理者が処理するかを意識しなくてよい |
| 典型例 | 承認フロー、Webミドルウェアのフィルターチェーン、GUIのイベント伝播 |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


