The Template Method is a behavioral design pattern that lets you define the skeleton of an algorithm in a superclass but lets subclasses override specific steps of the algorithm without changing its structure. It’s a powerful tool for promoting code reuse and enforcing a consistent workflow across different implementations.
[!NOTE] What’s a Behavioral Pattern? Behavioral design patterns are concerned with how objects interact and delegate responsibilities. They help ensure that objects collaborate in a structured and organized way, preventing tight coupling and making the system more flexible.
Let’s break down the core idea with a visual overview.
mindmap
root((Template Method))
Core Idea
:Define an algorithm's skeleton.
:Let subclasses fill in the blanks.
:Promotes code reuse (DRY).
:Inverts control (Hollywood Principle).
Components
AbstractClass
:template_method()
:primitive_operation_1() (abstract)
:primitive_operation_2() (abstract)
:hook() (optional override)
ConcreteClass
:Implements primitive_operation_1()
:Implements primitive_operation_2()
:(Optionally) Overrides hook()
The Problem: Rampant Code Duplication
Imagine you’re building a system to export a report in various formats: PDF, Word, and Excel. The overall process for each format is very similar:
- Fetch the data.
- Format the data for the specific file type.
- Save the formatted data to a file.
- Log that the operation occurred.
- Audit the action for security.
A naive implementation might look like this, with separate classes for each exporter, leading to a lot of repeated code.
# The "Before" state: lots of duplicated logic
class PdfExporter:
def export(self, report):
print(f"Exporting '{report.title}' to PDF...")
print("Fetching data...") # Duplicate
print("Formatting as PDF...")
print("Saving PDF file...")
print("Logging...") # Duplicate
print("Auditing...") # Duplicate
class WordExporter:
def export(self, report):
print(f"Exporting '{report.title}' to Word...")
print("Fetching data...") # Duplicate
print("Formatting as Word...")
print("Saving Word file...")
print("Logging...") # Duplicate
print("Auditing...") # Duplicate
class ExcelExporter:
def export(self, report):
print(f"Exporting '{report.title}' to Excel...")
print("Fetching data...") # Duplicate
print("Formatting as Excel...")
print("Saving Excel file...")
print("Logging...") # Duplicate
print("Auditing...") # Duplicate
The Fetching data..., Logging..., and Auditing... steps are identical in every class. If you need to change the data fetching logic or update the logging strategy, you have to modify all three classes. This violates the DRY (Don’t Repeat Yourself) principle and is a maintenance nightmare.
The Solution: Building a Template
The Template Method pattern solves this by creating a single template that defines the overall algorithm. We’ll create an abstract base class that handles the common steps and leaves the variable parts as abstract methods for the subclasses to implement.
graph TD
subgraph Before - High Duplication
A[PdfExporter]
B[WordExporter]
C[ExcelExporter]
end
subgraph After - Template Method
D(ReportExporterTemplate) --> E[PdfExporter]
D --> F[WordExporter]
D --> G[ExcelExporter]
end
style D fill:#f9f,stroke:#333,stroke-width:2px
Let’s refactor our code. First, we’ll organize our project files.
report_system/
├── main.py
└── exporters/
├── __init__.py
├── base_exporter.py
├── pdf_exporter.py
├── word_exporter.py
└── excel_exporter.py
Step 1: Create the Abstract Base Class (The Template)
In base_exporter.py, we’ll define our template. We use Python’s abc (Abstract Base Class) module to define the structure.
[!TIP] What is an Abstract Base Class (ABC)? An ABC is a class that cannot be instantiated on its own. It’s designed to be inherited by subclasses that must provide implementations for its abstract methods. It’s the perfect tool for creating templates.
# report_system/exporters/base_exporter.py
from abc import ABC, abstractmethod
class Report:
def __init__(self, title, content):
self.title = title
self.content = content
class ReportExporterTemplate(ABC):
"""
The Abstract Base Class defines the template method and the overall algorithm skeleton.
"""
def export(self, report: Report):
"""This is the Template Method. It defines the sequence of operations."""
self._get_header(report)
self._fetch_data()
formatted_data = self._format_data(report)
self._save_file(formatted_data)
self._log()
self._audit()
print("-" * 20)
# --- Common steps (implemented here) ---
def _fetch_data(self):
print("Fetching data...")
def _log(self):
print("Logging operation...")
def _audit(self):
print("Auditing operation...")
# --- Hooks (can be overridden, but have a default implementation) ---
def _get_header(self, report: Report) -> str:
header = f"Exporting '{report.title}'"
print(header)
return header
# --- Abstract steps (must be implemented by subclasses) ---
@abstractmethod
def _format_data(self, report: Report) -> str:
pass
@abstractmethod
def _save_file(self, formatted_data: str):
pass
Notice the structure:
export(): This is our Template Method. It defines the fixed sequence of calls. It’s public and meant to be the single entry point for the client._fetch_data,_log,_audit: These are common steps, implemented directly in the base class._get_header(): This is a Hook. It has a default implementation, but subclasses can override it if they need to add more specific information to the header._format_data,_save_file: These are Primitive Operations. They are marked as@abstractmethod, forcing subclasses to provide their own implementation.
Step 2: Create Concrete Implementations
Now, our subclasses become much simpler. They only need to inherit from the template and implement the required abstract methods.
Here’s the refactoring for PdfExporter.
# report_system/exporters/pdf_exporter.py
- class PdfExporter:
- def export(self, report):
- print(f"Exporting '{report.title}' to PDF...")
- print("Fetching data...")
- print("Formatting as PDF...")
- print("Saving PDF file...")
- print("Logging...")
- print("Auditing...")
+ from .base_exporter import ReportExporterTemplate, Report
+
+ class PdfExporter(ReportExporterTemplate):
+ """Concrete implementation for exporting reports to PDF."""
+
+ def _get_header(self, report: Report) -> str:
+ # Overriding the hook to add specific info
+ base_header = super()._get_header(report)
+ print(f"{base_header} to PDF")
+
+ def _format_data(self, report: Report) -> str:
+ print("Formatting data as PDF...")
+ return f"[PDF Content]: {report.content}"
+
+ def _save_file(self, formatted_data: str):
+ print("Saving PDF file...")
+ print(f"Saved: {formatted_data}\n")
The WordExporter and ExcelExporter follow the same pattern, only implementing their specific formatting and saving logic.
# report_system/exporters/word_exporter.py
from .base_exporter import ReportExporterTemplate, Report
class WordExporter(ReportExporterTemplate):
"""Concrete implementation for exporting reports to Word."""
def _get_header(self, report: Report):
super()._get_header(report)
print("Exporting to Word format")
def _format_data(self, report: Report) -> str:
print("Formatting data as Word...")
return f"[Word Content]: {report.content}"
def _save_file(self, formatted_data: str):
print("Saving Word file...")
print(f"Saved: {formatted_data}\n")
# report_system/exporters/excel_exporter.py
from .base_exporter import ReportExporterTemplate, Report
class ExcelExporter(ReportExporterTemplate):
"""Concrete implementation for exporting reports to Excel."""
def _get_header(self, report: Report):
super()._get_header(report)
print("Exporting to Excel format")
def _format_data(self, report: Report) -> str:
print("Formatting data as Excel...")
return f"[Excel Content]: {report.content}"
def _save_file(self, formatted_data: str):
print("Saving Excel file...")
print(f"Saved: {formatted_data}\n")
Step 3: The Client Code
The client code in main.py now works with any exporter through the same simple export method.
# report_system/main.py
from exporters.base_exporter import Report
from exporters.pdf_exporter import PdfExporter
from exporters.word_exporter import WordExporter
from exporters.excel_exporter import ExcelExporter
def main():
weekly_report = Report("Weekly Summary", "This week was productive.")
pdf_exporter = PdfExporter()
pdf_exporter.export(weekly_report)
word_exporter = WordExporter()
word_exporter.export(weekly_report)
excel_exporter = ExcelExporter()
excel_exporter.export(weekly_report)
if __name__ == "__main__":
main()
When you run this, you get a beautifully structured output, with common logic shared and specific logic executed where it belongs.
Exporting 'Weekly Summary'
Exporting to PDF format
Fetching data...
Formatting data as PDF...
Saving PDF file...
Saved: [PDF Content]: This week was productive.
Logging operation...
Auditing operation...
--------------------
Exporting 'Weekly Summary'
Exporting to Word format
Fetching data...
Formatting data as Word...
Saving Word file...
Saved: [Word Content]: This week was productive.
Logging operation...
Auditing operation...
--------------------
Exporting 'Weekly Summary'
Exporting to Excel format
Fetching data...
Formatting data as Excel...
Saving Excel file...
Saved: [Excel Content]: This week was productive.
Logging operation...
Auditing operation...
--------------------
When to Use the Template Method Pattern
[!WARNING] Template Method vs. Strategy Pattern Beginners often confuse the Template Method with the Strategy pattern.
- Template Method uses inheritance. The algorithm’s structure is fixed in a base class.
- Strategy uses composition. The algorithm is an object that you pass into another object, allowing you to change the entire algorithm at runtime.
Use Template Method when the overall algorithm is consistent. Use Strategy when you need to swap out entire algorithms dynamically.
Use the Template Method when:
- You want to let clients extend only particular steps of an algorithm, but not the whole structure.
- You have several classes that contain almost identical algorithms with minor differences. Refactoring them into a single template class avoids code duplication.
- It’s a great fit for frameworks, where the framework provides the skeleton and the user provides the implementation details.
Quiz: Test Your Knowledge
**Question:** In our example, if we wanted to add a new "Encrypt File" step right before saving, where would you add it? **Answer:** You would add a call to a new `_encrypt()` method inside the `export()` template method in the `ReportExporterTemplate` base class, right before the `_save_file()` call. You could implement it as a hook with a default "no encryption" behavior or as an abstract method if all exporters must encrypt.Conclusion
The Template Method pattern is a fundamental tool for writing clean, maintainable, and reusable object-oriented code. By defining a fixed skeleton in a superclass and deferring implementation details to subclasses, you establish a robust and flexible architecture that is easy to extend and hard to misuse. You’ve inverted control, allowing the base class to call subclass methods, not the other way around—a principle that is key to building powerful frameworks.