Template Method Pattern

Greetings, fellow developers! Welcome back to .Net Core Central. In today’s blog post, we embark on a journey into the realm of design patterns, focusing our attention on the Template Method Design Pattern – a powerful member of the Gang of Four design patterns.

Understanding the Essence of Template Method Pattern

The Template Method Design Pattern, nestled under behavioral design patterns, is a versatile tool for crafting algorithms with varying steps. At its core, this pattern seeks to define the skeleton of an algorithm within an operation, allowing subclasses to implement or redefine specific steps without altering the overall structure of the algorithm. The template method essentially creates a blueprint for an algorithm, with subclasses providing their unique twists to certain steps.

Demystifying the Template Method Pattern

To illustrate this concept in a programming context, let’s delve into an example involving a simple shopping cart with a checkout process. The checkout process encompasses multiple steps, and within that, one step stands out as a template method. In this scenario, we’re implementing the checkout process using both the Template Method Design Pattern and adhering to SOLID design principles.

Template Method Pattern Implementation

In the traditional template method approach, we create an abstract class named Cart with a method called Checkout. This method consists of three steps: validating items, executing payment, and sending a receipt. While the first two steps are implemented directly in the Cart class, the third step, sending a receipt, is marked as an abstract method, awaiting implementation by subclasses.

namespace TemplateMethodPattern.Demo.TemplateMethod;

internal abstract class Cart
{
    public void Checkout()
    {
        ValidateItem();
        ExecutePayment();
        SendReceipt();
    }

    protected abstract void SendReceipt();

    private void ExecutePayment()
    {
        Console.WriteLine("Payment executed");
    }

    private void ValidateItem()
    {
        Console.WriteLine("Item validated");
    }
}

Subsequently, we create two subclasses: EmailCart and TextCart. Each of these subclasses provides its unique implementation for sending receipts via email and text messages, respectively.

namespace TemplateMethodPattern.Demo.TemplateMethod;

internal class EmailCart : Cart
{
    protected override void SendReceipt()
    {
        Console.WriteLine("Email Sent");
    }
}

internal class TextCart : Cart
{
    protected override void SendReceipt()
    {
        Console.WriteLine("Text Sent");
    }
}

Implementation of using the Carts

using TemplateMethodPattern.Demo.TemplateMethod;

var cart = new EmailCart();
cart.Checkout();

var cart1 = new TextCart();
cart1.Checkout();

SOLID Design Principles Approach

In the SOLID design principles approach, we introduce three interfaces: IItemValidator, IPaymentExecutor, and IReceiptSender. The Cart class now takes instances of these interfaces through its constructor, allowing for a more modular and flexible design.

namespace TemplateMethodPattern.Demo.SOLID;

public interface IItemValidator
{
    void ValidateItem();
}

public class ItemValidator : IItemValidator
{
    public void ValidateItem()
    {
        Console.WriteLine("Item validated");
    }
}
namespace TemplateMethodPattern.Demo.SOLID;

public interface IPaymentExecutor
{
    void ExecutePayment();
}

public class PaymentExecutor : IPaymentExecutor
{
    public void ExecutePayment()
    {
        Console.WriteLine("Payment executed");
    }
}
namespace TemplateMethodPattern.Demo.SOLID;

public interface IReceiptSender
{
    void SendReceipt();
}

public class EmailReceiptSender : IReceiptSender
{
    public void SendReceipt()
    {
        Console.WriteLine("Email Sent");
    }
}

public class TextReceiptSender : IReceiptSender
{
    public void SendReceipt()
    {
        Console.WriteLine("Text Sent");
    }
}

Each interface has a single method representing its responsibility: ValidateItem, ExecutePayment, and SendReceipt. We then create concrete classes implementing these interfaces: ItemValidator, PaymentExecutor, EmailReceiptSender, and TextReceiptSender.

In the Cart class, the Checkout method now invokes the methods from these interfaces, providing a different implementation of the receipt-sending process based on the injected IReceiptSender instance.

namespace TemplateMethodPattern.Demo.SOLID;

internal class Cart
{
    private readonly IItemValidator itemValidator;
    private readonly IPaymentExecutor paymentExecutor;
    private readonly IReceiptSender receiptSender;

    public Cart(IItemValidator itemValidator,
        IPaymentExecutor paymentExecutor,
        IReceiptSender receiptSender)
    {
        this.itemValidator = itemValidator;
        this.paymentExecutor = paymentExecutor;
        this.receiptSender = receiptSender;
    }
    public void Checkout()
    {
        itemValidator.ValidateItem();
        paymentExecutor.ExecutePayment();
        receiptSender.SendReceipt();
    }
}

Implementation of using the Carts

using TemplateMethodPattern.Demo.SOLID;

var itemValidator = new ItemValidator();
var paymentExecutor = new PaymentExecutor();

var emailReciptSender = new EmailReceiptSender();
var textReceiptSender = new TextReceiptSender();

var cart = new Cart(itemValidator, paymentExecutor, emailReciptSender);
cart.Checkout();

cart = new Cart(itemValidator, paymentExecutor, textReceiptSender);
cart.Checkout();

Comparing Approaches

Both approaches yield the same outcome – a flexible checkout process with varying receipt-sending mechanisms. The choice between the traditional template method and the SOLID design principles approach depends on the specific requirements of the project and the desired level of flexibility.

Conclusion: Tailoring Algorithms with Templates

The Template Method Design Pattern empowers developers to create robust algorithms while accommodating variability in certain steps. Whether you opt for the traditional template method or embrace SOLID design principles, the key lies in crafting flexible and maintainable solutions.

That concludes our exploration of the Template Method Design Pattern in .NET Core. If you found this journey enlightening, don’t forget to go through the rest of my blog posts.

For more coding adventures, consider going to my YouTube channel and subscribing to .Net Core Central. Thank you for joining me, and happy coding!