Template Method Design Pattern And Alternatives


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.


Leave a Reply

Your email address will not be published. Required fields are marked *