Skip to content

Template Method Pattern

Category: Behavioral Pattern

Overview

Define the skeleton of an algorithm in a base class, allowing subclasses to override specific steps without changing the algorithm's structure. This pattern promotes code reuse and enforces a consistent algorithm structure across different implementations.

Usage Guidelines

Use when:

  • Multiple classes share the same algorithm skeleton
  • Some steps vary between implementations
  • Want to enforce specific algorithm sequence
  • Common steps should be implemented once

Avoid when:

  • Each implementation is completely different
  • Composition would be more flexible than inheritance
  • No shared logic between implementations
  • Algorithm structure changes frequently

Implementation

from __future__ import annotations
from abc import ABC, abstractmethod

class DataMiner(ABC):
    """Abstract class defining the template method for data mining."""

    def mine(self, path: str) -> dict[str, str]:
        """Template method defining the data mining algorithm.

        This method defines the skeleton of the algorithm. Subclasses
        should not override this method.

        Args:
            path: Path to the data file.

        Returns:
            Dictionary containing mining results.
        """
        data = self.open_file(path)
        raw_data = self.extract_data(data)
        analysis = self.analyze_data(raw_data)
        self.close_file(data)
        return analysis

    @abstractmethod
    def open_file(self, path: str) -> str:
        """Open the file."""
        pass

    @abstractmethod
    def extract_data(self, data: str) -> str:
        """Extract data from the file."""
        pass

    def analyze_data(self, data: str) -> dict[str, str]:
        """Analyze the extracted data.

        This is a hook method with a default implementation.
        """
        return {"status": "analyzed", "data": data}

    @abstractmethod
    def close_file(self, data: str) -> None:
        """Close the file."""
        pass

class PDFDataMiner(DataMiner):
    """Concrete data miner for PDF files."""

    def open_file(self, path: str) -> str:
        """Open PDF file."""
        return f"PDF({path})"

    def extract_data(self, data: str) -> str:
        """Extract data from PDF."""
        return f"Extracted from {data}"

    def close_file(self, data: str) -> None:
        """Close PDF file."""
        pass

class CSVDataMiner(DataMiner):
    """Concrete data miner for CSV files."""

    def open_file(self, path: str) -> str:
        """Open CSV file."""
        return f"CSV({path})"

    def extract_data(self, data: str) -> str:
        """Extract data from CSV."""
        return f"Parsed {data}"

    def close_file(self, data: str) -> None:
        """Close CSV file."""
        pass

Usage

# PDF data mining
pdf_miner = PDFDataMiner()
result = pdf_miner.mine("document.pdf")
print(result)  # {'status': 'analyzed', 'data': 'Extracted from PDF(document.pdf)'}

# CSV data mining
csv_miner = CSVDataMiner()
result = csv_miner.mine("data.csv")
print(result)  # {'status': 'analyzed', 'data': 'Parsed CSV(data.csv)'}

Trade-offs

Benefits:

  1. Common code is in one place promoting code reuse
  2. Algorithm structure is consistent across implementations
  3. Template method prevents algorithm modification
  4. Hook methods provide optional extension points

Drawbacks:

  1. Tight coupling through inheritance
  2. Subclass constraints may violate Liskov Substitution Principle
  3. Algorithm structure is fixed with limited flexibility
  4. Changes to template affect all subclasses

Real-World Examples

  • Framework hooks like Django views, React lifecycle methods
  • Data processing pipelines with ETL operations
  • Testing frameworks with setUp, test, tearDown methods
  • Build systems with pre-build, build, post-build steps
  • Strategy
  • Factory Method
  • Hook Method

API Reference

design_patterns.behavioral.template_method

Template Method Pattern Module

The Template Method pattern defines the skeleton of an algorithm in a base class, allowing subclasses to override specific steps of the algorithm without changing its structure. This pattern promotes code reuse and enforces a consistent algorithm structure across different implementations.

Example

Data mining algorithms with different data formats:

pdf_miner = PDFDataMiner()
pdf_miner.mine("document.pdf")  # Uses PDF-specific parsing

csv_miner = CSVDataMiner()
csv_miner.mine("data.csv")  # Uses CSV-specific parsing

Beverage

Bases: ABC

Abstract class for making beverages using template method.

Source code in src/design_patterns/behavioral/template_method.py
class Beverage(ABC):
    """Abstract class for making beverages using template method."""

    def prepare(self) -> list[str]:
        """Template method for preparing a beverage.

        Returns:
            List of preparation steps.
        """
        steps = []
        steps.append(self.boil_water())
        steps.append(self.brew())
        steps.append(self.pour_in_cup())
        if self.wants_condiments():
            steps.append(self.add_condiments())
        return steps

    def boil_water(self) -> str:
        """Boil water.

        Returns:
            Step description.
        """
        return "Boiling water"

    @abstractmethod
    def brew(self) -> str:
        """Brew the beverage.

        Returns:
            Step description.
        """
        pass

    def pour_in_cup(self) -> str:
        """Pour in cup.

        Returns:
            Step description.
        """
        return "Pouring into cup"

    @abstractmethod
    def add_condiments(self) -> str:
        """Add condiments.

        Returns:
            Step description.
        """
        pass

    def wants_condiments(self) -> bool:
        """Hook method to determine if condiments should be added.

        Returns:
            True if condiments should be added.
        """
        return True

add_condiments() abstractmethod

Add condiments.

Returns:

Type Description
str

Step description.

Source code in src/design_patterns/behavioral/template_method.py
@abstractmethod
def add_condiments(self) -> str:
    """Add condiments.

    Returns:
        Step description.
    """
    pass

boil_water()

Boil water.

Returns:

Type Description
str

Step description.

Source code in src/design_patterns/behavioral/template_method.py
def boil_water(self) -> str:
    """Boil water.

    Returns:
        Step description.
    """
    return "Boiling water"

brew() abstractmethod

Brew the beverage.

Returns:

Type Description
str

Step description.

Source code in src/design_patterns/behavioral/template_method.py
@abstractmethod
def brew(self) -> str:
    """Brew the beverage.

    Returns:
        Step description.
    """
    pass

pour_in_cup()

Pour in cup.

Returns:

Type Description
str

Step description.

Source code in src/design_patterns/behavioral/template_method.py
def pour_in_cup(self) -> str:
    """Pour in cup.

    Returns:
        Step description.
    """
    return "Pouring into cup"

prepare()

Template method for preparing a beverage.

Returns:

Type Description
list[str]

List of preparation steps.

Source code in src/design_patterns/behavioral/template_method.py
def prepare(self) -> list[str]:
    """Template method for preparing a beverage.

    Returns:
        List of preparation steps.
    """
    steps = []
    steps.append(self.boil_water())
    steps.append(self.brew())
    steps.append(self.pour_in_cup())
    if self.wants_condiments():
        steps.append(self.add_condiments())
    return steps

wants_condiments()

Hook method to determine if condiments should be added.

Returns:

Type Description
bool

True if condiments should be added.

Source code in src/design_patterns/behavioral/template_method.py
def wants_condiments(self) -> bool:
    """Hook method to determine if condiments should be added.

    Returns:
        True if condiments should be added.
    """
    return True

BlackCoffee

Bases: Beverage

Concrete beverage: Black coffee without condiments.

Source code in src/design_patterns/behavioral/template_method.py
class BlackCoffee(Beverage):
    """Concrete beverage: Black coffee without condiments."""

    def brew(self) -> str:
        """Brew coffee.

        Returns:
            Brewing step.
        """
        return "Dripping coffee through filter"

    def add_condiments(self) -> str:
        """No condiments for black coffee.

        Returns:
            Empty string.
        """
        return ""

    def wants_condiments(self) -> bool:
        """Black coffee doesn't want condiments.

        Returns:
            False.
        """
        return False

add_condiments()

No condiments for black coffee.

Returns:

Type Description
str

Empty string.

Source code in src/design_patterns/behavioral/template_method.py
def add_condiments(self) -> str:
    """No condiments for black coffee.

    Returns:
        Empty string.
    """
    return ""

brew()

Brew coffee.

Returns:

Type Description
str

Brewing step.

Source code in src/design_patterns/behavioral/template_method.py
def brew(self) -> str:
    """Brew coffee.

    Returns:
        Brewing step.
    """
    return "Dripping coffee through filter"

wants_condiments()

Black coffee doesn't want condiments.

Returns:

Type Description
bool

False.

Source code in src/design_patterns/behavioral/template_method.py
def wants_condiments(self) -> bool:
    """Black coffee doesn't want condiments.

    Returns:
        False.
    """
    return False

CSVDataMiner

Bases: DataMiner

Concrete data miner for CSV files.

Source code in src/design_patterns/behavioral/template_method.py
class CSVDataMiner(DataMiner):
    """Concrete data miner for CSV files."""

    def open_file(self, path: str) -> str:
        """Open CSV file.

        Args:
            path: Path to CSV file.

        Returns:
            CSV file representation.
        """
        return f"CSV({path})"

    def extract_data(self, data: str) -> str:
        """Extract data from CSV.

        Args:
            data: CSV file data.

        Returns:
            Extracted CSV data.
        """
        return f"Parsed {data}"

    def close_file(self, data: str) -> None:
        """Close CSV file.

        Args:
            data: CSV file data.
        """
        pass

    def send_report(self, analysis: dict[str, str]) -> None:
        """Send CSV mining report.

        Args:
            analysis: Analysis results.
        """
        pass

close_file(data)

Close CSV file.

Parameters:

Name Type Description Default
data str

CSV file data.

required
Source code in src/design_patterns/behavioral/template_method.py
def close_file(self, data: str) -> None:
    """Close CSV file.

    Args:
        data: CSV file data.
    """
    pass

extract_data(data)

Extract data from CSV.

Parameters:

Name Type Description Default
data str

CSV file data.

required

Returns:

Type Description
str

Extracted CSV data.

Source code in src/design_patterns/behavioral/template_method.py
def extract_data(self, data: str) -> str:
    """Extract data from CSV.

    Args:
        data: CSV file data.

    Returns:
        Extracted CSV data.
    """
    return f"Parsed {data}"

open_file(path)

Open CSV file.

Parameters:

Name Type Description Default
path str

Path to CSV file.

required

Returns:

Type Description
str

CSV file representation.

Source code in src/design_patterns/behavioral/template_method.py
def open_file(self, path: str) -> str:
    """Open CSV file.

    Args:
        path: Path to CSV file.

    Returns:
        CSV file representation.
    """
    return f"CSV({path})"

send_report(analysis)

Send CSV mining report.

Parameters:

Name Type Description Default
analysis dict[str, str]

Analysis results.

required
Source code in src/design_patterns/behavioral/template_method.py
def send_report(self, analysis: dict[str, str]) -> None:
    """Send CSV mining report.

    Args:
        analysis: Analysis results.
    """
    pass

Coffee

Bases: Beverage

Concrete beverage: Coffee.

Source code in src/design_patterns/behavioral/template_method.py
class Coffee(Beverage):
    """Concrete beverage: Coffee."""

    def brew(self) -> str:
        """Brew coffee.

        Returns:
            Brewing step.
        """
        return "Dripping coffee through filter"

    def add_condiments(self) -> str:
        """Add sugar and milk.

        Returns:
            Condiment step.
        """
        return "Adding sugar and milk"

add_condiments()

Add sugar and milk.

Returns:

Type Description
str

Condiment step.

Source code in src/design_patterns/behavioral/template_method.py
def add_condiments(self) -> str:
    """Add sugar and milk.

    Returns:
        Condiment step.
    """
    return "Adding sugar and milk"

brew()

Brew coffee.

Returns:

Type Description
str

Brewing step.

Source code in src/design_patterns/behavioral/template_method.py
def brew(self) -> str:
    """Brew coffee.

    Returns:
        Brewing step.
    """
    return "Dripping coffee through filter"

DataMiner

Bases: ABC

Abstract class defining the template method for data mining.

Source code in src/design_patterns/behavioral/template_method.py
class DataMiner(ABC):
    """Abstract class defining the template method for data mining."""

    def mine(self, path: str) -> dict[str, str]:
        """Template method defining the data mining algorithm.

        This method defines the skeleton of the algorithm. Subclasses
        should not override this method.

        Args:
            path: Path to the data file.

        Returns:
            Dictionary containing mining results.
        """
        data = self.open_file(path)
        raw_data = self.extract_data(data)
        analysis = self.analyze_data(raw_data)
        self.close_file(data)
        self.send_report(analysis)
        return analysis

    @abstractmethod
    def open_file(self, path: str) -> str:
        """Open the file.

        Args:
            path: Path to the file.

        Returns:
            File data representation.
        """
        pass

    @abstractmethod
    def extract_data(self, data: str) -> str:
        """Extract data from the file.

        Args:
            data: File data.

        Returns:
            Extracted data.
        """
        pass

    def analyze_data(self, data: str) -> dict[str, str]:
        """Analyze the extracted data.

        This is a hook method with a default implementation.

        Args:
            data: Extracted data.

        Returns:
            Analysis results.
        """
        return {"status": "analyzed", "data": data}

    @abstractmethod
    def close_file(self, data: str) -> None:
        """Close the file.

        Args:
            data: File data representation.
        """
        pass

    def send_report(self, analysis: dict[str, str]) -> None:
        """Send the analysis report.

        This is a hook method with a default implementation.

        Args:
            analysis: Analysis results.
        """
        pass

analyze_data(data)

Analyze the extracted data.

This is a hook method with a default implementation.

Parameters:

Name Type Description Default
data str

Extracted data.

required

Returns:

Type Description
dict[str, str]

Analysis results.

Source code in src/design_patterns/behavioral/template_method.py
def analyze_data(self, data: str) -> dict[str, str]:
    """Analyze the extracted data.

    This is a hook method with a default implementation.

    Args:
        data: Extracted data.

    Returns:
        Analysis results.
    """
    return {"status": "analyzed", "data": data}

close_file(data) abstractmethod

Close the file.

Parameters:

Name Type Description Default
data str

File data representation.

required
Source code in src/design_patterns/behavioral/template_method.py
@abstractmethod
def close_file(self, data: str) -> None:
    """Close the file.

    Args:
        data: File data representation.
    """
    pass

extract_data(data) abstractmethod

Extract data from the file.

Parameters:

Name Type Description Default
data str

File data.

required

Returns:

Type Description
str

Extracted data.

Source code in src/design_patterns/behavioral/template_method.py
@abstractmethod
def extract_data(self, data: str) -> str:
    """Extract data from the file.

    Args:
        data: File data.

    Returns:
        Extracted data.
    """
    pass

mine(path)

Template method defining the data mining algorithm.

This method defines the skeleton of the algorithm. Subclasses should not override this method.

Parameters:

Name Type Description Default
path str

Path to the data file.

required

Returns:

Type Description
dict[str, str]

Dictionary containing mining results.

Source code in src/design_patterns/behavioral/template_method.py
def mine(self, path: str) -> dict[str, str]:
    """Template method defining the data mining algorithm.

    This method defines the skeleton of the algorithm. Subclasses
    should not override this method.

    Args:
        path: Path to the data file.

    Returns:
        Dictionary containing mining results.
    """
    data = self.open_file(path)
    raw_data = self.extract_data(data)
    analysis = self.analyze_data(raw_data)
    self.close_file(data)
    self.send_report(analysis)
    return analysis

open_file(path) abstractmethod

Open the file.

Parameters:

Name Type Description Default
path str

Path to the file.

required

Returns:

Type Description
str

File data representation.

Source code in src/design_patterns/behavioral/template_method.py
@abstractmethod
def open_file(self, path: str) -> str:
    """Open the file.

    Args:
        path: Path to the file.

    Returns:
        File data representation.
    """
    pass

send_report(analysis)

Send the analysis report.

This is a hook method with a default implementation.

Parameters:

Name Type Description Default
analysis dict[str, str]

Analysis results.

required
Source code in src/design_patterns/behavioral/template_method.py
def send_report(self, analysis: dict[str, str]) -> None:
    """Send the analysis report.

    This is a hook method with a default implementation.

    Args:
        analysis: Analysis results.
    """
    pass

PDFDataMiner

Bases: DataMiner

Concrete data miner for PDF files.

Source code in src/design_patterns/behavioral/template_method.py
class PDFDataMiner(DataMiner):
    """Concrete data miner for PDF files."""

    def open_file(self, path: str) -> str:
        """Open PDF file.

        Args:
            path: Path to PDF file.

        Returns:
            PDF file representation.
        """
        return f"PDF({path})"

    def extract_data(self, data: str) -> str:
        """Extract data from PDF.

        Args:
            data: PDF file data.

        Returns:
            Extracted PDF data.
        """
        return f"Extracted from {data}"

    def close_file(self, data: str) -> None:
        """Close PDF file.

        Args:
            data: PDF file data.
        """
        pass

close_file(data)

Close PDF file.

Parameters:

Name Type Description Default
data str

PDF file data.

required
Source code in src/design_patterns/behavioral/template_method.py
def close_file(self, data: str) -> None:
    """Close PDF file.

    Args:
        data: PDF file data.
    """
    pass

extract_data(data)

Extract data from PDF.

Parameters:

Name Type Description Default
data str

PDF file data.

required

Returns:

Type Description
str

Extracted PDF data.

Source code in src/design_patterns/behavioral/template_method.py
def extract_data(self, data: str) -> str:
    """Extract data from PDF.

    Args:
        data: PDF file data.

    Returns:
        Extracted PDF data.
    """
    return f"Extracted from {data}"

open_file(path)

Open PDF file.

Parameters:

Name Type Description Default
path str

Path to PDF file.

required

Returns:

Type Description
str

PDF file representation.

Source code in src/design_patterns/behavioral/template_method.py
def open_file(self, path: str) -> str:
    """Open PDF file.

    Args:
        path: Path to PDF file.

    Returns:
        PDF file representation.
    """
    return f"PDF({path})"

Tea

Bases: Beverage

Concrete beverage: Tea.

Source code in src/design_patterns/behavioral/template_method.py
class Tea(Beverage):
    """Concrete beverage: Tea."""

    def brew(self) -> str:
        """Brew tea.

        Returns:
            Brewing step.
        """
        return "Steeping tea"

    def add_condiments(self) -> str:
        """Add lemon.

        Returns:
            Condiment step.
        """
        return "Adding lemon"

add_condiments()

Add lemon.

Returns:

Type Description
str

Condiment step.

Source code in src/design_patterns/behavioral/template_method.py
def add_condiments(self) -> str:
    """Add lemon.

    Returns:
        Condiment step.
    """
    return "Adding lemon"

brew()

Brew tea.

Returns:

Type Description
str

Brewing step.

Source code in src/design_patterns/behavioral/template_method.py
def brew(self) -> str:
    """Brew tea.

    Returns:
        Brewing step.
    """
    return "Steeping tea"

XMLDataMiner

Bases: DataMiner

Concrete data miner for XML files.

Source code in src/design_patterns/behavioral/template_method.py
class XMLDataMiner(DataMiner):
    """Concrete data miner for XML files."""

    def open_file(self, path: str) -> str:
        """Open XML file.

        Args:
            path: Path to XML file.

        Returns:
            XML file representation.
        """
        return f"XML({path})"

    def extract_data(self, data: str) -> str:
        """Extract data from XML.

        Args:
            data: XML file data.

        Returns:
            Extracted XML data.
        """
        return f"Parsed XML from {data}"

    def close_file(self, data: str) -> None:
        """Close XML file.

        Args:
            data: XML file data.
        """
        pass

    def analyze_data(self, data: str) -> dict[str, str]:
        """Analyze XML data with custom logic.

        Args:
            data: Extracted XML data.

        Returns:
            Custom XML analysis.
        """
        return {"status": "XML analyzed", "data": data, "format": "XML"}

analyze_data(data)

Analyze XML data with custom logic.

Parameters:

Name Type Description Default
data str

Extracted XML data.

required

Returns:

Type Description
dict[str, str]

Custom XML analysis.

Source code in src/design_patterns/behavioral/template_method.py
def analyze_data(self, data: str) -> dict[str, str]:
    """Analyze XML data with custom logic.

    Args:
        data: Extracted XML data.

    Returns:
        Custom XML analysis.
    """
    return {"status": "XML analyzed", "data": data, "format": "XML"}

close_file(data)

Close XML file.

Parameters:

Name Type Description Default
data str

XML file data.

required
Source code in src/design_patterns/behavioral/template_method.py
def close_file(self, data: str) -> None:
    """Close XML file.

    Args:
        data: XML file data.
    """
    pass

extract_data(data)

Extract data from XML.

Parameters:

Name Type Description Default
data str

XML file data.

required

Returns:

Type Description
str

Extracted XML data.

Source code in src/design_patterns/behavioral/template_method.py
def extract_data(self, data: str) -> str:
    """Extract data from XML.

    Args:
        data: XML file data.

    Returns:
        Extracted XML data.
    """
    return f"Parsed XML from {data}"

open_file(path)

Open XML file.

Parameters:

Name Type Description Default
path str

Path to XML file.

required

Returns:

Type Description
str

XML file representation.

Source code in src/design_patterns/behavioral/template_method.py
def open_file(self, path: str) -> str:
    """Open XML file.

    Args:
        path: Path to XML file.

    Returns:
        XML file representation.
    """
    return f"XML({path})"