I cannot deny that the Template Method Design Pattern is one of my favourites, mainly because it’s so simple and helps us get rid of duplicated code, but it also has some downsides. In this article, I’ll give you a concrete real-life example of this design pattern in action, and what some alternatives are.
Solving A Real Problem
My team provides a feature that allows customers to download some of their data to CSV or XLSX files. There are four types of data that they can download, and given their differences, they are implemented in four different ways, or flows.
We wanted to improve the way in which this data was downloaded, but it soon became clear that we needed to refactor the code we had before extending it.
Initially, I thought that all four download flows would be very different, but it turns out there was a single algorithm that could describe all of them:
- Get context data
- Get data to be downloaded
- Save data to file to be downloaded
- Upload file to the cloud
So, I refactored one flow at a time, making the algorithm super explicit in each of them. I ended up with four files like this.
class DownloadFlowOne
def run
context_data = get_context_data
data = get_data(context_data)
file = save_data(data)
upload_file(file)
end
private
# I'll let you imagine the code of the
# methods below :)
def get_context_data; end
def get_data(context_data); end
def save_data(data); end
def upload_file(file); end
# Other support methods were also needed
def format_filename; end
def update_upload_progress; end
def send_analytics; end
end
It turns out that most of the methods were very similar between the flow. Unsurprisingly, the main differences were on the get_data and save_data methods, as the data to be downloaded is different for each flow.
Having confirmed that all flows are essentially the same, implementing the Template Method Design Pattern becomes really easy.
Template Method Design Pattern
To implement the Template Method Design Pattern, we need a class that clearly describes the steps of an algorithm, but not the implementation. Then, subclasses inherit from the template class and implement the steps of the algorithm.
In my case, I created a Template class as shown below. Note that this class not only describes the algorithm, but it also implements all the common functionality. The only things that are explicitly undefined are the get_data and save_data methods:
class Template
def run
context_data = get_context_data
data = get_data(context_data)
file = save_data(data)
upload_file(file)
end
private
def get_data(context_data)
raise "Implement in subclass"
end
def save_data(data)
raise "Implement in subclass"
end
# These methods are implemented here
def get_context_data; end
def upload_file(file); end
def format_filename; end
def update_upload_progress; end
def send_analytics; end
end
And then, we just create the flow classes with the code that is different from each other:
class DownloadFlowOne < Template
def get_data(context_data)
# Concrete implementation for DownloadFlowOne
end
def save_data(data)
# Concrete implementation for DownloadFlowOne
end
end
Although this works fine, I was not happy with it. When we want to download some data, we invoke run on classes DownloadFlowOne, DownloadFlowTwo, and so on. However, if we inspect the code of these classes, the algorithm is hidden. And on top of that, whenever we change the base Template class, we propagate the change to all children by default. This is both the advantage and disadvantage of this approach.
So, I started thinking about alternatives to this.
Including Modules
Some languages, like Ruby, also allow using modules to include code in classes. This is a very flexible and powerful approach, which even helps as a workaround when multiple inheritance is not supported by the language.
We can take advantage of this and, instead of inheriting from the Template class, we could turn it into a module and import it into our specific classes. Something like:
class DownloadTypeOne
include CommonModule
def get_data(context_data)
# Concrete implementation for Type One Downloads
end
def save_data(data)
# Concrete implementation for Type One Downloads
end
end
In practice, this is the same as doing the Template Method Design Pattern, only that we do inheritance through module inclusion.
Composition
Whenever we want to avoid inheritance, composition is usually the best bet. The core idea of composition is to have different classes collaborate between them. One way of doing that is by passing an instance of a class by argument, so we can delegate tasks to it.
In our example, we’d have to create a DownloadAny class that, just like the template class, implements all the common logic for all download flows. The important difference is that this class receives an object to which it delegates methods get_data and save_data.
class DownloadAny
def run(delegate)
context_data = get_context_data
data = delegate.get_data(context_data)
file = delegate.save_data(data)
upload_file(file)
end
private
# These methods are implemented here
def get_context_data; end
def upload_file(file); end
def format_filename; end
def update_upload_progress; end
def send_analytics; end
end
And now we need to implement delegate classes instead, which have the same code as before, except that they do not inherit from the Template class. Usually, these classes would implement an interface. In languages like Ruby, though, we must rely on duck-typing.
class FlowOneDelegate
def get_data(context_data)
# Concrete implementation for DownloadFlowOne
end
def save_data(data)
# Concrete implementation for DownloadFlowOne
end
end
Finally, to execute a specific download flow, we pass an instance to our DownloadAny class:
delegate = FlowOneDelegate.new
DownloadAny.new.run(delegate)
Unlike with the Template Method Design Pattern, we directly invoke the class that has the main algorithm, at the cost of hiding the concrete implementation of the Delegate classes.
The advantage, however, is that changes to the main class are easier to control. Callers choose to use DownloadAny, and if changes to it would cause issues for the callers, we can switch to a different class instead. This is harder when the behaviour lives in a base class through inheritance.
What To Choose
All the options presented in this article are valid approaches to avoid repeating identical flows. They are just different. Which one we choose depends on what we want to achieve.
The Template Method Design Pattern approach is ideal if we want to avoid injecting dependencies to delegate the core logic, or if having a single class that handles all cases was not practical.
Instead, composition is great at separating concerns and making testing easier as long as we are fine passing dependencies and invoking the same class (with the right dependency) for all use cases.
Happy coding!
José Miguel
Share if you find this content useful, and Follow me on LinkedIn to be notified of new articles.

