テンプレートメソッドパターンは、処理の手順を親クラスに固定し、変わる部分だけをサブクラスに書かせるGoFの振る舞いパターンです。正体は「穴あきの処理フロー」だと考えると早い。この記事では、Python 3.14.6とPHP 8.5.8で実際に動かしたコードで構造を示し、Java標準ライブラリのjava.io.InputStreamが抱える性能上の代償、SpringのJdbcTemplateがこのパターンに当たるのかまで、公式ドキュメントの記述をもとに判断します。
まとめ:テンプレートメソッドパターンの要点
- 手順を固定するテンプレートメソッド、実装必須の抽象メソッド、既定実装を持つフックメソッドの3要素で構成する
- PHPの
finalはテンプレートメソッドの上書きをFatal errorで止めるが、Pythonの@finalは型チェッカー向けの表明で実行時には素通りする - Java標準の
java.io.InputStreamは典型例。read()を1本書けば全メソッドが動く代わりに、既定実装は1バイトずつ読む - SpringのJdbcTemplateは継承ではなくコールバックで可変部を差し込むため、GoFのテンプレートメソッドとは差し込み方が異なる
- 手順が本当に不変で、差分が2〜3箇所に収まるときだけ使う。可変部を実行時に切り替えたいならStrategyパターンを選ぶ
以下では、この5点の根拠を実行結果と公式ドキュメントの記述で順に確認します。
テンプレートメソッドパターンとは:処理の骨組みを親クラスに固定する振る舞いパターン
テンプレートメソッド・抽象メソッド・フックメソッドの3要素
親クラスに、処理の順番だけを書いたメソッドを1本用意します。これがテンプレートメソッドです。その中から呼ばれる個々のステップのうち、サブクラスごとに必ず変わるものを抽象メソッドとして宣言し、たいていは共通だが時々変えたいものをフックメソッドとして既定実装つきで置きます。
帳票の生成を例にとると、「読み込む → 不正な行を除く → 出力形式に整える」という順番は変わりません。変わるのは読み込み元(CSVかJSONか)と出力形式です。順番をテンプレートメソッドに閉じ込め、読み込みと出力を抽象メソッドに、除外条件をフックメソッドにする。この配分がパターンの設計判断そのものです。
| 役割 | 親クラスでの書き方 | サブクラスでの扱い |
|---|---|---|
| テンプレートメソッド | 手順だけを実装 | 上書き禁止 |
| 抽象メソッド | 宣言のみ | 実装が必須 |
| フックメソッド | 既定の実装つき | 必要なときだけ上書き |
抽象メソッドとフックメソッドの線引きを誤ると破綻します。「サブクラスが必ず答えを持っている問い」だけを抽象メソッドにしてください。既定値が決められる時点で、それは抽象メソッドではなくフックメソッドです。
GoF23パターンでの位置づけと「テンプレートパターン」という呼び名
出典はGamma・Helm・Johnson・Vlissidesによる書籍『Design Patterns: Elements of Reusable Object-Oriented Software』です。出版社Pearson(InformIT)の書誌ページによれば、同書がカタログ化したパターンは23種類で、Creational(生成)・Structural(構造)・Behavioral(振る舞い)の3章に分かれています。テンプレートメソッドが属するのは振る舞いに関するパターンです。GoF23種の全体像はGoFのデザインパターンとは?23種類の一覧と代表パターンをコードで解説で確認できます。
検索では「テンプレートパターン」「テンプレートメソッド」「Template Methodパターン」と表記が割れますが、いずれも同じパターンを指します。正式名は Template Method です。単に「テンプレート」と書くとテンプレートエンジンやHTMLテンプレートと混同されるため、実務の設計ドキュメントでは Template Method か「テンプレートメソッドパターン」まで書き切るほうが誤解を生みません。
Pythonでのテンプレートメソッド実装:abc.ABCとフックの上書き
Pythonには抽象メソッドを表す構文キーワードが無いため、標準ライブラリのabcモジュールを使います。ABCを継承し、実装必須のメソッドに@abstractmethodを付けます。
import csv, io
from abc import ABC, abstractmethod
from typing import final
class ReportPipeline(ABC):
@final
def run(self, csv_text): # テンプレートメソッド:手順を固定
rows = [r for r in self.load(csv_text) if self.is_valid(r)]
return self.render(rows)
@abstractmethod
def load(self, csv_text): ... # 抽象メソッド:実装必須
@abstractmethod
def render(self, rows): ...
def is_valid(self, row): # フックメソッド:既定の実装を持つ
return bool(row.get("name"))
class CsvReport(ReportPipeline):
def load(self, csv_text):
return list(csv.DictReader(io.StringIO(csv_text)))
def render(self, rows):
return " / ".join(r["name"] for r in rows)
class DevOnlyReport(CsvReport):
def is_valid(self, row): # フックだけ差し替える
return bool(row.get("name")) and row.get("dept") == "dev"
src = "name,dept\nsato,dev\n,sales\nkato,sales\n"
print(CsvReport().run(src))
print(DevOnlyReport().run(src))
Python 3.14.6で実行した結果です。
sato / kato
sato
DevOnlyReportが書き換えたのはis_validの3行だけで、読み込みも出力もCsvReportのものがそのまま動いています。フックメソッドの差し替えだけで振る舞いが変わる、という部分がテンプレートメソッドパターンの実利です。
@abstractmethodには実装漏れを起動時に落とす効果があります。loadとrenderを実装しないままインスタンス化すると、同じ環境で次の例外になりました。
TypeError: Can't instantiate abstract class ReportPipeline without an implementation for abstract methods 'load', 'render'
ここで注意したいのが@finalです。テンプレートメソッドは手順を固定するのが役目なので、サブクラスに上書きされては困ります。ところが実際にrunを上書きしたサブクラスを作って呼び出すと、例外は出ず上書き後の実装が動きました。PEP 591も、実行時に継承や上書きを禁止する案を検討したうえで「Nothing else in typing does any runtime enforcement, though, so final will not either.」と述べ、実行時強制を持ち込まない方針を示しています。Pythonでは@finalはmypy等の静的チェックに効く表明であり、CIに型チェックを組み込んでいないプロジェクトでは骨組みを守れないと理解しておいてください。
PHPでのテンプレートメソッド実装:finalによる骨組みの保護
PHPはabstractとfinalの両方を言語仕様として持つため、テンプレートメソッドパターンの意図をそのままコードに書けます。
<?php
abstract class ReportPipeline {
final public function run(string $csv): string {
$rows = array_filter($this->load($csv), fn($r) => $this->isValid($r));
return $this->render($rows);
}
abstract protected function load(string $csv): array;
abstract protected function render(array $rows): string;
protected function isValid(array $row): bool { return $row['name'] !== ''; }
}
class CsvReport extends ReportPipeline {
protected function load(string $csv): array {
$lines = explode("\n", trim($csv));
$head = explode(',', array_shift($lines));
return array_map(fn($l) => array_combine($head, explode(',', $l)), $lines);
}
protected function render(array $rows): string {
return implode(' / ', array_column($rows, 'name'));
}
}
$src = "name,dept\nsato,dev\n,sales\nkato,sales";
echo (new CsvReport())->run($src), PHP_EOL;
try { new ReportPipeline(); } catch (Error $e) { echo 'Error: ', $e->getMessage(), PHP_EOL; }
PHP 8.5.8での実行結果です。
sato / kato
Error: Cannot instantiate abstract class ReportPipeline
抽象メソッドをprotectedにしている点に意味があります。loadやrenderは部品であって、外部から単体で呼ばれるべきものではありません。公開するのはテンプレートメソッドのrunだけに絞ると、呼び出し側から見た契約が1本になります。
Pythonとの差が出るのがfinalです。runを上書きしたサブクラスを定義したファイルを実行すると、処理が始まる前にコンパイル時エラーで止まりました。
PHP Fatal error: Cannot override final method ReportPipeline::run() in ... on line 3
Stack trace:
#0 {main}
骨組みを言語機能で守れるという点で、PHPやJavaはテンプレートメソッドパターンと相性が良い。Pythonで同じ強度を求めるなら、静的型チェックをCIの必須ジョブにするしかありません。
標準ライブラリの実例:java.io.InputStreamとPython unittest
java.io.InputStream:read()を1本書けば動く代償
Javaのjava.io.InputStreamは、教科書的な例より説得力のある実例です。Java SE 21以降のJavadoc(Java SE 25でも記述は同じ)によれば、引数なしのread()は抽象メソッドで、サブクラスはこれだけを実装すればread(byte[])やread(byte[], int, int)も含めてストリームとして機能します。ここまでは、抽象メソッド1本に絞り込んだテンプレートメソッドの利点そのものです。
代償は同じJavadocに書かれています。read(byte[] b, int off, int len)の説明には「The read(b, off, len) method for class InputStream simply calls the method read() repeatedly.」とあり、読み出すバイト数についても「The number of bytes read is, at most, equal to len.」と規定されています。既定実装は1バイトずつの読み出しを最大len回繰り返すだけで、まとめ読みの最適化は一切していません。そしてJavadocは「Subclasses are encouraged to provide a more efficient implementation of this method.」と、サブクラス側での効率的な実装を明示的に促しています。
この一文が実務上の判断につながります。独自のInputStreamを書くとき、read()だけ実装して満足すると、1バイトごとにシステムコールやネットワーク往復が発生する実装ができあがります。まとめ読みが可能なソースを扱うなら、Javadocの推奨どおりread(byte[], int, int)も併せてオーバーライドする。利用側ではBufferedInputStreamで包む。テンプレートメソッドが「1本実装すれば動く」という手軽さを提供するとき、性能の責任は既定実装ではなく設計者側に残ります。
unittest.TestCase:setUpとtearDownはフックメソッド
Pythonのunittestも同じ構造です。テストの実行手順はTestCase.run()が固定し、前処理のsetUpと後処理のtearDownがフックメソッドとして開放されています。既定実装は何もしないため、必要なテストだけが上書きします。
公式ドキュメントはtearDownについて「If setUp() succeeded, tearDown() will be run whether the test method succeeded or not.」、setUpの失敗については「If the setUp() method raises an exception while the test is running, the framework will consider the test to have suffered an error, and the test method will not be executed.」と説明しています。Python 3.14.6で3パターンのテストを流したところ、テスト失敗時はsetUp・テスト本体・tearDownの順に実行され、setUpが例外を投げたケースではtearDownの出力が現れませんでした。
後片付けの前提が変わります。setUpで複数のリソースを順に確保していると、途中で失敗したときにtearDownが呼ばれず、確保済みのリソースが取り残される。addCleanupで確保のたびに解放処理を登録しておけば、この穴を塞げます。フックメソッドは「必ず呼ばれる」わけではない、というのが標準ライブラリの実装から読み取れる注意点です。
SpringのJdbcTemplateとの関係:継承ではなくコールバックによる差し込み
クラス名にTemplateと付くため、SpringのJdbcTemplateをテンプレートメソッドパターンの実例として挙げる解説が目立ちます。公式ドキュメントの記述に照らすと、この対応づけは厳密ではありません。
Spring Frameworkリファレンスのデータアクセス章は、JdbcTemplateについて「handles the creation and release of resources」「performs the basic tasks of the core JDBC workflow (such as statement creation and execution), leaving application code to provide SQL and extract results」と説明します。固定手順をフレームワークが持ち、可変部をアプリケーションが埋める——ここまでは確かに同じ発想です。
異なるのは埋め方です。同じ章は「you need only to implement callback interfaces, giving them a clearly defined contract」と述べ、PreparedStatementCreator・CallableStatementCreator・RowCallbackHandlerといったコールバックインタフェースを挙げています。可変部を担うのはJdbcTemplateを継承したサブクラスではなく、引数として渡されるオブジェクトです。公式ドキュメントが案内する拡張点はコールバックインタフェースの実装であり、JdbcTemplateの継承には触れていません。
GoFのテンプレートメソッドが要求するのは、可変部をサブクラスに委ねることです。委譲で埋めるJdbcTemplateは、この条件を満たしません。とはいえ固定手順を肩代わりして可変部だけを外から受け取るという発想は共通しており、「テンプレート」という呼び名自体は自然なものです。GoFの分類名として使うと、拡張点が継承なのか委譲なのかという設計上いちばん効く差が見えなくなる。レビューで問われたときは、発想は同じで差し込み方が違うと説明できれば十分です。当該章に「template method」という語が出てこないことも、クラス名だけではパターンを判断できない例として覚えておいてください。
Strategyパターンとの使い分け:継承で差し込むか、委譲で差し込むか
判断軸は、可変部を切り替えるタイミングです。
| 観点 | テンプレートメソッド | Strategy |
|---|---|---|
| 可変部の埋め方 | 継承・オーバーライド | 委譲・オブジェクト注入 |
| 切り替えのタイミング | クラス定義時 | 実行時に差し替え可 |
| 組み合わせ数 | 可変点の数だけ乗算 | 可変点ごとに独立 |
| 手順の共有 | 親クラスに1本だけ持つ | 呼び出し側が手順を持つ |
可変点が1つで、処理の順番そのものを共有したいならテンプレートメソッドが簡潔です。可変点が2つ以上になった瞬間に評価は逆転します。読み込み形式3種と出力形式3種を継承で表現すると9クラスが必要ですが、委譲なら3+3の6クラスで済み、実行時の組み合わせも自由です。委譲側の具体的な書き方はStrategyパターンとは?PHP・Java・TypeScriptの実装例と使いどころにPHP・Java・TypeScriptの実装例があります。
テンプレートメソッドパターンのデメリットと採用を見送るべき場面
このパターンは継承を前提にしており、継承の弱点をそのまま引き継ぎます。次のいずれかに当てはまるなら採用しないでください。
手順そのものが変わる可能性がある場合。テンプレートメソッドが固定するのはステップの順番です。「あるサブクラスだけ検証を2回走らせたい」「別のサブクラスでは整形を飛ばしたい」という要求が出た時点で、親クラスに条件分岐が増えていきます。分岐を吸収するためのフックを足し続けた親クラスは、どのサブクラスがどのフックを上書きするのか追えなくなる。可変なのが手順なら、手順自体を組み立て直せる構成に変えるべきです。
可変点が3つ以上ある場合。前節のとおり、可変点の数だけ組み合わせがクラス数として跳ね返ります。3つの可変点にそれぞれ3つの選択肢があれば27クラスです。ここは迷わずStrategyやファクトリパターンとは?Factoryパターンの仕組み・使いどころとJava/Pythonの実装例で扱う生成の分離に寄せてください。
親クラスを自分たちで保守できない場合。外部ライブラリの抽象クラスを継承してフックを埋める設計は、ライブラリ側がテンプレートメソッドの中身を変えた瞬間に壊れます。呼び出し順やフックの呼ばれる回数は公開APIの一部として明示されていないことが多く、マイナーバージョンアップで挙動が変わっても仕様違反とは言えません。外部の抽象クラスに依存するなら、自分たちのコードとの間にアダプタを1枚挟んでおきます。
逆に言えば、手順が仕様として確定していて、変わるのが1〜2箇所に限られ、親クラスを自分たちで持っているなら、テンプレートメソッドは最も安価な選択肢です。継承1段で済み、追加のインタフェース定義も注入の配線も要りません。
よくある質問
テンプレートメソッドパターンのメリットは?
共通の処理手順を親クラス1箇所に集約できるため、手順の修正がすべてのサブクラスへ一度に反映されます。サブクラス側が書くのは差分だけです。実装漏れも言語機能が拾い、Pythonなら@abstractmethodの未実装がインスタンス化時のTypeError、PHPやJavaならコンパイル時のエラーとして落ちます。コピー&ペーストによる共通化との決定的な差は、手順を守らせる強制力が言語機能で担保される点にあります。
Pythonでテンプレートメソッドパターンはどう実装しますか?
abc.ABCを継承した親クラスを作り、手順を書いたテンプレートメソッドを1本置きます。サブクラスで必ず実装させたいステップには@abstractmethodを付け、既定実装を持たせたいステップは通常のメソッドとして定義してフックにします。注意が必要なのはtyping.finalです。PEP 591のとおり実行時の強制は無いため、mypyなどの静的型チェックをCIに組み込んで初めて意味を持ちます。
SpringのJdbcTemplateはテンプレートメソッドパターンですか?
厳密には異なります。Spring Frameworkリファレンスは、JdbcTemplateが定型のJDBC処理を担い、アプリケーション側はRowCallbackHandlerやPreparedStatementCreatorといったコールバックインタフェースを実装すればよいと説明しています。可変部を渡すのは継承ではなく引数、つまり委譲です。GoFのテンプレートメソッドは可変部をサブクラスに委ねる形を指すため、委譲で埋めるJdbcTemplateは厳密には当てはまりません。当該ドキュメントにtemplate methodという語も出てきません。クラス名にTemplateとあるかどうかで、GoFのパターン名は判断できないということです。
Strategyパターンとの違いは?
可変部の差し込み方が違います。テンプレートメソッドはサブクラスがメソッドをオーバーライドして埋めるため、組み合わせはクラス定義時に決まります。Strategyは振る舞いをオブジェクトとして外から渡すため、実行時の差し替えが可能です。可変点が1つで手順を共有したいならテンプレートメソッド、可変点が複数あるか実行時に切り替えたいならStrategyという基準で選んでください。
デザインパターンは全部で何個ありますか?
GoFと呼ばれるGamma・Helm・Johnson・Vlissidesの書籍『Design Patterns: Elements of Reusable Object-Oriented Software』がカタログ化したのは23パターンです。出版社Pearson(InformIT)の書誌ページでも23と記載されています。生成に関するパターン、構造に関するパターン、振る舞いに関するパターンの3分類で構成され、テンプレートメソッドは振る舞いに関するパターンに含まれます。