はじめに
本記事は、GoFデザインパターン解説シリーズの1つです。
今回は、振る舞いに関するパターンの中のTemplate Method(テンプレートメソッド)パターンを解説します。
前回解説したStrategyパターンと似た目的を持ちながら、継承をベースにしている点が特徴のパターンです。
解決したい課題
「処理全体の大まかな流れは同じだが、一部のステップだけが処理内容によって異なる」という場面があります。
例えば、「データを読み込み→処理し→出力する」という一連のバッチ処理があり、対象データがCSVファイルの場合とデータベースの場合とで、「読み込み」「出力」の具体的な処理内容だけが異なるとします。
これを何も考えずに実装すると、CsvBatchProcessorとDatabaseBatchProcessorのような別々のクラスに、「処理全体の流れ」を表すコードまで重複してコピーされてしまうことになりがちです。
全体の流れに変更(例えば「処理」と「出力」の間に「検証」ステップを追加する)が必要になった場合、すべての重複箇所を漏れなく修正しなければなりません。
Template Methodパターンは、「処理全体の流れ(骨格)」を親クラスに1箇所だけ定義し、変化する部分だけをサブクラスに実装させることで、この問題を解決します。
クラス構成
- AbstractClass(抽象クラス): 処理全体の流れ(テンプレートメソッド)を定義する抽象クラス。個別のステップの一部は、サブクラスに実装を委ねる抽象メソッドとして宣言する
- ConcreteClass(具体的なクラス): AbstractClassを継承し、抽象メソッドの部分だけを具体的に実装するクラス
Javaでの実装例
先ほどのバッチ処理の例で実装してみます。
AbstractClass:処理全体の流れを定義する抽象クラス
public abstract class BatchProcessor {
// これがテンプレートメソッド。全体の処理の流れ(骨格)を1箇所に定義し、finalで変更を禁止する
public final void execute() {
System.out.println("=== バッチ処理開始 ===");
Object data = loadData(); // サブクラスに委ねる
Object processed = processData(data); // サブクラスに委ねる
validate(processed); // 共通処理(サブクラスでは変更不可)
outputData(processed); // サブクラスに委ねる
System.out.println("=== バッチ処理完了 ===");
}
protected abstract Object loadData();
protected abstract Object processData(Object data);
protected abstract void outputData(Object data);
// 共通のステップは、親クラスに実装を持たせることもできる(サブクラスでの上書き不要)
private void validate(Object data) {
System.out.println("[共通処理] データを検証しました");
}
}
ConcreteClass:CSVファイルを扱う場合の実装
public class CsvBatchProcessor extends BatchProcessor {
@Override
protected Object loadData() {
System.out.println("[CSV] ファイルからデータを読み込みました");
return "csv-data";
}
@Override
protected Object processData(Object data) {
System.out.println("[CSV] データを加工しました: " + data);
return "processed-" + data;
}
@Override
protected void outputData(Object data) {
System.out.println("[CSV] 加工済みデータをファイルに出力しました: " + data);
}
}
ConcreteClass:データベースを扱う場合の実装
public class DatabaseBatchProcessor extends BatchProcessor {
@Override
protected Object loadData() {
System.out.println("[DB] データベースからデータを読み込みました");
return "db-data";
}
@Override
protected Object processData(Object data) {
System.out.println("[DB] データを加工しました: " + data);
return "processed-" + data;
}
@Override
protected void outputData(Object data) {
System.out.println("[DB] 加工済みデータをデータベースに書き戻しました: " + data);
}
}
利用側のコード
public class Main {
public static void main(String[] args) {
BatchProcessor csvProcessor = new CsvBatchProcessor();
csvProcessor.execute();
System.out.println();
BatchProcessor dbProcessor = new DatabaseBatchProcessor();
dbProcessor.execute();
}
}
実行結果:
=== バッチ処理開始 ===
[CSV] ファイルからデータを読み込みました
[CSV] データを加工しました: csv-data
[共通処理] データを検証しました
[CSV] 加工済みデータをファイルに出力しました: processed-csv-data
=== バッチ処理完了 ===
=== バッチ処理開始 ===
[DB] データベースからデータを読み込みました
[DB] データを加工しました: db-data
[共通処理] データを検証しました
[DB] 加工済みデータをデータベースに書き戻しました: processed-db-data
=== バッチ処理完了 ===
処理全体の流れ(「読み込み→加工→検証→出力」という順序)は、BatchProcessor#execute()という1箇所のみに定義されており、final修飾子によってサブクラスでの上書きも禁止されています。
CsvBatchProcessorとDatabaseBatchProcessorは、それぞれ異なるべき「読み込み方法」「加工方法」「出力方法」の3つのメソッドだけを実装しています。
今後、全体の流れに新しいステップを追加したくなった場合も、execute()を1箇所修正するだけで、すべてのサブクラスに反映されます。
Strategyパターンとの違い
前回解説したStrategyパターンと、Template Methodパターンは、どちらも「アルゴリズムの一部を切り替え可能にする」という目的では共通していますが、実現方法が異なります。
| 観点 | Template Method | Strategy |
|---|---|---|
| 実現方法 | 継承(サブクラスでメソッドをオーバーライド) | コンポジション(戦略オブジェクトを外部から注入) |
| 変更の柔軟性 | コンパイル時に決まる(実行時の切り替えは基本不可) | 実行時に自由に切り替え可能 |
| クラス間の結合度 | 親クラスとサブクラスが密結合になりやすい | Contextと戦略オブジェクトは疎結合 |
「継承よりコンポジションを優先せよ」という設計原則の観点から、近年は同じ課題に対してStrategyパターンが好まれる傾向もありますが、「処理の骨格を1箇所に固定し、変更を防ぎたい」という意図が明確な場合は、Template Methodパターンが今でも有効です。
使用場面
- 複数のクラスで、処理の大まかな流れ(骨格)は共通だが、一部のステップだけが異なる場合
- フレームワークが処理の骨格を提供し、利用者側に一部の処理だけをカスタマイズさせたい場合(多くのフレームワークの拡張ポイント設計で使われる)
- 処理順序を厳密に固定し、サブクラス側で処理順序自体を変更できないようにしたい場合
まとめ
| 項目 | 内容 |
|---|---|
| 解決する課題 | 処理全体の流れは共通だが、一部のステップだけが異なる処理の重複を防ぎたい |
| 実現方法 | 処理全体の流れを親クラスのテンプレートメソッドに定義し、変化する部分だけをサブクラスに実装させる |
| メリット | 全体の流れの変更が1箇所で完結し、サブクラスは差分のみ実装すればよい |
| Strategyとの違い | Template Methodは継承ベース(コンパイル時固定)、Strategyはコンポジションベース(実行時切り替え可能) |
おすすめ書籍
GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。
Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]
※本リンクはアフィリエイトリンクを含みます。


