On this page
Description
Template Method is the behavioral pattern for "the same recipe, different details": a base class fixes the order of an algorithm's steps in one method and lets subclasses fill in the steps that vary. It's how frameworks hand you a skeleton - servlets, test fixtures, abstract collections - and call your code at the right moments. This skill covers the pattern, required steps versus optional hooks, and when composition is the better choice.
You export reports as CSV and as Markdown. Both exporters do the same steps - write a header, write each row, write a footer - but the code is duplicated:
Java
class CsvExporter {
String export(List<Row> rows) {
StringBuilder out = new StringBuilder("name,score\n");
for (Row r : rows) out.append(r.name()).append(',').append(r.score()).append('\n');
return out.toString();
}
}
class MarkdownExporter {
String export(List<Row> rows) {
StringBuilder out = new StringBuilder("| name | score |\n|---|---|\n");
for (Row r : rows) out.append("| ").append(r.name()).append(" | ").append(r.score()).append(" |\n");
return out.toString();
}
}
Python
class CsvExporter:
def export(self, rows: list[Row]) -> str:
out = ["name,score\n"]
for r in rows:
out.append(f"{r.name},{r.score}\n")
return "".join(out)
class MarkdownExporter:
def export(self, rows: list[Row]) -> str:
out = ["| name | score |\n|---|---|\n"]
for r in rows:
out.append(f"| {r.name} | {r.score} |\n")
return "".join(out)
When you add sorting, or skipping empty rows, you must remember to change every copy.
We'll go from simple to more technical:
- The pattern: a
finaltemplate method, abstract steps and hooks. - How it works, step by step - "don't call us, we'll call you".
- Where you'll see it in the standard libraries and frameworks.
- Inheritance costs, and Template Method versus Strategy.
The pattern
Template Method puts the fixed sequence of steps in one method of an abstract base class (the template method) and lets subclasses fill in the steps that vary.
Java
abstract class ReportExporter {
// The template method: the order of steps is fixed here, once.
public final String export(List<Row> rows) {
StringBuilder out = new StringBuilder();
out.append(header());
for (Row r : rows) {
if (includeRow(r)) out.append(formatRow(r));
}
out.append(footer());
return out.toString();
}
protected abstract String header(); // must be provided
protected abstract String formatRow(Row r); // must be provided
protected String footer() { return ""; } // hook: optional
protected boolean includeRow(Row r) { return true; } // hook: optional
}
class CsvExporter extends ReportExporter {
protected String header() { return "name,score\n"; }
protected String formatRow(Row r) { return r.name() + "," + r.score() + "\n"; }
}
Python
from abc import ABC, abstractmethod
from typing import final
class ReportExporter(ABC):
# The template method: the order of steps is fixed here, once.
@final
def export(self, rows: list[Row]) -> str:
out = [self.header()]
for r in rows:
if self.include_row(r):
out.append(self.format_row(r))
out.append(self.footer())
return "".join(out)
@abstractmethod
def header(self) -> str: ... # must be provided
@abstractmethod
def format_row(self, r: Row) -> str: ... # must be provided
def footer(self) -> str: # hook: optional
return ""
def include_row(self, r: Row) -> bool: # hook: optional
return True
class CsvExporter(ReportExporter):
def header(self) -> str:
return "name,score\n"
def format_row(self, r: Row) -> str:
return f"{r.name},{r.score}\n"
How it works, step by step
- Write the algorithm once in the base class, in a method marked
finalso subclasses can't change the order. (In Python,@typing.finalmakes a type checker such as mypy reject an override; the interpreter itself doesn't stop one.) - Steps that differ become abstract methods: every subclass must implement them.
- Optional steps become hooks: methods with a sensible default that subclasses may override.
- Subclasses only fill in the blanks; they never call the template method's steps themselves.
- "Don't call us, we'll call you" - the base class calls down into the subclass.
Where you'll see it
java.util.AbstractList: implementget()andsize(), and you get iteration,contains,indexOfand more for free.java.io.InputStream: subclasses implement oneread(); the bulkread(byte[], ...)methods are built on top of it.HttpServlet.service()decides which ofdoGet()/doPost()to call.- Python's
collections.abc.Sequence: implement__getitem__and__len__, and you getin, iteration,indexandcountfor free;unittest.TestCaserunssetUp, the test andtearDownin a fixed order. - Test frameworks run setup, the test and teardown in a fixed order.
Choosing among the behavioral patterns
| If you need to... | Reach for | It's about |
|---|---|---|
| Let many objects react when something happens, without the source knowing them | Observer | Who gets told about a change. |
| Swap one algorithm for another at runtime | Strategy | How a task is done - chosen from outside. |
| Turn a request into an object you can queue, log, undo or redo | Command | What to do, packaged for later. |
| Make an object behave differently depending on its current state | State | Behavior that changes itself as the object moves between states. |
| Fix the steps of an algorithm but let subclasses fill in some of them | Template Method (this lesson) | The skeleton stays, the details vary. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| A template method that can be overridden | a subclass reorders or skips steps | final in Java; @typing.final plus a type checker in Python |
| Ten abstract steps | every subclass is a chore | few required steps, more hooks with defaults |
Hooks that only work if subclasses remember to call super | forgotten calls silently skip base work | the template does the base work and calls empty hooks |
| Using it for variations that must change at run time | one fixed choice per subclass | Strategy (composition) |
| Two independent variations in one hierarchy | a subclass per combination | inject one of them as a strategy |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. Why is the template method declared final?
Show answer
So subclasses can't reorder or skip the steps - they may only fill in the steps themselves. The fixed order is the whole guarantee.
2. What's the difference between an abstract step and a hook?
Show answer
Every subclass must implement an abstract step; a hook has a default (often doing nothing) that a subclass may override.
3. When would you pick Strategy over Template Method for the part that varies?
Show answer
When the variant must be swapped at runtime, tested on its own, or combined with other independent variations - composition handles all three; inheritance fixes one choice per subclass.
Key takeaways
- The base class fixes the order of steps in one
finaltemplate method. - Required steps are abstract; optional steps are hooks with a default.
- Subclasses fill in blanks - the base class calls them, not the other way round.
- Great for removing duplicated workflows; watch out for fragile base classes.
- Composition with callbacks (like
JdbcTemplate) is often the more flexible alternative.