Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Template Method Design Pattern by Example

DZone's Guide to

Template Method Design Pattern by Example

In this post, using C# we take a look at this design pattern and how it can help developers solve some interesting problems.

· Web Dev Zone ·
Free Resource

Jumpstart your Angular applications with Indigo.Design, a unified platform for visual design, UX prototyping, code generation, and app development.

template-method-design-pattern

This pattern falls under behavioral design patterns, and, as the name suggests, it defines the template which can be used further to create something by using it. You can think of it like stencils, you can create designs on a wall or other surface without much effort; you just need to choose the color and apply the paint.

Let’s understand this with an example. We will be implementing a Logger which is capable of logging in multiple places like a database, a file or sending logs in an email. We will start with one simple solution and will refactor it gradually to see how the template method pattern can be useful for us.

Examples are written in C#, but easily understandable for anyone who knows basic OOPS concepts.

Approach 1: Create Different Classes for Each Type of Logger

We have three classes for each type of logger, i.e. FileLogger, EmailLogger, and DatabaseLogger. All have implemented their own logic.

Source Code: Template Method Pattern/Logger/Approach1

public class FileLogger
{
    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        OpenFile();
        WriteLogMessage(messageToLog);
        CloseFile();
    }

    private string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }

    private void OpenFile() {
        WriteLine("Opening File.");
    }

    private void WriteLogMessage(string message) {
        WriteLine("Appending Log message to file : " + message);
    }

    private void CloseFile() {
        WriteLine("Close File.");
    }
}

public class EmailLogger
{
    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        ConnectToMailServer();
        SendLogToEmail(messageToLog);
        DisposeConnection();
    }

    private string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }

    private void ConnectToMailServer() {
        WriteLine("Connecting to mail server and logging in");
    }

    private void SendLogToEmail(string message) {
        WriteLine("Sending Email with Log Message : " + message);
    }

    private void DisposeConnection() {
        WriteLine("Dispose Connection");
    }
}

public class DatabaseLogger
{
    public void Log(string message) {
        string messageToLog = SerializeMessage(message);
        ConnectToDatabase();
        InsertLogMessageToTable(messageToLog);
        CloseDbConnection();
    }

    private string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }

    private void ConnectToDatabase() {
        WriteLine("Connecting to Database.");
    }

    private void InsertLogMessageToTable(string message) {
        WriteLine("Inserting Log Message to DB table : " + message);
    }

    private void CloseDbConnection() {
        WriteLine("Closing DB connection.");
    }
}

class MainClass
{
    static void Main(string[] args) {
        FileLogger fileLogger = new FileLogger();
        fileLogger.Log("Message to Log in File.");
        WriteLine();
        EmailLogger emailLogger = new EmailLogger();
        emailLogger.Log("Message to Log via Email.");
        WriteLine();
        DatabaseLogger databaseLogger = new DatabaseLogger();
        databaseLogger.Log("Message to Log in DB.");
    }
}

Reviewing Approach 1

No code reusability - SerializeMessage() has the same implementation in each class.

Approach 2: Move Duplicate Code to Common Place

We have created the class AbstractLogger and moved the common code (i.e. SerializeMessage here) here. All classes requiring this code will inherit this class now to reuse the code.

Source Code: Template Method Pattern/Logger/Approach2

public abstract class AbstractLogger
{
    protected string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }
}

public class FileLogger : AbstractLogger
{
    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        OpenFile();
        WriteLogMessage(messageToLog);
        CloseFile();
    }

    private void OpenFile() {
        WriteLine("Opening File.");
    }

    private void WriteLogMessage(string message) {
        WriteLine("Appending Log message to file : " + message);
    }

    private void CloseFile() {
        WriteLine("Close File.");
    }
}

public class EmailLogger : AbstractLogger
{
    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        ConnectToMailServer();
        SendLogToEmail(messageToLog);
        DisposeConnection();
    }

    private void ConnectToMailServer() {
        WriteLine("Connecting to mail server and logging in");
    }

    private void SendLogToEmail(string message) {
        WriteLine("Sending Email with Log Message : " + message);
    }

    private void DisposeConnection() {
        WriteLine("Dispose Connection");
    }
}

public class DatabaseLogger : AbstractLogger
{
    public void Log(string message) {
        string messageToLog = SerializeMessage(message);
        ConnectToDatabase();
        InsertLogMessageToTable(messageToLog);
        CloseDbConnection();
    }

    private void ConnectToDatabase() {
        WriteLine("Connecting to Database.");
    }

    private void InsertLogMessageToTable(string message) {
        WriteLine("Inserting Log Message to DB table : " + message);
    }

    private void CloseDbConnection() {
        WriteLine("Closing DB connection.");
    }
}

class MainClass
{
    static void Main(string[] args) {
        FileLogger fileLogger = new FileLogger();
        fileLogger.Log("Message to Log in File.");
        WriteLine();
        EmailLogger emailLogger = new EmailLogger();
        emailLogger.Log("Message to Log via Email.");
        WriteLine();
        DatabaseLogger databaseLogger = new DatabaseLogger();
        databaseLogger.Log("Message to Log in DB.");
    }
}

Reviewing Approach 2

All loggers have three kinds of operations now: Opening Connection/FileWriting log message, and Closing/destroying File/object/connection. So we can assume a typical Logger will always have this kind of operation, but, still, a person who is implementing some new Logger in future has to remember and implement those operations. Shouldn’t that be enforced?

Log() is doing nothing fancy, just calling all other methods in sequence, isn’t it?

Approach 3: Enforce User to Implement Required Step, and Move Responsibility to Call Them in Base Class

We have added abstract methods in an abstract class with a generalized name, i.e OpenDataStoreOperation()LogMessage(), and CloseDataStoreOpreation() which are representing three operations mentioned above in the Approach 2 problems. So all the loggers have to implement them.

They give us one more advantage, in that we can also move Log() to an Abstract class, because all the methods which are being called in sequence in child classes are available in parent classes.

Both problems of the above approach are solved in this approach - this is how we implement the Template Method Design Pattern. Any class inheriting the AbstractLogger class just has to implement a few methods and they will already have some concrete methods like SerializeMessage(), in this case. We can even provide optional implementations by using virtual keywords in concrete methods.

Source Code: Template Method Pattern/Logger/Approach3

public abstract class AbstractLogger
{
    protected string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }

    protected abstract void OpenDataStoreOperation();

    protected abstract void LogMessage(string messageToLog);

    protected abstract void CloseDataStoreOpreation();

    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        OpenDataStoreOperation();
        LogMessage(messageToLog);
        CloseDataStoreOpreation();
    }
}

public class FileLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Opening File.");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Appending Log message to file : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Close File.");
    }
}

public class EmailLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Connecting to mail server and logging in");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Sending Email with Log Message : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Dispose Connection");
    }
}

public class DatabaseLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Connecting to Database.");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Inserting Log Message to DB table : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Closing DB connection.");
    }
}

class MainClass
{
    static void Main(string[] args) {
        FileLogger fileLogger = new FileLogger();
        fileLogger.Log("Message to Log in File.");
        WriteLine();
        EmailLogger emailLogger = new EmailLogger();
        emailLogger.Log("Message to Log via Email.");
        WriteLine();
        DatabaseLogger databaseLogger = new DatabaseLogger();
        databaseLogger.Log("Message to Log in DB.");
    }
}

Reviewing Approach 3

Here, all the steps of our algorithm/program will be executed for sure, but I have some optional steps which I wish to let the user choose whether to call or not.

Approach 4: Let the Caller Decide Some of the Things

Suppose that in our example, along with logging to a data store, I optionally want to let the user choose whether to log in to the console also or not. To achieve this, I have added on a boolean property in tge base class  (ConsoleLogging) and one virtual method (LogToConsole()). In Log(), I have added a condition, based on the ConsoleLogging value, of whether to execute LogToConsole() or not(see code). Now, if the user wants to log in to the console, they just need to pass true in the ConsoleLogging property (see in Main() EmailLogger).

Source Code: Template Design Pattern/Logger/Approach4

public abstract class AbstractLogger
{
    public bool ConsoleLogging { get; set; }

    protected string SerializeMessage(object message) {
        WriteLine("Serializing message");
        return message.ToString();
    }

    protected abstract void OpenDataStoreOperation();

    protected abstract void LogMessage(string messageToLog);

    protected abstract void CloseDataStoreOpreation();

    protected virtual void LogToConsole(string messageToLog) {
        WriteLine("Writing in Console : " + messageToLog);
    }

    public void Log(object message) {
        string messageToLog = SerializeMessage(message);
        OpenDataStoreOperation();
        LogMessage(messageToLog);
        CloseDataStoreOpreation();
        if (ConsoleLogging) {
            LogToConsole(messageToLog);
        }
    }
}

public class FileLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Opening File.");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Appending Log message to file : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Close File.");
    }
}

public class EmailLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Connecting to mail server and logging in");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Sending Email with Log Message : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Dispose Connection");
    }
}

public class DatabaseLogger : AbstractLogger
{
    protected override void OpenDataStoreOperation() {
        WriteLine("Connecting to Database.");
    }

    protected override void LogMessage(string messageToLog) {
        WriteLine("Inserting Log Message to DB table : " + messageToLog);
    }

    protected override void CloseDataStoreOpreation() {
        WriteLine("Closing DB connection.");
    }
}

class MainClass
{
    static void Main(string[] args) {
        FileLogger fileLogger = new FileLogger();
        fileLogger.Log("Message to Log in File.");
        WriteLine();
        EmailLogger emailLogger = new EmailLogger();
        emailLogger.ConsoleLogging = true;
        emailLogger.Log("Message to Log via Email.");
        WriteLine();
        DatabaseLogger databaseLogger = new DatabaseLogger();
        databaseLogger.Log("Message to Log in DB.");
    }
}

Conclusion

You can see in Approach 4 that the code looks better. Whenever you see your code/algorithm uses these same steps, with minor configurable changes like the given example, the template method can be useful.

Complete Source Code: Template Method Pattern/Logger

Take a look at an Indigo.Design sample application to learn more about how apps are created with design to code software.

Topics:
template method pattern ,c# ,design patterns ,web dev ,oops

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}