Design Patterns Software Engineering

Design Patterns – Template Method

Pinterest LinkedIn Tumblr
 The code is available on GitHub here

With Template Method Pattern we encapsulate an algorithm by defining its steps in a superclass and let the subclasses extend any step they need proving their own specific implementation.

When to use

The Template Method Pattern is great fit when one of the following is true:

Use the Template Method Pattern if you have an algorithm and some of its steps needs to be implemented differently depending on the situation. The main structure of the algorithm stays the same.

Usually, frameworks have many core algorithms that contains business logic in the form of steps. In many cases those algorithms needs to be adapted in the needs of each client that integrates with them. Utilizing the pattern makes a framework easy to adapt with various client needs.

Use the Template Method Pattern if you have duplicated logic scattered across the application that has slight differences in terms of implementation details.

There is an opportunity of refactoring when you come across similar behavior in different parts of the software but with some aspects implemented differently. Using the Template Method Pattern makes the software more extensible and reduces potential bugs to a single class.

How to implement

The Template Method Pattern uses inheritance in order to extend behavior. Before we explore the role of the subclasses, first we have to define the steps of our algorithm in an abstract class. The algorithm could contain any business logic our application might have. Those steps will be encapsulated in a method, the Template Method.

Some of the steps can have default implementations allowing the subclasses to provide their own, others have implementations that are not allowed to be altered and others don’t have implementation at all and the subclasses must hook their own implementation.

The subclasses can extend the steps of the algorithm in order to give different implementations in various aspects of it. However, they cannot alter its basic structure and purpose.

All steps are represented by methods. Below we see an example containing all kinds of steps inside the TemplateMethod() that contains the algorithm.

// This method defines the outline of the algorithm.
public void TemplateMethod(){
   StepImplementedOnlyInSuperclass();
   StepWithDefaultImplementation();
   StepWithoutImplementation();
}

// This method is implemented only in superclass.
private void StepImplementedOnlyInSuperclass() { /* implementation.. */ }

// This method has a default implementation, but subclasses can override it and provide their own.
protected virtual void StepWithDefaultImplementation() { /* a default implementation*/ }

// This method must be extended by subclasses.
protected abstract void StepWithoutImplementation(); 

The main challenge implementing the pattern is to recognize the algorithm and write the steps that is synthesized from.

The class diagram of the Template Method Pattern shows the various entities needed for the pattern. The AbstractClass contains the main template method with various steps ( private or protected ). The subclasses can extend any method they need in order to give the main algorithm the desired behavior.

Template Method Pattern class diagram. Every concrete subclass can extend any step it needs.

Hook Methods

Another concept in Template Method Pattern is a hook method which is a step/method of the algorithm, usually empty. This provides to the subclasses the opportunity to override the hook method and provide an additional logic.

This is not the same as providing another implementation of a default method, but its an additional logic we would like to plug in. Hook methods are the extensibility points that makes it possible. A good case of a hook method would be adding additional logic before or after a computation. For example, examine the code below:

public abstract class Content
{
   // The template method containing the step of the algorithm
   public void Render()
   {
      Initialize();
      OnInit(); // a hook method
      AfterContentInit(); // a hook method
      RenderContent();
      AfterRender();// another hook method
   }

   // A required method for the render algorithm.
   private void Initialize() { /* make the proper initializations. */ }

   // Hook method. Runs after initialization of all properties of the component.
   // Override this method to handle any additional initialization tasks.
   protected virtual void OnInit() { }

   // Hook method. Runs after content is fully initialized and validated.
   private void AfterContentInit() { }

   // A method that renders the contents.
   private void RenderContent() { /* rendering. */ }

   // Hook method. Is called after the component has completed rendering all content.
   private void AfterRender() { }
}

The Render() is the Template Method. Inside that method, there are steps that are mandatory to the algorithm, like Initialize() and RenderContent(). The other methods are called before or after certain computation is started or finished. Those hooks are also called lifecycle hooks.

The steps of the Render algorithm modeled in Content abstract class. The diagram shows the steps in the form of a pipeline and how subclasses can alter certain aspects of the main algorithm by overriding some of the steps.

Real World Example

A real world example could be found in angular – https://angular.io/guide/lifecycle-hooks – where the template method with lifecycle hooks has been used in order to give the subclass more control over the lifecycle of a component, how is constructed, what happens when something is changed and if there are additional properties that need to be destroyed.

Example – Report Processor

Now that we understand when and how to implement the Template Method Pattern, we will build a ReportProcessor that handles the execution of various reports. These reports are used for exporting useful information from various types of data with different formats.

Despite the variability of the formats and the result type, the main algorithm of reading data and extracting a result from those data is common in all cases.

The main Algorithm

The main algorithm steps are the following:

  1. ReadRecords() – Extract the data from a file.
  2. ProcessRecords() – Use the records to extract useful information.
  3. SendResult() – We send the result to a destination, depending on the type of data we processed.

The main abstract Report class that contains the template method is shown below:

public abstract class Report
{
   protected readonly string _filePath;

   protected Report(string filePath)
   {
      _filePath = filePath;
   }

   // This is the template method that contains the algorithm.
   public void Run()
   {
      var records = ReadRecords();
      var result = ProcessRecords(records);
      SendResult(result);
   }

   // These methods must be implemented by subclasses.
   protected abstract List<string[]> ReadRecords();
   protected abstract object ProcessRecords(List<string[]> records);

   // Method with default implementation.
   protected virtual void SendResult(object result) => Console.WriteLine(result.ToString());
}

As we can see the methods ReadRecords() and ProcessRecords() are abstractly defined in the Report. That means they must be implemented from the subclasses. However, the SendResult() method has a default implementation that sends the result to the console.

According to requirements we have the following cases:

The table above represents the requirements that we have. A product report should read from a JSON file and find the product with the most orders. A customer report should read a CSV file and calculate the average age of all records in the file.

In the image below, we show the different implementations we have to do if we don’t implement the Template Method Pattern. One of the challenges the pattern has and an opportunity for refactoring is to examine where a common algorithm emerges from different parts of the application, model it into an abstract class and then let the subclasses extend various aspects of it. This way we can eliminate duplicated code reducing possible bugs.

Creating the subclasses, ProductReport and CustomerReport

Next, we create the first subclass, ProductReport that implements the ReadRecords() reading the data from a JSON file and the ProcessRecords() that returns the product with the most orders. It also overrides the SendResult() method because in this case we want to store the result into a file and not direct it to the console.

public class ProductReport : Report
{
   private readonly string _outputPath;

   public ProductReport(string filePath, string outputPath) : base(filePath) {
      _outputPath = outputPath;
   }

   // Reads a JSON file. Uses the Product class as the model for deserialization.
   protected override List<string[]> ReadRecords()
   {
      var json = File.ReadAllText(this._filePath);
      var products = JsonConvert.DeserializeObject<List<Product>>(json);

      List<string[]> records = new();
      foreach(var product in products)
         records.Add(new string[] { product.Name, product.Orders.ToString() });
      return records;
   }

   // Finds the product with the most orders.
   protected override object ProcessRecords(List<string[]> records)
   {
      string productWithMostOrders = string.Empty;
      int maxOrders = 0;
      foreach (var record in records)
      {
         int orders = Convert.ToInt32(record[1]);
         if (maxOrders < orders)
         {
            maxOrders = orders;
            productWithMostOrders = record[0];
         }
      }
      return productWithMostOrders;
   }

   protected override void SendResult(object result)
      => File.WriteAllText(_outputPath, result.ToString());

}

public class Product
{
   public string Name { get; set; }
   public int Orders { get; set; }
}

The other subclass that extends the Report is the CustomerReport class and will be used for exporting reports for customers. That class implements the ReadRecords() reading the data from a CSV file and the ProcessRecords() that returns the average age of all customer records. In this case we don’t override the SendResult() method from the base class. Its default implementation is what we want.

public class CustomerReport : Report
{
   public CustomerReport(string filePath) : base(filePath) { }

   // Reads a CSV file.
   protected override List<string[]> ReadRecords()
   {
      List<string[]> records = new();
      var lines = File.ReadAllLines(_filePath).Skip(1);
      foreach (var line in lines)
      {
         string[] values = line.Split(',');
         records.Add(values);
      }
      return records;
   }

   // Calculates the average age of all records.
   protected override object ProcessRecords(List<string[]> records)
   {
      decimal sum = 0m;
      foreach(var record in records)
      {
         sum += Convert.ToDecimal(record[1]);
      }
      return sum / records.Count;
   }
}

In this point we have the abstract class that contains the Template Method and two subclasses that implement two different cases.

Usage example

Finally, we put all parts together and create two different report objects, CustomerReport and ProductReport as shown below:

public static void Main()
{
   var customerReport = new CustomerReport("../../../customers.csv");
   var productReport = new ProductReport("../../../products.json", "../../../product_report.txt");

   var reportProcessor = new ReportProcessor(customerReport, productReport);
   reportProcessor.ProcessReports();
}

public class ReportProcessor
{
   private readonly List<Report> _reports;

   public ReportProcessor(params Report[] reports)
   {
      _reports = reports.ToList();
   }

   public void ProcessReports()
      => _reports.ForEach(x => x.Run());
}

The ReportProcessor in the previous code is a simple component that iterates through all reports and call their Run() methods. After executing the previous code we get the following result:

33

The previous result is the average age of all customer records inside the customers.csv file. The CustomerReport didn’t override the SendResult() method so the result is printed in the console. On the other hand, the ProductReport has applied a different implementation for the SendResult() method redirecting the result to the product_report.txt file. The full code containing the files with sample data is found on GitHub.

Using Dependency Injection

Below, we show how we can use the Template Method Pattern with various Dependency Injection containers. The common approach for all of them is to register the multiple subclasses as the same abstract class.

Autofac

C#

var builder = new ContainerBuilder();
// first we register them as the same service.
builder.RegisterType<CustomerReport>().Keyed<Report>("customer-report-key");
builder.RegisterType<ProductReport>().Keyed<Report>("product-report-key");

// later we can resolve any of them with their key.
var customerReport = container.ResolveKeyed<Report>("customer-report-key");
var productReport = container.ResolveKeyed<Report>("product-report-key");

Recommendations

If you want to learn more, refresh or better understand Design Patterns and Software Design in general, I also recommend reading the following books:

A must in Design Patterns:

A very well written book with plenty of examples and insights:

* This site contains product affiliate links. We may receive a commission if you make a purchase after clicking on one of these links.

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.