YouTip LogoYouTip

Python Template Method

The Template Method Pattern is a behavioral design pattern that defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. The template method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

\n

Simply put, it's like a recipe for cooking: the recipe specifies the order of steps (wash vegetables, chop vegetables, stir-fry, plate), but how exactly to wash, chop, and stir-fry can be implemented by different chefs according to their own style.

\n
\n

Why Do We Need the Template Method Pattern?

\n

Code Reuse

\n

The Template Method Pattern moves invariant behavior to the superclass, avoiding code duplication in subclasses. Multiple subclasses can share the same algorithm structure defined by the template method.

\n

Extensibility

\n

Subclasses can extend or modify part of the algorithm's behavior by overriding hook methods or concrete step methods, without changing the overall structure of the algorithm.

\n

Inversion of Control

\n

The parent class controls the execution flow of the algorithm, while subclasses only need to focus on the specific steps they need to implement. This realizes the "Hollywood Principle" β€” "Don't call us, we'll call you."

\n
\n

Structure of the Template Method Pattern

\n

Let's understand the structure of the Template Method Pattern through a class diagram:

\n

Image 1

\n

Core Component Descriptions

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ComponentResponsibilityDescription
AbstractClassDefines the algorithm skeletonContains template methods and abstract step methods
ConcreteClassImplements concrete stepsImplements abstract methods defined by the parent class
template_methodTemplate methodDefines the invariant part of the algorithm
step1, step2Abstract methodsConcrete steps that need to be implemented by subclasses
hookHook methodOptional step that subclass can choose whether to override
\n
\n

Basic Syntax and Implementation

\n

Definition of Abstract Base Class

\n

Examples

\n
from abc import ABC, abstractmethod\n\nclass AbstractClass(ABC):\n    """Template MethodAbstract Base Class of the Pattern"""\n\n    def template_method(self):\n        """Template Method - Define Algorithm Skeleton"""\n        self.step1()\n        self.step2()\n        self.hook()\n        self.step3()\n\n    @abstractmethod\n    def step1(self):\n        """Abstract Method 1 - Must Be Implemented by Subclasses"""\n        pass\n\n    @abstractmethod\n    def step2(self):\n        """Abstract Method 2 - Must Be Implemented by Subclasses"""\n        pass\n\n    def step3(self):\n        """Concrete Method - Already has Default Implementation"""\n        print("Execute Step 3 - Default Implementation")\n\n    def hook(self):\n        """Hook Method - Optional Step, Subclasses May Choose to Override"""\n        print("Execute Hook Method - Do Nothing by Default")\n
\n

Implementation of Concrete Subclasses

\n

Examples

\n
class ConcreteClassA(AbstractClass):\n    """Concrete Implementation Class A"""\n\n    def step1(self):\n        print("ConcreteClassA - Execute Step 1")\n\n    def step2(self):\n        print("ConcreteClassA - Execute Step 2")\n\n    def hook(self):\n        print("ConcreteClassA - Override Hook Method to Add Extra Functionality")\n\nclass ConcreteClassB(AbstractClass):\n    """Concrete Implementation Class B"""\n\n    def step1(self):\n        print("ConcreteClassB - Execute Step 1")\n\n    def step2(self):\n        print("ConcreteClassB - Execute Step 2")\n    # Do Not Override Hook Method, Use Default Implementation\n
\n
\n

Practical Application Examples

\n

Let's explore the application of the Template Method Pattern through several practical examples.

\n

Example 1: Data Processing Template

\n

Examples

\n
from abc import ABC, abstractmethod\nimport json\nimport csv\n\nclass DataProcessor(ABC):\n    """Data Processing Template"""\n\n    def process_data(self, input_file, output_file):\n        """Data Processing Template Method"""\n        print(f"Start Processing Data: {input_file} -> {output_file}")\n        \n        # Read Data\n        data = self.read_data(input_file)\n        print(f"Read to {len(data)} Data Records")\n        \n        # Transform Data\n        transformed_data = self.transform_data(data)\n        print("Data Conversion Complete")\n        \n        # Save Data\n        self.save_data(transformed_data, output_file)\n        print("Data Saving Completed")\n        \n        # Cleanup\n        self.cleanup()\n\n    @abstractmethod\n    def read_data(self, file_path):\n        """Read Data - Abstract Method"""\n        pass\n\n    @abstractmethod\n    def transform_data(self, data):\n        """Transform Data - Abstract Method"""\n        pass\n\n    def save_data(self, data, file_path):\n        """Save Data - Concrete Method"""\n        with open(file_path, 'w', encoding='utf-8') as f:\n            if isinstance(data, list):\n                for item in data:\n                    f.write(str(item) + 'n')\n            else:\n                f.write(str(data))\n\n    def cleanup(self):\n        """Cleanup - Hook Method"""\n        print("Cleanup Complete")\n\nclass JSONProcessor(DataProcessor):\n    """JSON Data Processor"""\n\n    def read_data(self, file_path):\n        with open(file_path, 'r', encoding='utf-8') as f:\n            return json.load(f)\n\n    def transform_data(self, data):\n        # Simple Transformation: Convert All String Values to Uppercase\n        if isinstance(data, dict):\n            return {k: v.upper() if isinstance(v, str) else v \n                   for k, v in data.items()}\n        elif isinstance(data, list):\n            return [item.upper() if isinstance(item, str) else item \n                   for item in data]\n        return data\n\nclass CSVProcessor(DataProcessor):\n    """CSV Data Processor"""\n\n    def read_data(self, file_path):\n        data = []\n        with open(file_path, 'r', encoding='utf-8') as f:\n            reader = csv.DictReader(f)\n            for row in reader:\n                data.append(row)\n        return data\n\n    def transform_data(self, data):\n        # Add Processing Timestamp to Each Record\n        import datetime\n        timestamp = datetime.datetime.now().isoformat()\n        for record in data:\n            record['processed_at'] = timestamp\n        return data\n\n    def cleanup(self):\n        """Override Cleanup Method, Add Extra Cleanup Logic"""\n        print("CSV Processor Cleanup Completed")\n        print("Release CSV Parser Resources")\n\n# Usage example\nif __name__ == "__main__":\n    # Create Test Data\n    import os\n    \n    # Test JSON Processor\n    json_data = {'name': 'john', 'age': 30, 'city': 'new york'}\n    with open('test.json', 'w') as f:\n        json.dump(json_data, f)\n    \n    json_processor = JSONProcessor()\n    json_processor.process_data('test.json', 'output_json.txt')\n    \n    print("n" + "="*50 + "n")\n    \n    # Test CSV Processor\n    csv_data = "name,age,citynjohn,30,new yorknjane,25,los angeles"\n    with open('test.csv', 'w') as f:\n        f.write(csv_data)\n    \n    csv_processor = CSVProcessor()\n    csv_processor.process_data('test.csv', 'output_csv.txt')\n    \n    # Clean Up Test Files\n    for file in ['test.json', 'test.csv', 'output_json.txt', 'output_csv.txt']:\n        if os.path.exists(file):\n            os.remove(file)\n
\n

Example 2: Beverage Making Template

\n

Examples

\n
from abc import ABC, abstractmethod\n\nclass BeverageMaker(ABC):\n    """Beverage Making Template"""\n\n    def make_beverage(self):\n        """Template Method for Making Beverages"""\n        self.boil_water()\n        self.brew()\n        self.pour_in_cup()\n        if self.customer_wants_condiments():\n            self.add_condiments()\n        self.serve()\n\n    def boil_water(self):\n        """Boil Water - Concrete Method"""\n        print("Boil Water")\n\n    @abstractmethod\n    def brew(self):\n        """Brew - Abstract Method"""\n        pass\n\n    def pour_in_cup(self):\n        """Pour into Cup - Concrete Method"""\n        print("Pour into Cupin")\n\n    @abstractmethod\n    def add_condiments(self):\n        """Add Condiments - Abstract Method"""\n        pass\n\n    def customer_wants_condiments(self):\n        """Hook Method - Does the Customer Want Condiments"""\n        return True\n\n    def serve(self):\n        """Serve Beverage - Concrete Method"""\n        print("Beverage Ready, Enjoy!")\n\nclass CoffeeMaker(BeverageMaker):\n    """Coffee Making"""\n\n    def brew(self):\n        print("Brew Coffee Powder with Boiling Water")\n\n    def add_condiments(self):\n        print("Add Sugar and Milk")\n\n    def customer_wants_condiments(self):\n        answer = input("Does the Coffee Need Sugar and Milk?(y/n): ")\n        return answer.lower() == 'y'\n\nclass TeaMaker(BeverageMaker):\n    """Tea Making"""\n\n    def brew(self):\n        print("Steep Tea Leaves in Boiling Water")\n\n    def add_condiments(self):\n        print("Add Lemon")\n\n# Usage example\nif __name__ == "__main__":\n    print("Make Coffee:")\n    coffee = CoffeeMaker()\n    coffee.make_beverage()\n    \n    print("n" + "="*30 + "n")\n    \n    print("Make Tea:")\n    tea = TeaMaker()\n    tea.make_beverage()\n
\n
\n

Variations of the Template Method Pattern

\n

1. Template Method with Parameters

\n

Examples

\n
class ConfigurableProcessor(ABC):\n    """Configurable Data Processor"""\n\n    def process_with_config(self, input_file, output_file, config):\n        """Template Method with Configuration Parameters"""\n        self.validate_config(config)\n        data = self.read_data(input_file)\n        processed_data = self.process_with_config_impl(data, config)\n        self.save_data(processed_data, output_file)\n        self.post_process(config)\n\n    def validate_config(self, config):\n        """Validate Configuration - Concrete Method"""\n        required_keys = ['format', 'encoding']\n        for key in required_keys:\n            if key not in config:\n                raise ValueError(f"Missing Required Keys in Configuration: {key}")\n\n    @abstractmethod\n    def process_with_config_impl(self, data, config):\n        """Implementation for Processing Data Using Configuration"""\n        pass\n\n    def post_process(self, config):\n        """Postprocessing - Hook Method"""\n        if config.get('cleanup', False):\n            print("Execute Cleanup Operations")\n
\n

2. Multi-Step Template Method

\n

Examples

\n
class MultiStepProcessor(ABC):\n    """Multi-Step Processor"""\n\n    def complex_processing(self):\n        """Complex Processing Workflow"""\n        self.initialize()\n        self.pre_process()\n        self.main_process()\n        self.post_process()\n        self.finalize()\n\n    def initialize(self):\n        print("Initialize Processor")\n\n    def pre_process(self):\n        """Preprocessing - Hook Method"""\n        pass\n\n    @abstractmethod\n    def main_process(self):\n        """Main Processing Logic"""\n        pass\n\n    def post_process(self):\n        """Postprocessing - Hook Method"""\n        pass\n\n    def finalize(self):\n        print("Processing Completed")\n
\n
\n

Best Practices and Considerations

\n

1. Reasonable Use of Abstract Methods

\n
    \n
  • Only declare methods that truly need to be implemented by subclasses as abstract methods
  • \n
  • Provide hook methods with default implementations for optional steps
  • \n
\n

2. Access Control of Template Methods

\n
    \n
  • Template methods should usually be declared as final (in Python, this can be done through naming conventions)
  • \n
  • Step methods should be protected to avoid being called directly from outside
  • \n
\n

3. Error Handling

\n

Examples

\n
class RobustTemplate(ABC):\n    """Robust Template Class"""\n\n    def template_method(self):\n        try:\n            self.setup()\n            self.execute_steps()\n        except Exception as e:\n            self.handle_error(e)\n        finally:\n            self.cleanup()\n\n    def execute_steps(self):\n        """Execute Step Sequence"""\n        self.step1()\n        self.step2()\n        self.step3()\n\n    def handle_error(self, error):\n        """Error Handling - Hook Method"""\n        print(f"Error Occurred During Processing: {error}")\n\n    @abstractmethod\n    def setup(self):\n        pass\n\n    @abstractmethod\n    def step1(self):\n        pass\n\n    @abstractmethod\n    def step2(self):\n        pass\n\n    @abstractmethod\n    def step3(self):\n        pass\n\n    def cleanup(self):\n        print("Clean Up Resources")\n
\n
\n

Practice Exercises

\n

Now it's your turn to practice! Please complete the following exercises to consolidate your understanding of the Template Method Pattern:

\n

Exercise 1: Implement a File Export Template

\n

Create a file export template that supports exporting to different formats (TXT, HTML, JSON).

\n

Requirements:

\n
    \n
  • Define abstract base class FileExporter
  • \n
  • Implement concrete subclasses TXTExporter, HTMLExporter, JSONExporter
  • \n
  • The template method should include: preparing data, formatting data, saving file, post-processing, and other steps
  • \n
\n

Exercise 2: Improve the Beverage Making Template

\n

Expand the previous beverage making template by adding the following features:

\n
    \n
  • Support selecting beverage size (small, medium, large)
  • \n
  • Add price calculation functionality
  • \n
  • Support custom condiments
  • \n
\n
\n

Summary

\n

The Template Method Pattern is a powerful and practical design pattern that helps us build better code in the following ways:

\n

Main Advantages

\n
    \n
  1. Improves code reusability: Places common code in the parent class
  2. \n
  3. Improves extensibility: Extends specific steps through subclasses
  4. \n
  5. Conforms to the Open/Closed Principle: Open for extension, closed for modification
  6. \n
  7. Inversion of control: Parent class controls the flow, subclasses implement the details
  8. \n
\n

Applicable Scenarios

\n
    \n
  • Multiple classes have the same methods, but with different implementations
  • \n
  • Need to control extension points of subclasses
  • \n
  • Need to define algorithm skeleton,
  • \n
← Python Chain Of ResponsibilityPython Strategy β†’