Design Patterns (GoF and modern) Module 4 – Behavioral patterns: algorithms and requests
Template Method: a fixed skeleton with hooks
A base class fixes the order of an algorithm and subclasses fill in its steps: required steps, optional hooks, the composition alternative and two traps.
What you will learn
- Implement a template method with required steps and optional hooks
- Rewrite a template method with composition, using strategies or function parameters, and compare the two
- Recognise template methods in standard libraries and test frameworks
- Avoid the fragile base class problem and calls to overridable steps from constructors
Before you start
What you’ll learn · Before you start
What you will learn
- Implement a template method with required steps and optional hooks
- Rewrite a template method with composition, using strategies or function parameters, and compare the two
- Recognise template methods in standard libraries and test frameworks
- Avoid the fragile base class problem and calls to overridable steps from constructors
Before you start
On this page
Template Method is a design pattern in which a base class fixes the steps of an algorithm, and their order, in one method, the template method, and lets subclasses supply the steps that differ. Some steps are required: the base class declares them, and every subclass must implement them. Others are hooks: they come with a default, often one that does nothing, which a subclass may override. The base class calls the subclass’s code, not the other way round, a reversal the Gang of Four call the Hollywood principle: “don’t call us, we’ll call you”. Use it when several variants share one procedure that must not drift apart, such as importers, report builders, test fixtures and the request handling of a framework.
The idea: the base class owns the order
Three importers that each copy the same sequence of reading, checking, converting and saving rows will drift: one
skips the check, another converts before checking. A template method keeps the sequence in one place. Here run()
is the only method that knows the order; each format supplies how it reads, and one format also uses the hook for
rejected rows:
# A data import as a template method: run() fixes the order of the steps once, in the base class. Subclasses fill in
# the steps that differ (reading a format) and may override a hook (here, what to do with rejected rows).
import csv
import io
import json
class Importer:
def run(self, source): # the template method: the order lives here and nowhere else
rows = self.read(source) # required: every format reads differently
good = [r for r in rows if self.valid(r)]
self.rejected(len(rows) - len(good)) # hook: does nothing unless a subclass says otherwise
saved = [self.transform(r) for r in good]
print(f" saved {saved}")
def read(self, source):
raise NotImplementedError
def valid(self, row): # a default step, shared by all formats
return row.get("name", "").strip() != "" and row.get("qty", "").isdigit()
def transform(self, row):
return (row["name"].strip().title(), int(row["qty"]))
def rejected(self, count): # the hook's default
pass
class CsvImporter(Importer):
def read(self, source):
return list(csv.DictReader(io.StringIO(source)))
class JsonImporter(Importer):
def read(self, source):
return [{k: str(v) for k, v in item.items()} for item in json.loads(source)]
def rejected(self, count): # this format reports bad rows
print(f" {count} row(s) rejected")
print("CSV:")
CsvImporter().run("name,qty\npens,12\n,3\nink,x\nruler,4\n")
print("JSON:")
JsonImporter().run('[{"name": "paper", "qty": 500}, {"name": "glue", "qty": "two"}]') Output
CSV:
saved [('Pens', 12), ('Ruler', 4)]
JSON:
1 row(s) rejected
saved [('Paper', 500)]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 importer.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
Template Method in the importer example: the base class owns the order, subclasses fill in steps
Text description of the diagram
Three class boxes. Each box shows the class name and its operations, with a note on the right of each operation.
At the top is Importer, the base class. Its operations are run(source), the template method; read(source), a required step; valid(row) and transform(row), default steps that subclasses may keep; and rejected(count), a hook whose default does nothing. run() calls the steps in a fixed order: read, then valid for each row, then rejected with the number of bad rows, then transform for each good row.
Below it are two subclasses, each joined to Importer by a solid line with a hollow triangle at Importer, which means inheritance. CsvImporter overrides only read(source), which returns the rows of a CSV text; its line is labelled "overrides read()". JsonImporter overrides read(source), for JSON text, and the hook rejected(count), which prints the count; its line is labelled "overrides read() and the hook". Neither subclass defines run(), so neither can change the order of the steps.
The CSV importer wrote no validation code, yet its rows with an empty name and a quantity of “x” were rejected, because the check belongs to the skeleton. The JSON importer added one behaviour, reporting rejected rows, by overriding the hook, and changed nothing else.
That is the payoff. A fourth format, say fixed-width text, is one new class with one method; the checks, the conversion and the reporting come with the skeleton, and a fix to the validation reaches every format at once. The price is a tight bond between the classes: a subclass can vary only the steps the base class chose to expose, and it relies on the base class calling them in the order it always has.
The pattern comes with a few neighbours worth naming. Composition gives the same skeleton without inheritance:
the varying steps are passed in as functions or small objects. Standard-library template methods are classes you
already use: Java’s AbstractList and InputStream, Rust’s Iterator, Python’s unittest.TestCase. The fragile
base class problem is a subclass that breaks when the base class changes its own skeleton. Calls from
constructors to overridable steps run code before the object is ready, and each language fails differently.
Required steps, hooks and a template no subclass can reorder Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- The importer in five more languages, by inheritance and by composition
- Template methods in standard libraries
- Two traps reproduced: the fragile base class and constructor calls
- Template Method or Strategy?
- Practise: a report generator, two ways
- A report generator as a template method and by composition
- Check yourself
- Check yourself
- Interview questions
- A base class with a template method is subclassed by 40 internal services. You need to swap the order of two of its steps for performance. How do you make the change without breaking subclasses you cannot see?
- In a code review, a change adds a fifth subclass of ReportBase only to format dates differently in one step. What would you suggest, and why?
- A base class constructor calls an overridable initialise() step. In Java a subclass's initialise() sees null in a field it sets in its own constructor; the same design in C++ runs the base version instead. Explain both, and how you would restructure the code.
- Key takeaways
7 runnable examples (C++, C++: constructor, Go, Java, Java: AbstractList, Java: InputStream, Java: constructor, Java: final, Python, Rust, Rust: Iterator, TypeScript) · 1 exercise with sample tests · 3 interview answers · about 19 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley (Pearson))
- Class AbstractList (Java SE 25 API) (Oracle)
- Class InputStream (Java SE 25 API) (Oracle)
- Trait Iterator (Rust standard library) (The Rust Project)
- unittest: Unit testing framework (Python Software Foundation)
- abc: Abstract Base Classes (Python Software Foundation)
- C++ Core Guidelines: C.82, Don't call virtual functions in constructors and destructors (Standard C++ Foundation)
- Design Patterns in Dynamic Languages (Peter Norvig)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress