The software engineering process often reveals to us many redundant elements inside the structure and code of our software. Knowing this, some of the primary responsibilities of a developer are to write easily understandable and maintainable code - not just solutions.
However, projects often get more complex with time, making the initial software structure a critical element that must be well thought-out and implemented from the very beginning.
Design Patterns are intended to solve this issue.
Design patterns are a set of standards used to optimize specific tasks related to the Object Oriented Programming (OOP) paradigm. They aim to reduce the overall count of code lines, optimize structures, and standardize the software architecture.
The Object-Oriented Programming paradigm delivers a structure based on classes, where each class represents a blueprint for an object (instance of that class) that has its own attributes and methods. These classes are related and have their own dependencies, compositions, inheritance, etc. Translating real-life problems and structures to software solutions is the primary motivation for implementing this structure.
In this guide, we’ll explore one of the Behavioral Design Patterns and its implementation in Python: the Template Method.
This design pattern will give us a general method composed of multiple steps. The classes related to our template method class can then call these steps individually or overwrite them.
In addition, we’ll learn how to use the ABC Library in Python, which defines an inheritance relationship for abstract base classes (ABCs). We'll use this library to create a simple template method example.
Behavioral Design Patterns
The Template Method is a Behavioral Design Pattern. What is a Behavioral Design pattern exactly?
There are three widely recognized software design patterns: Creational, Structural, and Behavioral.
Creational Design Patterns are intended to enable the creation of objects while abstracting/hiding away the object's creation logic. Creational Design Patterns are used to give programs greater flexibility in selecting the objects they should create for any use case.
Structural Design Patterns are intended to handle the composition of objects and classes, relying on inheritance to control how objects are created and given functions.
Behavioral Design Patterns are focused on the communication that occurs between objects, controlling how data moves between objects, and distributing behavior between classes.
The Template Method is focused on the distribution of functions and methods across classes.
The Template Method Design Pattern
The template method design pattern allows us to create a base class that contains some number of steps needed to complete a process. When you define these steps with a template, it becomes possible to create one or more concrete classes and overwrite the template steps. This lets you implement some or all of the steps, depending on the concrete class, without overwriting the entire process.
To use the template method, we require an abstract class. The abstract class is essentially one major process broken up into smaller steps or minor processes. To put that another way, the abstract class will make use of the template method (the major process) and within the template we’ll find calls to the minor steps that complete the major process. These minor processes will be methods/functions that the concrete classes can call.
Using an abstract class means we don’t have to instantiate the whole base class to get access to the steps defined with the template method. Instead, we can create subclasses from the abstract class and overwrite just the steps we need in the individual subclasses.
Once we define the abstract class, we can create the concrete classes that will overwrite the steps we need. We’ll use an inheritance relationship to achieve this. Depending on the context of the concrete class, we will overwrite all the steps or just some of them.
We can represent the structure of the template method with a class diagram like this, in accordance with the OOP paradigm:
Here you can see that we begin by creating an abstract class with a template method consisting of multiple steps/functions. From this abstract class, we create two concrete classes that use different steps of the template method.
The Template Method vs the Factory Method
There's some confusion regarding the differences between the Template method Pattern and the Factory Method Pattern. This is because their structure is similar, although they aren't the same thing. The Factory Method is a Creational Pattern used to create objects from a superclass. In contrast, the Template Method is a Behavioral Pattern used to define a general method composed of steps that can be modified by subclasses of the abstract class, containing the template method.
In other words, while the Factory Method creates objects, the Template Method overwrites the functionalities of a major/base process.
Now that we've clarified the difference between these patterns, we can explore how to implement the Template Method Design Pattern in Python.
Note: Python doesn’t support abstract classes without using a specific library. To use abstract class associations, we need to import the ABC library.
The ABC Library
The ABC library provides infrastructure for managing abstract base classes in Python. This means we can create class relationships like inheritance or implementations for abstract classes - something required for implementing most design patterns and particularly important in the Template Method's case.
When to Use the Template Method Pattern?
You'll want to use the Template Method when you need to use or modify some or all of the steps of an algorithm. In these cases, you'll need to differentiate the steps of your algorithm or process, making them individually accessible via inheritance or implementation.
Let's take a look at a practical example:
We have two groups of researchers, one from University A and another from University B. These two groups are studying the effects of quarantine, implemented by governments in response to the SARS‑CoV‑2 pandemic. Both groups have the same base research process. The basic research process is a template that the two research groups can use to proceed with their investigation. Yet the research groups can customize the research process in terms of:
- Which steps are carried out during the research.
- How each research step is carried out.
Let's represent this research with a class diagram before creating the model with Python code.
The research guideline is composed of 4 steps:
- University A decides to apply 2 of the 4 steps (2 and 3)
- University B applies the 3 of the steps (1, 3 and 4)
- Both groups modified all the chosen steps.
- Finally, step number 3 must be applied for both groups as it is mandatory.
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
We already have our diagram class, we just have to change it so it fits with our problem.
Updating the diagram to fit the conditions we specified, we end up with the following model:
Implementing the Template Method Design Pattern in Python
Now that we have the outline for our abstract and concrete classes, let's implement them in Python.
Let's start with our abstract class -
researchGuideline.py, which will contain our template methods with the four principal steps for the research.
First, we'll import the ABC library. This library contains a class called
ABC, and we'll use it as a superclass for our research template, turning it into an abstract base class.
Next, we'll define our steps as class methods. These methods will be empty for now, but when we define the subclasses - they will overwrite the steps:
# Importing the ABC library from abc import ABC, abstractmethod # Creating our abstract class: class ResearchGuideline(ABC): # Template Method definition: def templateMethod(self): # Calling all the steps self.step1() self.step2() self.step3() self.step4() # Defining the Template Method Steps def step1(self): pass def step2(self): pass def step3(self): pass def step4(self): pass
Notice how we added the
@abstractmethod decorator to step 3. This shows that an abstract class' subclasses must always overwrite that method. We must include this decorator in the imports, as it’s also part of the ABC library.
Let's define our concrete classes now. We are talking about Universities A and B, with their respective steps. We will create a subclass for both of the universities by using the
For both classes, we have to import the
ResearchGuideline class and create an inheritance between the superclass and the subclass. This allows us to use the steps we defined in the guideline/template and overwrite them. The application of the steps will be a simple log/print in this case.
Let's start with the first subclass:
from researchGuideline import ResearchGuideline class UniversityA(ResearchGuideline): def step2(self): print("Step 2 - Applied by University A") def step3(self): print("Step 3 - Applied by University A")
We'll save this in a Python file called
universityA. Now let's set up the second subclass:
from researchGuideline import ResearchGuideline class UniversityB(ResearchGuideline): def step1(self): print("Step 1 - Applied by University B") def step3(self): print("Step 3 - Applied by University B") def step4(self): print("Step 4 - Applied by University B")
We'll save this in a Python file called
Notice that we've indicated which university is applying which steps. This helps us appreciate the variation between the two concrete classes.
Our model of the template method, including the abstract class and concrete classes, is done! Now let's create our client script, so that we can apply the model.
First, let's import our classes. This involves importing the abstract class and the two concrete classes. Then, we'll create a function that receives a
ResearchGuideline object as a parameter, which is our template/abstract class.
Here's the beauty of the inheritance relationship - because the university classes are subclasses from
ResearchGuideline they share the same object type.
We can pass either the
UniversityB object as the argument into our function that calls template method (this is
client_call() below), and the steps overwritten by the concrete class will change how the template method executes.
Here we use both classes, so we can compare the outputs:
# Imports from researchGuideline import * from universityA import UniversityA from universityB import UniversityB # Auxiliary function def client_call(research_guideline: ResearchGuideline): research_guideline.templateMethod(); # Entry point if __name__ == '__main__': # Calling the Template Method using the University A class as parameter print("University A:") client_call(UniversityA()) # Calling the Template Method using the University A class as parameter print("University B:") client_call(UniversityB())
Running this code, we obtain the following output:
University A: Step 2 - Applied by University A Step 3 - Applied by University A University B: Step 1 - Applied by University B Step 3 - Applied by University B Step 4 - Applied by University B
The template method is an effective way to distribute tasks between classes, redefine processes, and reduce code. Applying this design pattern to an algorithm or solution can help you avoid redundant methods and streamline longer execution processes.
The template method is also an example of the proper use of the OOP paradigm. The model can't be applied in every instance, so be sure to understand your project's needs before using it.