The problem
A product catalogue imports data from its suppliers, and each supplier sends a file in some format. The first importer reads CSV: it opens the file, reads its rows, turns each row into a product record, checks each record, saves the good ones and closes the file. When a supplier sends JSON Lines instead, the quickest route is to copy the class and change the line that parses a row. Two classes now hold the same procedure, and only one of its six steps really differs.
The copies drift apart with the first fix. In the diagram, products without a name were getting into the catalogue, so a name check was added to CsvImporter; JsonImporter has its own copy of that step, nobody remembered it, and it still accepts them. Every later change to the shared steps (a transaction around the saves, a limit on the number of rows, clearer error messages) has to be found, made and tested in every copy, and nothing stops one copy from running the steps in a different order.
How it works
Template Method writes the procedure once, in a base class, as a method that calls the steps in a fixed order, and leaves some of those steps for subclasses to fill in. The base class decides what happens and when; a subclass decides only how its own steps are done. The pattern is one of the behavioural patterns in Design Patterns by Gamma, Helm, Johnson and Vlissides (1994), and unlike most of them it works through inheritance rather than by combining objects.
| Participant | In the diagram | Role |
|---|---|---|
| AbstractClass | DataImporter |
Holds the template method, run(file), which calls the steps in order. It implements the steps that never vary (open, readRows, save, close), declares the abstract ones and gives each hook its default. |
| ConcreteClass | CsvImporter, JsonImporter |
Implements the abstract steps (parse(row)) and overrides a hook when it needs to (JsonImporter.validate). It doesn't decide the order of the steps. |
Fixed steps, abstract steps and hooks. The steps that a template method calls come in three kinds, and whoever writes a subclass needs to know which is which:
- Fixed steps belong to the base class and are not meant to change:
open,readRows,saveandclose. In the code below they are private toDataImporter. - Abstract steps have no implementation in the base class, so every subclass has to provide one:
parse(row). The TypeScript, Java and C# compilers reject a concrete subclass that leaves one out. - Hooks come with a default, and a subclass may override them:
validate(rec)rejects a record without a name unless a subclass says otherwise. Many hooks do nothing by default and only mark a place, often just before or after an important step, where a subclass can add behaviour. Refactoring.Guru keeps the word hook for those empty ones and calls a step with a real default an optional step. Either way, the template method works whether the step is overridden or not.
Make the difference visible in the code. Abstract methods take care of the steps that must be written. Name hooks for the moment they run or the decision they make (beforeSave, afterRow, shouldSkip) so that they read as optional, document their defaults, and make them protected so that callers can't run them out of order. Java's ThreadPoolExecutor is a good model: its documentation lists beforeExecute, afterExecute and terminated under the heading Hook methods, and the first two do nothing unless a subclass overrides them.
Inverted control. In the diagram the calls go from the base class down into the subclass: run() calls this.parse(row), and CsvImporter never drives the import itself. This is inversion of control, also called the Hollywood principle ("don't call us, we'll call you"), and Martin Fowler's InversionOfControl uses the template method as its simplest example: the superclass owns the flow, and subclasses fill in the steps it calls. Frameworks are extended this way, which is also why a subclass read on its own doesn't tell you when its methods run.
Protecting the skeleton. The pattern holds only while subclasses can't change the order, so the template method itself should not be overridable:
- Java: declare it
final. The Java tutorial suggestsfinalfor a method whose behaviour must not change because the object's consistency depends on it. - C# and Kotlin make methods non-overridable unless you say otherwise: C# methods are non-virtual by default, and Kotlin members can't be overridden without
open. Mark only the stepsabstract,virtualoropen. - TypeScript has no
final. Protect the skeleton by convention (a comment, code review) and by not exposing what shouldn't change: ES private methods such as#savecan't be called or replaced from a subclass, andprotectedkeeps the other steps out of callers' reach, at compile time. With the compiler optionnoImplicitOverride, every override has to be markedoverride, sorun()can't be overridden by accident, although it still can be on purpose. - Python:
@typing.finaltells type checkers that a method must not be overridden; nothing checks it at runtime.
Code
TypeScript that mirrors the diagram. Node 22.18 or later runs it as is (node importer.ts): it strips the types without checking them, so use tsc --noEmit to catch a subclass that forgets parse(). The code also passes with noImplicitOverride turned on. A map stands in for the file system, and the saved array for the database.
interface Product { readonly id: number; readonly name: string; readonly price: number }
// Stand-ins for the file system and the database, so the example runs as is.
const files: Record<string, string> = {
'products.csv': '1001,Mug,12.00\n1002,Lamp,45.00\n1003,,7.50\n1004,Plate,9.90\n1005,Bowl,6.40',
'products.jsonl': '{"id":2001,"name":"Cup","price":0}\n{"id":2002,"name":"Vase","price":19.5}',
};
// AbstractClass: run() is the template method.
abstract class DataImporter {
readonly saved: Product[] = [];
readonly rejected: Product[] = [];
#text = '';
// The skeleton. Don't override it: TypeScript has no `final`.
run(file: string): void {
this.#open(file);
const records = this.#readRows().map((row) => this.parse(row));
for (const rec of records) {
if (this.validate(rec)) this.#save(rec);
else this.rejected.push(rec);
}
this.#close();
}
// Abstract step: every subclass must supply it.
protected abstract parse(row: string): Product;
// Hook: a default that a subclass may override.
protected validate(rec: Product): boolean {
return rec.name.trim() !== '';
}
// Fixed steps: #private, so a subclass can neither call nor replace them.
#open(file: string): void { this.#text = files[file]; }
#readRows(): string[] { return this.#text.split('\n'); }
#save(rec: Product): void { this.saved.push(rec); }
#close(): void { this.#text = ''; }
}
// ConcreteClasses
class CsvImporter extends DataImporter {
protected override parse(row: string): Product {
const [id, name, price] = row.split(',');
return { id: Number(id), name, price: Number(price) };
}
}
class JsonImporter extends DataImporter {
protected override parse(row: string): Product {
return JSON.parse(row) as Product;
}
protected override validate(rec: Product): boolean {
return super.validate(rec) && rec.price > 0; // keep the default, add a rule
}
}
const csv = new CsvImporter();
csv.run('products.csv');
console.log(`${csv.saved.length} saved, ${csv.rejected.length} rejected`); // 4 saved, 1 rejected
console.log(csv.rejected.map((r) => r.id)); // [ 1003 ]
const json = new JsonImporter();
json.run('products.jsonl');
console.log(json.rejected.map((r) => r.name)); // [ 'Cup' ] (price 0)
Output:
4 saved, 1 rejected
[ 1003 ]
[ 'Cup' ]
JsonImporter reads JSON Lines, one object per row, so the fixed readRows() step serves both formats. The second file's Cup costs 0, so JsonImporter's hook rejects it; CsvImporter, which keeps the default, would have saved it.
When to use it
- Several classes run the same procedure and differ in a few of its steps, and the order must be the same for all of them: importers and exporters, report generators, the life cycle of a test, a request or a job.
- You are writing a framework or a library base class: the framework keeps the flow and offers abstract steps and hooks as the places where its users plug in.
- Two classes grew by copy and paste, as in step 1, and the shared steps should exist once (see It is a refactoring below).
- Not when the variable parts must be chosen at runtime or combined freely (CSV parsing with the stricter JSON validation, say): pass them in as objects or functions instead, which is Strategy. And not for a single variant, where a plain method is simpler.
Trade-offs
- Inheritance couples. A subclass depends on the base class's protected contract: which steps exist, when they are called and what each may assume. A change to the base class can break subclasses it has never seen, and in a language with single inheritance a class that extends
DataImportercan extend nothing else. - A subclass for every combination. Two parsers and two validation rules make four subclasses, or a deeper hierarchy, and each variant is fixed when its class is written.
- Hidden control flow. Reading
CsvImporteralone, you can't see whenparse()runs or what happens to its result; you have to readrun()in the base class. - Overrides can weaken the default.
JsonImporter.validate()keeps the name check only because it callssuper.validate(rec). Drop that call and nameless products get in again, as in step 1, and the subclass breaks a promise the base class made; Refactoring.Guru warns that suppressing a default step this way can violate the Liskov substitution principle. The implementation notes show a safer shape. - In return, the procedure is written and fixed once, every importer gets a fix to the shared steps at the same moment, and a new format is a small class with one method.
Implementation notes
- It is a refactoring. Going from step 1 to step 2 is Martin Fowler's Form Template Method, from the first edition of Refactoring (1999): move each part that differs into a method with the same name and signature in both subclasses, and once the two
run()methods are identical, pullrun()up into the base class. The refactoring isn't in the index of the current online catalog, but its page is still there. Replace Subclass with Delegate goes the other way, from a subclass to an object the class delegates to. - Keep hooks narrow. When a subclass has to call
superto keep the base behaviour, nothing reminds it to. A safer shape keepsvalidate()fixed in the base class and has it call an empty hook after its own check:validate(rec)checks the name and then returnsthis.extraChecks(rec), which returnstrueby default.JsonImporteroverrides onlyextraChecks(), withrec.price > 0, and no subclass can lose the name check. - Prefer composition when steps vary on their own. If the parser and the validation rule vary independently, or have to be chosen per file at runtime, give one importer class its steps as objects or functions:
new Importer({ parse: parseCsv, validate: requirePositivePrice }). That is Strategy applied to the steps, with the skeleton still in one place. Spring'sJdbcTemplateis built this way: it runs the fixed JDBC workflow (it creates and releases the resources, creates and runs the statement, iterates over the results and translates exceptions), and the application passes callbacks, such as aRowMapperlambda, for the parts that vary. Where functions are values, a template method often becomes a higher-order function that takes its steps as arguments. - Keep the hierarchy shallow. Each level that overrides a step and calls
supermakes the real sequence harder to follow: to know whatrun()does for aGzipCsvImporterthat extendsCsvImporter, you read three classes. One abstract base class and one level of concrete classes is the easy case; push further variation into composed objects. - Don't call steps from the constructor. In TypeScript, JavaScript and Java, a base-class constructor that calls an overridden step runs the subclass's code before the subclass's own fields are initialised. Let the client call the template method after construction, as it calls
run()here. - Where it appears.
- Java:
AbstractListneeds onlyget(int)andsize()from a subclass to make an unmodifiable list, and builds the rest of theListinterface on them, iterators included.InputStreamleaves the one-byteread()to subclasses, and its defaultread(byte[], int, int)simply callsread()repeatedly; the documentation encourages subclasses to override that one with something faster.ThreadPoolExecutor's hook methods are above. - Python:
unittest.TestCase.run()callssetUp(), the test method andtearDown(). Both fixture methods do nothing by default, andtearDown()runs even when the test raised an exception, but only ifsetUp()succeeded. Fowler's bliki entry makes the same point about JUnit'ssetUpandtearDown, the framework that inspiredunittest. - Jakarta Servlet:
HttpServlet'sservice()dispatches each request todoGet,doPostor anotherdoXxxmethod; a servlet overrides the ones it supports, and the documentation says there is almost no reason to overrideservice()itself.
- Java:
- Relatives.
- Factory Method: a factory method is often one of the steps of a template method, the step whose job is to create an object, and Refactoring.Guru calls Factory Method a specialisation of Template Method. Overriding it changes what gets created, not the order of the steps.
- Strategy: both separate a part that varies from a part that doesn't. Template Method varies steps of an algorithm through subclasses, chosen per class when the code is written; Strategy varies the whole algorithm through an object that the context holds, chosen per object and swappable at runtime.
- Builder: its Director runs a fixed sequence of building steps on a builder object it is given, so it is the same idea built by composition.
- At architecture scale, a fixed sequence of stages is Pipes and Filters: read, parse, validate and save become separate filters joined by pipes. What changes is that each stage is its own deployable component, which can be scaled, replaced or reused on its own, the order is set by how the pipes are wired rather than by a base class, and the stages share only the data that flows between them.