Skip to Content
Course content

378: The Template Method Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this happen a dozen times during code reviews. You're building a system that handles different types of data imports—maybe from a CSV, a JSON API, and an XML feed. On the surface, the process is always the same: you open the file, validate the schema, transform the data, and save it to the database.

Here is a common way a developer might implement this. It looks clean at first, but it's actually a maintenance time bomb.

class DataImporter:
    def import_data(self):
        pass

class CSVImporter(DataImporter):
    def import_data(self):
        print("Opening CSV file...")
        print("Validating CSV columns...")
        print("Transforming CSV rows to objects...")
        print("Saving to database...")
        print("Closing CSV file...")

class JSONImporter(DataImporter):
    def import_data(self):
        print("Opening JSON API connection...")
        print("Validating JSON schema...")
        print("Transforming JSON keys to objects...")
        print("Saving to database...")
        print("Closing JSON connection...")

The Maintenance Trap of Duplicate Orchestration

The code above "works," but look closely at the import_data methods. Both classes are doing the exact same orchestration: Open → Validate → Transform → Save → Close.

Now, imagine your boss tells you that we need to add a professional logging layer and a performance timer around the entire import process. In the current setup, you have to go into CSVImporter, JSONImporter, and every other importer you've written to manually add those logs. If you miss one, you've got an inconsistent system. If you change the order of operations, you're hunting through five different files to make sure they all match. You've duplicated the algorithm, even though the algorithm is the same for everyone.

Defining the Skeleton with the Template Method

The Template Method pattern solves this by moving the "skeleton" of the algorithm into a base class. Instead of the subclasses deciding how to run the process, the base class dictates the sequence, and the subclasses simply fill in the blanks.

Here is how I would rewrite this. We define a "template method" (which I've called run_import) that is final in spirit—subclasses shouldn't touch it. Then, we define the specific steps as methods that the subclasses must implement.

from abc import ABC, abstractmethod

class DataImporter(ABC):
    # This is the Template Method. 
    # It defines the fixed skeleton of the algorithm.
    def run_import(self):
        self._log_start()
        self.open_source()
        self.validate()
        self.transform()
        self.save_to_db()
        self.close_source()
        self._log_end()

    def _log_start(self):
        print("LOG: Starting import process...")

    def _log_end(self):
        print("LOG: Import process completed successfully.")

    def save_to_db(self):
        print("Saving to database (this is common logic for all importers)...")

    @abstractmethod
    def open_source(self):
        pass

    @abstractmethod
    def validate(self):
        pass

    @abstractmethod
    def transform(self):
        pass

    @abstractmethod
    def close_source(self):
        pass

class CSVImporter(DataImporter):
    def open_source(self):
        print("Opening CSV file...")

    def validate(self):
        print("Validating CSV columns...")

    def transform(self):
        print("Transforming CSV rows to objects...")

    def close_source(self):
        print("Closing CSV file...")

class JSONImporter(DataImporter):
    def open_source(self):
        print("Opening JSON API connection...")

    def validate(self):
        print("Validating JSON schema...")

    def transform(self):
        print("Transforming JSON keys to objects...")

    def close_source(self):
        print("Closing JSON connection...")

# Usage
csv = CSVImporter()
csv.run_import()

Notice what happened here. The CSVImporter no longer knows (or cares) that it needs to log the start and end or that it needs to call save_to_db. It only focuses on the parts that are unique to CSVs.

I call this the "Hollywood Principle": Don't call us, we'll call you. The base class calls the subclass methods when it's ready for them. If I want to add a timer to the whole process now, I only have to change one line of code in the DataImporter.run_import method, and every single importer in the system is instantly upgraded. That is the power of the Template Method.




📋 Practical Task

Build a Multi-Format Document Generator

You need to create a document generation system that supports both HTML and PDF outputs. Regardless of the format, the generation process must always follow these steps in order:

  • Initialize the document metadata
  • Create the header
  • Generate the main body content
  • Append the footer
  • Finalize and save the file

Your Task:

  1. Create an abstract base class DocumentGenerator with a template method called generate_document() that orchestrates the five steps above.
  2. Implement a method in the base class for initialize_metadata() since this is identical for all documents (e.g., it just prints "Setting document metadata...").
  3. Define the remaining four steps as abstract methods.
  4. Create two subclasses: HTMLGenerator and PDFGenerator. Implement the abstract methods to print specific messages related to that format (e.g., HTMLGenerator.create_header() should print "Creating <header> tags...").
  5. Instantiate both generators and call generate_document() on each to verify the sequence is identical but the implementation details differ.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.