概要
処理の全体的な流れを親クラスで固定し、個々のステップだけをサブクラスでカスタマイズする。Template Method パターンは、バッチ処理、データ移行、ETL パイプラインなど、手順は共通だが各ステップの中身が異なる場面で威力を発揮します。テンプレートメソッドを final にすることで、処理の順序を強制しつつ、abstract メソッドで必須のカスタマイズポイントを定義し、フックメソッドでオプションの拡張ポイントを提供します。この記事では、データ移行バッチ(読み込み → 処理 → 書き込み → 後処理)を題材に実装し、record でバッチ結果を返す方法、Java 21 の sealed interface で各ステップを型安全に定義する方法を確認します。
使いどころ
データ移行バッチの共通フロー(接続 → データ読込 → 変換 → 書込 → 後処理)を親クラスに定義し、移行元ごとにサブクラスでステップを実装する
帳票出力処理のヘッダー → 明細 → フッターの共通フローを固定し、帳票種別ごとに出力内容をカスタマイズする
テストの共通セットアップ → テスト実行 → 検証 → クリーンアップのフローを基底クラスに定義し、テストケースごとに内容を変える
コード例
public class TemplateMethodDemo {
record BatchResult(String batchName, int recordCount,
boolean success) {}
static abstract class DataMigrationBatch {
private final String batchName;
public DataMigrationBatch(String name) {
this.batchName = name;
}
public final BatchResult execute() {
System.out.println("=== " + batchName + " 開始 ===");
readData();
processData();
writeData();
cleanup();
System.out.println("=== " + batchName + " 終了 ===");
return new BatchResult(batchName,
getRecordCount(), true);
}
protected abstract void readData();
protected abstract void processData();
protected abstract void writeData();
protected int getRecordCount() { return 0; }
protected void cleanup() {
System.out.println("[後処理] デフォルト");
}
}
static class UserMigrationBatch extends DataMigrationBatch {
public UserMigrationBatch() { super("ユーザー移行"); }
@Override protected void readData() {
System.out.println("[読込] 旧DBから1000件");
}
@Override protected void processData() {
System.out.println("[処理] メール正規化");
}
@Override protected void writeData() {
System.out.println("[書込] 新DBに1000件");
}
@Override protected int getRecordCount() {
return 1000;
}
}
static class OrderMigrationBatch extends DataMigrationBatch {
public OrderMigrationBatch() { super("注文移行"); }
@Override protected void readData() {
System.out.println("[読込] CSVから5000件");
}
@Override protected void processData() {
System.out.println("[処理] 金額再計算");
}
@Override protected void writeData() {
System.out.println("[書込] 新DBに5000件");
}
@Override protected int getRecordCount() {
return 5000;
}
@Override protected void cleanup() {
System.out.println("[後処理] CSVアーカイブ");
}
}
public static void main(String[] args) {
var userResult = new UserMigrationBatch().execute();
System.out.println("結果: " + userResult);
System.out.println();
var orderResult = new OrderMigrationBatch().execute();
System.out.println("結果: " + orderResult);
}
}Version Coverage
record で BatchResult を定義し、テンプレートメソッドから処理結果をまとめて返せる。var で変数宣言も簡潔に。
// Java 17: record で処理結果をまとめて返す
record BatchResult(String batchName, int recordCount,
boolean success) {}
public final BatchResult execute() {
readData();
processData();
writeData();
cleanup();
var result = new BatchResult(
batchName, getRecordCount(), true);
return result;
}Library Comparison
注意点
テンプレートメソッドは final にして、サブクラスが処理の順序を変えられないようにすること。final がないと意図しないオーバーライドで処理フローが壊れる
フックメソッドのデフォルト実装が空の場合は、サブクラスがオーバーライドしなくても動く設計にすること。必須のカスタマイズポイントは abstract にする
サブクラスが増えすぎると継承階層が深くなる。Strategy パターンとの併用(処理ステップを Strategy で差し替え)も検討する
テンプレートメソッド内で例外が発生した場合のクリーンアップ処理を確実に行うこと。try-finally か try-with-resources を検討する
FAQ
Template Method は処理の骨格を固定して部分だけカスタマイズします。Strategy はアルゴリズム全体を差し替えます。骨格が共通なら Template Method、全く異なる処理なら Strategy が適しています。
デフォルト実装を持つメソッドで、サブクラスが必要に応じてオーバーライドします。abstract メソッドと異なり、オーバーライドは任意です。
サブクラスが処理の順序を変更できてしまいます。readData の前に writeData が呼ばれるなど、意図しない動作の原因になります。