The Template Method Pattern

The Template Method Pattern

 

The Template Method Pattern is one of my favourite patterns. It’s a neat behavioural design pattern useful in situations when we want to reuse a multi-step workflow. 

Here is how it works: A base class contains an outline of a workflow composed of several steps. Derived classes follow this workflow yet may have different implementations for the actions. The base class defines the steps in the workflow as methods. When the methods are virtual, they are default actions. Subclasses can override these defaults with different implementations. If the base class workflow methods are abstract, then they represent placeholder operations that must be implemented in the child classes.

Let’s look at an example. Payment gateways like PayPal, Stripe or Braintree follow the same step-by-step process:

  1. Create an order
  2. Pay for order
  3. Dispatch order

We can capture the essence of this workflow in a BasePaymentGateway class:

  public abstract class BasePaymentGateway : IPaymentGateway
  {
     public PaymentResult Pay(ShoppingCart cart, Customer customer)
     {
        // Outline of the general payment gateway workflow.
        // We want to keep this workflow the same for all payment gateways. 
        try
        {
           Validate(cart, customer);
           var order = GenerateOrder(cart, customer);
           var result = PayForOrder(order, customer.PaymentAuthorisation);
           if (result.Status == SUCCESS)
              DispatchOrder(order);

           return result;
        }
        catch (Exception ex)
        {
           // ... 
        }
     }

     protected void Validate(ShoppingCart cart, Customer customer)
     {
        // Concrete method to validate ShoppingCart and Customer.
        // Not virtual as is the same for all payment gateways.
        if (cart.IsEmpty)
           throw new EmptyShoppingCart(customer.Id);
        if (customer.PaymentAuthorisation == null)
           throw new MissingPaymentAuthorisation(customerId);
            
        // ...
     }

     protected abstract CustomerOrder GenerateOrder(ShoppingCart cart, Customer customer);
     protected abstract PaymentResult PayForOrder(CustomerOrder order, PaymentAuthorisation authorisation);

     protected virtual void DispatchOrder(CustomerOrder order)
     {
        // Concrete method to dispatch the order.
        // There might be payment gateway specific parts so keep as virtual.
        // Maybe Stripe overrides but Braintree is happy with the default option.
            
        // ...
     }
  }

A few points: 

  • Class BasePaymentGateway is abstract. We cannot directly instantiate it. We only want to instantiate specific payment gateway classes, e.g. StripePaymentGateway, PaypalPaymentGateway, etc..
  • The Validate() method is concrete. It is not declared as virtual, and child classes will not be able to override it. Do this if you’re sure the method will work the same for all subclasses.
  • DispatchOrder() is a virtual method. It will have a base implementation (not shown here) that will work for most of the derived payment gateway classes. If the base implementation does not suit, subclasses may override.
  • GenerateOrder() and PayForOrder() are declared abstract. There is no implementation here. Each subclass must provide their own. Why not provide a default implementation? Specific payment gateways generate orders and pay for them in very distinct ways. It makes little sense to do anything other than provide a placeholder method within the workflow of our BasePaymentGateway class.

Here is the definition for the StripePaymentGateway subclass:

  public class StripePaymentGateway : BasePaymentGateway
  {
     protected override CustomerOrder GenerateOrder(ShoppingCart cart, Customer customer)
     {
        // Stripe-specific generation of order.
        // Communicate with Stripe API to create order.

        // ...
     }

     protected override PaymentResult PayForOrder(CustomerOrder order, PaymentAuthorisation authorisation)
     {
        // Stripe-specific payment of order.
        // Communicate with Stripe API to pay for order.

        // ...
     }
  }

Points of interest:

  • StripePaymentGateway is a small class with little logic. It only contains the overrides of the two abstract methods in BasePaymentGateway; GenerateOrder() and PayForOrder(). These two methods have to be implemented since they were declared abstract in the parent class.
  • The workflow does not need to be redefined in each of the payment gateway subclasses. StripePaymentGateway (and any siblings) can leverage the workflow in BasePaymentGateway
  • DispatchOrder() is not overridden; the default implementation in BasePaymentGateway is sufficient.

A summary of The Template Method design pattern:

  • Define a step-by-step workflow in a base class.
  • The workflow steps are defined either as defaults (i.e. virtual methods) or placeholders (i.e. abstract methods). Subclasses either accept the base class defaults or override them. Similarly, subclasses must implement placeholder methods.

The Template Method patterns may apply when there is a consistent overall workflow, yet some of the steps differ in their specific detail.

Maybe consider applying The Template Method design pattern in one of your projects? 

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply