Template Method
Fixing a duplicated workflow
A reporting service generates two kinds of reports: CSV and JSON. Each begins by querying the database, then formats the rows, then writes output to a file. The query and write steps are identical; only the formatting differs. Without a shared structure, two nearly parallel implementations grow side by side:
1# Bad: two report classes with duplicated connect, query, and write logic.
2class CsvReportGenerator:
3 def generate(self, query: str, path: str) -> None:
4 conn = Database.connect() # duplicated
5 rows = conn.execute(query) # duplicated
6 with open(path, "w") as f:
7 f.write(",".join(rows[0].keys()) + "\n")
8 for row in rows:
9 f.write(",".join(str(v) for v in row.values()) + "\n")
10 conn.close() # duplicated
11
12class JsonReportGenerator:
13 def generate(self, query: str, path: str) -> None:
14 import json
15 conn = Database.connect() # duplicated
16 rows = conn.execute(query) # duplicated
17 with open(path, "w") as f:
18 json.dump([dict(r) for r in rows], f, indent=2)
19 conn.close() # duplicated
When a third format (XML, Parquet) is needed, the pattern repeats: copy the class, change the formatting block, copy the bugs in the connect and write logic too. The failure mode is duplicated intent, not just repeated lines: a bug in the connection-handling code must be fixed in every format class independently.
The Template Method pattern addresses this by moving the invariant steps into a base class and leaving only the varying steps to subclasses.
How it works
A base class defines the template method — a method that calls the steps of the algorithm
in a fixed order. Invariant steps are implemented directly in the base class. Variable steps
are declared abstract; each subclass provides its own implementation. The template method
itself is not overridden — in Python by convention, in Java by marking it final.
generate() is the template method. It calls connect(), execute(), format(), and
write() in order. connect(), execute(), and write() are invariant steps in the base
class. format() is the abstract hook — each subclass overrides exactly that one method and
nothing else.