Template Method: Fixed Skeleton with Variable Steps
What This Concept Is
Template Method defines the outline of an algorithm in an abstract base class and lets subclasses fill in specific steps. The base class owns the order of operations. Subclasses own the details of individual steps.
Two kinds of steps:
- abstract steps -- subclasses must implement them
- hooks -- steps with a default (often empty) implementation that subclasses may override
The template method itself should not be overridden; that is the whole point of fixing the skeleton.
Why It Matters Here
This is the inheritance-based cousin of Strategy. Both handle variation. Strategy uses composition; Template Method uses inheritance. Template Method is stronger when:
- the structure of the algorithm is truly fixed
- you want to share code across variants (not just swap an algorithm)
- there are many steps, and each variant overrides only one or two
It is weaker when variation must change at runtime: subclassing is static, Strategy is dynamic.
Concrete Example
A report generator where the skeleton is fixed (load, transform, render, persist) but formats differ.
from abc import ABC, abstractmethod
class Report(ABC):
def run(self, source: str) -> None:
data = self.load(source)
cleaned = self._normalize(data)
self.before_render()
output = self.render(cleaned)
self.persist(output)
def _normalize(self, data):
return [row for row in data if row is not None]
def before_render(self): pass
@abstractmethod
def load(self, source): ...
@abstractmethod
def render(self, rows): ...
@abstractmethod
def persist(self, output): ...
class CSVReport(Report):
def load(self, source):
with open(source) as f: return [line.strip().split(",") for line in f]
def render(self, rows):
return "\n".join(",".join(r) for r in rows)
def persist(self, output):
with open("out.csv", "w") as f: f.write(output)
class JSONReport(Report):
def load(self, source):
import json
with open(source) as f: return json.load(f)
def before_render(self):
print("rendering JSON")
def render(self, rows):
import json; return json.dumps(rows)
def persist(self, output):
with open("out.json", "w") as f: f.write(output)
run is the template method: the order is fixed. _normalize is shared. before_render is a hook. load, render, and persist are abstract.
Common Confusion / Misconception
- Overriding the template method itself. If subclasses change step order, the base class is not owning the skeleton. Mark the template method
final(Java),@final(Python convention), orsealedwhere possible. - Shared code pressure creates a god base class. If every subclass uses the same three helpers but also six unused ones, split the hierarchy or move helpers out to a utility.
- Using Template Method when Strategy would suffice. If only one step varies, a Strategy is usually smaller. Template Method pays off with multiple variable steps.
- Hooks as a dumping ground. Every hook is a subclass extension point. Add them deliberately, not speculatively.
How To Use It
- Write the algorithm once, concretely, in one method.
- Find steps that would differ between variants. Mark them.
- Pull those steps out into named methods on the same class.
- Make the class abstract; make the varying methods abstract or give them defaults.
- Decide which defaulted steps deserve to be "hooks" -- truly optional -- and name them so.
- Forbid overriding the top-level method.
Test by instantiating two subclasses and ensuring they share the invariant (step order) while producing different outputs.
Check Yourself
- Which methods in Template Method are subclasses allowed to override, and which are not?
- What makes a step a "hook" versus an abstract step?
- When would you reach for Strategy instead of Template Method?
- Why is fixed step order the thing the base class protects?
Mini Drill or Application
Refactor an existing class with two variants that each have a four-step pipeline with duplicated steps 1, 2, and 4.
- Promote a base class containing the pipeline.
- Mark step 3 abstract.
- Add a hook between steps 2 and 3.
- Remove duplication in the subclasses.
Stop when the subclasses contain only the differences, not the shared skeleton.