DEV Community

Cover image for Template Method in C#
Kostas Kalafatis
Kostas Kalafatis

Posted on • Originally published at dfordebugging.wordpress.com

Template Method in C#

The Template Method design pattern is a behavioral design pattern in which a superclass defines the skeleton of an algorithm but allows subclasses to override specific algorithm steps. It can be used to create adaptable algorithms. It also prevents subclasses from making incompatible changes to the algorithm.

In this pattern, a base class defines the basic steps of an algorithm and the order in which they are executed. These stages are declared as abstract methods that must be implemented by concrete subclasses. The algorithm is designed to be extendable, so subclasses can override particular parts of the process to tailor its behavior.

The template method pattern is beneficial when you have numerous classes that implement similar algorithms but with tiny changes in the details of each algorithm. Rather than repeating the code for each algorithm, the template method pattern allows you to factor out the common code into a base class, making it easier to maintain and update.

You can find the example code of this post, on GitHub

Conceptualizing the Problem

Imagine that we're creating a data analysis application that swifts through different analytics data. Users can feed the app with endpoints from various analytics producers, and it tries to extract meaningful data from the responses of the systems in a uniform format.

The first version of the application could work only with Google Analytics. In the following version it could support Hotjar Analytics. A month later, we taught it to retrieve Sitecore Analytics also.

Image description

At some point, we saw that a lot of the code in all three classes was the same. In each class, the code for dealing with different data formats was completely different, but the code for handling and analyzing data was almost the same. Wouldn't it be great to get rid of the duplicate code without changing the way the method works?

The client code that used these classes was also a source of trouble. It had a lot of conditionals that chose the right thing to do based on the class of the object being processed. If all three processing classes shared a common interface or base class, we could get rid of the conditionals in client code and use polymorphism when calling a processing object's functions.

The Template Method design suggests that we should break up an algorithm into a series of steps, turn these steps into methods, and put a series of calls to these methods inside a single template method. The steps may be abstract, or they may have some default implementation. To use the algorithm, the client should provide its own subclass, implement all abstract steps, and, if necessary, modify some of the optional steps (but not the template method itself).

Let's see how this works in our mining app. All three parsing methods can be built on the same base class. This class sets up a template method that consists of a number of calls to different steps in the document-processing process.

Image description

First, we can make all of the steps abstract, which means that subclasses must create their own code for these methods. In our case, subclasses already have all the appropriate implementations, so the only thing we might need to do is change the method signatures to match those of the methods in the superclass.

Now, let's see what we can do to get rid of the duplicate code. It looks like the code for opening and closing connections and extracting and parsing data is different for provider, so there's no reason to touch those methods. But the code for other steps, like processing raw data and writing reports, is very similar, so it can be pulled up into the base class and shared by subclasses.

There are three types of steps:

  • abstract steps
  • optional steps
  • hooks

Abstract steps, are the steps that must be implemented by every subclass, such as opening and closing connections with a data provider.

Optional steps, are the steps that have some default implementation, but still can be overriden if needed. Such a step could be the processing of raw data or writing the report.

Hooks, are optional steps with an empty body. A template method would work even if a hook isn't changed. Most of the time, hooks are put before and after important steps in algorithms. This gives subclasses more places to add to an algorithm.

Structuring the Template Method Pattern

In its base implementation, the Template Method has 2 participants:

  • Abstract Class: The Abstract Class defines methods that act as steps in an algorithm, as well as the actual template method that calls these methods in a certain sequence. Either the steps can be labeled abstract or they can have a default implementation.
  • Concrete Class: All of the steps can be changed by Concrete Classes, but not the template method itself.

Image description

To demonstrate how the Template Pattern works, we are going to create a small social media aggregator. In this example, our Template Method will define an algorithm for communicating with a social network. Subclasses that match a particular social network will implement these steps according to the specific network's API.

First we are going to define our Template Method participant, the Network class.

namespace TemplateMethod.SocialNetworks;

public abstract class SocialNetwork
{
    protected string username;
    protected string password;

    protected SocialNetwork()
    {
    }

    /// <summary>
    /// Publish a post to a social network.
    /// </summary>
    /// <param name="message">The message to post.</param>
    /// <returns>true if the message is successfully posted, false otherwise.</returns>
    public bool Post(string message)
    {
        if (!LogIn(username, password)) 
            return false;

        var result = SendData(Encoding.ASCII.GetBytes(message));
        LogOut();
        return result;

    }

    protected abstract bool LogIn(string username, string password);
    protected abstract bool SendData(byte[] data);
    protected abstract void LogOut();
}
Enter fullscreen mode Exit fullscreen mode

We first need to define an abstract SocialNetwork class, which will provide our framework for publishing posts to social networks. We will also define two protected members, username and password to use for the login process.

The Post method is defined as public and takes a string parameter message as input. This method is implemented in our Template Method class, since the posting mechanism is the same across all social networks. This method performs a login workflow, and if successful it sends the data to the social network and then logs out. Note that while the class implements the Post method, the specifics for the login, posting and logout are left for the concrete classes.

The LogIn, SendData and Logout methods are all marked as abstract meaning that they are not implemented in the SocialNetwork class and must be implemented by its derived classes. This way, derived classes can define their own workflows and logic depending on the API provided by each social network.

Now let's implement our concrete classes. First is the Facebook class:

public class Facebook : SocialNetwork
{
    public Facebook(string username, string password)
    {
        base.username = username;
        base.password = password;
    }

    protected override bool LogIn(string username, string password)
    {
        Console.WriteLine("\nChecking user's credentials");
        Console.WriteLine($"Name: {username}");
        Console.Write("Password: ");

        for(var i = 0; i < password.Length; i++)
            Console.Write("*");

        SimulateLatency();
        Console.WriteLine("\n\nFacebook login successful");
        return true;
    }

    protected override bool SendData(byte[] data)
    {
        Console.WriteLine($"Message was posted successfully on Facebook");
        return true;
    }

    protected override void LogOut()
    {
        Console.WriteLine($"User '{username}' was logged out from Facebook");
    }

    private void SimulateLatency()
    {
        var i = 0;
        var end = new Random().Next(5, 15);

        Console.WriteLine();
        while (i < end)
        {
            Console.Write(".");
            Thread.Sleep(new Random().Next(200, 800));
            i++;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Facebook class implements all of the abstract methods defined by the SocialNetwork. Note that while it can call the parent class's Post method, it will use its own implementations of Login, Logout and SendData.

Same goes for the Twitter class:

namespace TemplateMethod.SocialNetworks;

public class Twitter : SocialNetwork
{
    public Twitter(string username, string password)
    {
        base.username = username;
        base.password = password;
    }

    protected override bool LogIn(string username, string password)
    {
        Console.WriteLine("\nChecking user's credentials");
        Console.WriteLine($"Name: {username}");
        Console.Write("Password: ");

        for(var i = 0; i < password.Length; i++)
            Console.Write("*");

        SimulateLatency();
        Console.WriteLine("\n\nTwitter login successful");
        return true;
    }

    protected override bool SendData(byte[] data)
    {
        Console.WriteLine($"Message was posted successfully on Twitter");
        return true;
    }

    protected override void LogOut()
    {
        Console.WriteLine($"User '{username}' was logged out from Twitter");
    }

    private void SimulateLatency()
    {
        var i = 0;
        var end = new Random().Next(5, 15);

        Console.WriteLine();
        while (i < end)
        {
            Console.Write(".");
            Thread.Sleep(new Random().Next(200, 800));
            i++;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

And finally the DevTo class:

namespace TemplateMethod.SocialNetworks;

public class DevTo : SocialNetwork
{
    public DevTo(string username, string password)
    {
        base.username = username;
        base.password = password;
    }

    protected override bool LogIn(string username, string password)
    {
        Console.WriteLine("\nChecking user's credentials");
        Console.WriteLine($"Name: {username}");
        Console.Write("Password: ");

        for(var i = 0; i < password.Length; i++)
            Console.Write("*");

        SimulateLatency();
        Console.WriteLine("\n\nDevTo login successful");
        return true;
    }

    protected override bool SendData(byte[] data)
    {
        Console.WriteLine($"Message was posted successfully on DevTo");
        return true;
    }

    protected override void LogOut()
    {
        Console.WriteLine($"User '{username}' was logged out from DevTo");
    }

    private void SimulateLatency()
    {
        var i = 0;
        var end = new Random().Next(5, 15);

        Console.WriteLine();
        while (i < end)
        {
            Console.Write(".");
            Thread.Sleep(new Random().Next(200, 800));
            i++;
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now that we have our ConcreteClass participants, we are going to create our main class and run our program:

using TemplateMethod.SocialNetworks;

SocialNetwork network = null;

Console.Write("Enter your username: ");
var username = Console.ReadLine();
Console.Write("Enter your password: ");
var password = Console.ReadLine();
Console.Write("Enter your post message: ");
var message = Console.ReadLine();

Console.WriteLine(@"Choose the social network you want to post to:
                        1. Facebook\n
                        2. Twitter\n
                        3. Dev.to");
var choice = int.Parse(Console.ReadLine());

network = choice switch
{
    1 => new Facebook(username, password),
    2 => new Twitter(username, password),
    3 => new DevTo(username, password),
    _ => throw new Exception()
};

network.Post(message);
Enter fullscreen mode Exit fullscreen mode

The program first prompts the user to enter their username, password, and message to post. Then, it displays a menu with three social networks to choose from: Facebook, Twitter, and Dev.to. The user's choice is read from the console input using the int.Parse method, which converts the user's input from a string to an integer.

Next, a new instance of the SocialNetwork class is created based on the user's choice using a switch statement. Depending on the user's input, the network variable is set to a new instance of either the Facebook, Twitter, or DevTo class, passing in the username and password entered by the user.

Now remember that each of the concrete classes extend the SocialNetwork class, so they all have the Post method available. The only thing changing is how the Post method's algorithm handles the different network APIs. Depending on the network selected, the Post method will call the appropriate implementations of Login, SendData and Logout.

Now the output of the above would be something like:

Image description

Pros and Cons of the Template Method Pattern

✔ By allowing clients to override only specific portions of a complex algorithm, it is possible to minimize the impact of changes made to other parts of the algorithm on those clients. ❌The provided framework of an algorithm may constrain certain clients.
✔The repeated code can be consolidated into a superclass. ❌A default step implementation in a superclass could be suppressed by a subclass, resulting in a violation of the Liskov Substitution Principle.
❌ As the number of steps in a template method increases, the difficulty of maintaining it also tends to increase.

Relations with Other Patterns

  • The Factory Method design pattern is a specialized form of the Template Method design pattern. However, a Factory Method can also function as a step in a larger Template Method.
  • The Template Method design pattern relies on inheritance to allow subclasses to modify specific parts of an algorithm. In contrast, the Strategy pattern relies on composition to alter an object's behavior by providing it with different strategies. The Template Method is implemented at the class level and is static, meaning its structure is determined at compile-time. On the other hand, the Strategy pattern operates on the object level, allowing for dynamic switching of behaviors at runtime.

Final Thoughts

In this article, we have discussed what is the Template Method pattern, when to use it and what are the pros and cons of using this design pattern. We then examined some use cases for this pattern and how the Template Method relates to other classic design patterns.

It's worth noting that the Template Method pattern, along with the rest of the design patterns presented by the Gang of Four, is not a panacea or a be-all-end-all solution when designing an application. Once again it's up to the engineers to consider when to use a specific pattern. After all these patterns are useful when used as a precision tool, not a sledgehammer.

Top comments (0)