2026-05-05
You've written this code three times: same five steps, same order, but step three differs each time. You copy-paste, change the middle, and now you have three near-identical functions that drift apart over the next two years. The Template Method pattern fixes this by defining the algorithm's skeleton in a base class and letting subclasses override only the steps that vary.
The pattern has two ingredients:
final — that calls a fixed sequence of steps.Real-world example: a data import pipeline. Every importer does the same dance: open a source, validate the schema, transform rows, write to the warehouse, emit metrics. Only the source-opening and transformation logic differ between CSV, Parquet, and a vendor's REST API.
abstract class Importer {
// Template method — the skeleton, locked down.
final void run() {
var raw = openSource();
validate(raw);
var rows = transform(raw);
write(rows);
emitMetrics(rows.size());
}
protected abstract Source openSource();
protected abstract List<Row> transform(Source s);
// Default hooks — override only if needed.
protected void validate(Source s) { /* sane default */ }
}
Now CsvImporter and ParquetImporter each implement two methods instead of duplicating fifty lines. When you add OpenTelemetry tracing to run(), every importer gets it for free.
Rule of thumb: the 80/20 split. If 80% of your algorithm is identical across variants and 20% varies in predictable spots, Template Method pays off. If the variation is more like 50/50, or the steps differ in order rather than content, reach for Strategy or Chain of Responsibility instead.
Watch out for these traps:
final and document which hooks are required vs. optional.Template Method shines when the process is the contract and the steps are the negotiation. Use it for ETL jobs, request lifecycles, test fixtures, and report generators — anywhere the recipe is fixed but the ingredients vary.
