委派パターンの概要
委派パターン(Delegate Pattern)は、あるオブジェクトが受け取った要求を、自分自身で処理するのではなく、別のオブジェクト(委託先)に転送して実行させる設計手法です。このパターンは、継承に頼らずにコードの再利用性を高めるためのアプローチとして知られています。
本質的には「特定のタスクを誰に割り当てるか」という意思決定に焦点を当てたパターンであり、一種の「静的プロキシ」の変形と見なされることもあります。プロキシパターンが対象へのアクセス制御やプロセスの管理を重視するのに対し、委派パターンは「結果の達成」と「適切な割り振り」を重視します。なお、GoFの23のデザインパターンには含まれませんが、多くのフレームワークで頻繁に利用される振る舞いに関するパターンです。
構成要素と役割
委派パターンは通常、以下の3つの役割で構成されます。
- 抽象インターフェース(Abstract Task): 実行すべきタスクを定義した抽象クラスまたはインターフェース。
- 具体的実行者(Concrete Worker): 実際にビジネスロジックを処理するクラス。
- 委派者(Delegator): 外部からのリクエストを受け取り、状況に応じて最適な実行者を選択して処理を委ねるクラス。
コードによる実装例
以下に、特定の業務コマンドに応じて適切なプロセッサにタスクを振り分ける簡単な例を示します。
// 1. 抽象インターフェース
public interface TaskProcessor {
void process(String command);
}
// 2. 具体的な実行クラスA
public class DataCleanupTask implements TaskProcessor {
@Override
public void process(String command) {
System.out.println("データクリーンアップを実行中: " + command);
}
}
// 3. 具体的な実行クラスB
public class ReportGenerationTask implements TaskProcessor {
@Override
public void process(String command) {
System.out.println("レポート作成を実行中: " + command);
}
}
// 4. 委派者(Delegator)
public class TaskDispatcher implements TaskProcessor {
private final Map<String, TaskProcessor> registry = new HashMap<>();
public TaskDispatcher() {
registry.put("cleanup", new DataCleanupTask());
registry.put("report", new ReportGenerationTask());
}
@Override
public void process(String command) {
// コマンドの内容に基づいて、実行対象を動的に選択
TaskProcessor processor = registry.get(command);
if (processor != null) {
processor.process(command);
} else {
System.out.println("対応するプロセッサが見つかりません。");
}
}
}
フレームワークやJDKにおける適用例
委派パターンは、多くの標準ライブラリやフレームワークのコアロジックで採用されています。
1. Javaのクラスロード機構(Parent Delegation Model)
JVMのClassLoaderは、クラスをロードする際に「親のクラスローダー」に処理を委派します。親がロードできない場合にのみ、自分自身でロードを試みる仕組みになっており、これによりシステムの整合性とセキュリティが保たれています。loadClass()メソッドの内部実装がその典型例です。
2. Java反射API(Reflection Method.invoke)
java.lang.reflect.Methodクラスのinvokeメソッドは、内部的にMethodAccessorオブジェクトに実際の実行処理を委派しています。実行回数などの状況に応じて、ネイティブ実装から動的生成されたバイトコード実装へと委派先が切り替わる仕組みになっています。
3. Spring MVCのDispatcherServlet
Spring WebにおけるDispatcherServletは、委派パターンの非常に代表的な例です。HTTPリクエストを受け取ると、自分自身でロジックを処理するのではなく、HandlerMappingを介して適切なコントローラーを特定し、処理を委ねます。
// Spring MVCの内部的な概念イメージ
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 1. ハンドラー(委託先)の決定
HandlerExecutionChain mappedHandler = getHandler(request);
// 2. アダプターを介して処理を委派
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
ModelAndView mv = ha.handle(request, response, mappedHandler.getHandler());
// 3. 結果の出力処理
processDispatchResult(request, response, mappedHandler, mv, dispatchException);
}
委派パターンの評価
利点
- 責務の分離: 呼び出し側は「誰がやるか」を意識せずに済み、処理の割り振りと実行のロジックを独立させることができます。
- 拡張性: 新しい実行クラスを追加する際、既存の実行ロジックを修正することなく、委派者の選択リストに追加するだけで対応が可能です。
欠点
- 複雑性の増加: 委派の階層が深くなりすぎると、コードの追跡が困難になり、パフォーマンスに微細な影響を与える可能性があります。
- 管理コスト: 委託先が多くなると、それらを管理するためのマッピングロジックが肥大化しやすくなります。