DP-22: Template Method パターン
アルゴリズムの「骨組み(流れ)」をスーパークラスで定義し、具体的な処理をサブクラスに委ねるパターンです。 バッチ処理やデータ移行など「手順が共通で中身だけ変わる」処理に最適です。
Template Method パターンとは
複数のバッチ処理を作るとき、「データ読み込み → データ処理 → データ書き込み → 後処理」という手順は どのバッチでも共通です。しかし「何を読み込むか」「どう処理するか」はバッチごとに異なります。 Template Method パターンでは、この「共通の手順(テンプレート)」をスーパークラスの テンプレートメソッドとして定義し、「変わる部分」だけをサブクラスに実装させます。
final キーワードの役割
テンプレートメソッド(execute())にはfinal キーワードをつけるのが慣習です。final をつけることで 「処理の順序はスーパークラスが管理する」ことが明確になり、 サブクラスが誤って手順を変えてしまうことを防げます。
// final をつけることでサブクラスでのオーバーライドを禁止
public final void execute() {
readData(); // ← ここの順序は変えられない
processData();
writeData();
cleanup();
}Java 標準ライブラリや Java EE / Jakarta EE でも Template Method パターンは広く使われています。
Java 標準ライブラリの Template Method 実例
// 例1: javax.servlet.http.HttpServlet
// service() がテンプレートメソッド(HTTP メソッドで振り分ける)
// doGet() / doPost() をオーバーライドして具体的な処理を実装する
public class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// GET リクエストの処理を実装
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// POST リクエストの処理を実装
}
}
// 例2: JUnit 4 のテストライフサイクル
// テストフレームワークが setUp() → テストメソッド → tearDown() の順で呼ぶ
public class MyTest extends TestCase {
@Override
protected void setUp() {
// テスト前の準備(DBコネクション作成など)
}
@Override
protected void tearDown() {
// テスト後の後処理(DBコネクションのクローズなど)
}
}サンプルコード
Java 8 ではテンプレートメソッド(execute())に final をつけることで「処理の順序はスーパークラスが管理し、サブクラスは変更できない」ことを明示します。cleanup() はフックメソッドとしてデフォルト実装を持ち、必要なサブクラスだけオーバーライドします。
よくあるミス・注意点
⚠️ 抽象メソッドを増やしすぎてサブクラスの実装が大変になる
抽象メソッドが多すぎると、サブクラスを作るたびに全てのメソッドを実装しなければなりません。 「必須でない処理」は抽象メソッドではなく、空実装またはデフォルト処理を持つ 「フックメソッド(hook method)」として提供しましょう。 サンプルコードの cleanup() がフックメソッドの例です。
⚠️ テンプレートメソッドに final をつけ忘れる
final をつけ忘れると、 サブクラスが execute() をオーバーライドして 処理の順序を変えてしまう可能性があります。 Template Method パターンの意図は「手順はスーパークラスが守る」ことなので、 テンプレートメソッドには必ず final をつけましょう。
⚠️ サブクラス側で super.execute() を呼んでしまう
サブクラスで execute() をオーバーライドしてsuper.execute() を呼ぶと、 処理が二重に実行される場合があります。 テンプレートメソッドは呼び出す側(利用者)が直接呼ぶもので、 サブクラスからは抽象メソッド・フックメソッドだけをオーバーライドするのが正しい使い方です。
テストする観点
- テンプレートメソッドが
readData()→processData()→writeData()→cleanup()の順に呼び出されること - デフォルトの
cleanup()(フックメソッド)が正しく動作すること - サブクラスが
cleanup()をオーバーライドした場合に、オーバーライド後の実装が呼ばれること - Java 17 版で
execute()が正しいBatchResult(batchName・recordCount・success)を返すこと - 各サブクラス(UserMigrationBatch・OrderMigrationBatch)が期待通りのログを出力すること
- テンプレートメソッドが
finalであり、サブクラスでオーバーライドできないこと(コンパイルエラーで確認)