1. Overview

Often when we have the need to write down different implementations of a complex algorithm that is made up of different steps. Many times all that changes from one to another implementation is just a single step or something like that. What we want to avoid is to copy and paste the same code in different classes and in the same time we want to avoid complex conditional flows to select the right step. Template Method Design Pattern is the solution to this kind of problem.

2. Structure

The design pattern Template Method states that common steps of the algorithm should be implemented in an Abstract base class while the steps that have different implementations should be placed in separate subclasses. The key here is to define the skeleton of an algorithm in an operation deferring some step to subclasses.

Template Method pattern is often confused with Strategy one because they are really similar but they differ in these aspects:

  • Strategy defines a family of algorithms and makes them interchangeable while Template is focused on single components of the same algorithm
  • Strategy works by composition while the Template Method works by inheritance.
  • In the template method design pattern, all the common code is in the superclass so that all the subclass share this code, while in Strategy pattern, some of the code can be duplicated in all the subclasses.

Let’s introduce our example: implement a solution to make a pizza.

I state that I am Neapolitan but now I live in Bari (Italy) and this is my tribute to Neapolitan pizza may be the best pizza in the world.

In every place we go we will find a different implementation of the same pizza preparation algorithm but in this case, suppose to have just one recipe of the pizza preparation. We can have different pizza: Margherita, Capricciosa etc…

The steps to prepare a pizza are

1)prepare the dough

2)roll out the dough

3)add toppings to the rolled out dough

4) bake the pizza

What changes from one preparation to the other are just the toppings (step 3).

PizzaPreparation is an Abstract Class that has inside all the steps of our algorithm. GetToppings is the only Abstract method because is the only one that changes in every implementation.

3. Spring implementation

First of all, we define the model classes for Pizza and Dough (code is omitted you can find it on GitHub repository)
Next step is to define the template class PizzaPreparation

package com.mgiglione.templateMethod.template;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mgiglione.templateMethod.model.Dough;
import com.mgiglione.templateMethod.model.Pizza;


public abstract class PizzaPreparation {

    Logger logger=LoggerFactory.getLogger(this.getClass());
    
    
    public Dough prepareDough() {
        logger.info("Preparing dough..");
        return new Dough();
    }
    
    public  void rollOutDough(Dough dough) {
        logger.info("Rolling out dough..");
        dough.setRolledOut(true);
    }
    
    public Pizza addToppings(List<String> toppings,Dough dough) {
        logger.info("Adding Toppings..");
        return new Pizza(dough,toppings);
        
    }
    
    public void bake(Pizza pizza) {
        logger.info("Baking pizza..");
        pizza.setBaked(true);
        
    }
    
    public Pizza prepare() {
        Dough dough = prepareDough();
        rollOutDough(dough);
        Pizza pizza = addToppings(getToppings(), dough);
        bake(pizza);
        logger.info("Your pizza is ready");
        logger.info("Toppings are:"+pizza.getToppings() );
        return pizza;
    }
    
    /**
     * This is the only abstract method of this class. Your sublcass must provide an implementation to make a new preparation
     * @return A list of toppings
     */
    public abstract List<String> getToppings();
    
    
}

Now let’s define the two implementations of the template:

package com.mgiglione.templateMethod.template.implementations;

import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.mgiglione.templateMethod.template.PizzaPreparation;

@Component
@Qualifier("margherita")
public class PizzaMargheritaPreparation extends PizzaPreparation {

    @Override
    public List<String> getToppings() {
        return Arrays.asList("mozzarella", "tomato", "basil","oil");
    }

}

package com.mgiglione.templateMethod.template.implementations;

import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import com.mgiglione.templateMethod.template.PizzaPreparation;

@Component
@Qualifier("capricciosa")
public class PizzaCapricciosaPreparation extends PizzaPreparation{

    @Override
    public List<String> getToppings() {
        return Arrays.asList("mozzarella", "tomato", "mushrooms", "artichokes", "cooked ham", "olives", "oil");
    }

}

As you can see from the code we implemented the only getToppings method in subclasses.
The other thing that you will note is the use of Spring @Qualifier annotation. This annotation gives the opportunity to choose between implementations to use.

To test our template method design pattern implementation I write down a simple test unit

package com.mgiglione.templateMethod.test;
import static org.junit.Assert.assertTrue;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import com.mgiglione.templateMethod.model.Pizza;
import com.mgiglione.templateMethod.template.PizzaPreparation;

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestPizzaPreparation {

    
    @Autowired
    @Qualifier("margherita")
    PizzaPreparation margheritaPreparation;
    
    @Autowired
    @Qualifier("capricciosa")
    PizzaPreparation capricciosaPreparation;
    
    @Test
    public void testMargheritaPreparation() {
        Pizza margherita = margheritaPreparation.prepare();
        assertTrue(margherita.getToppings().contains("basil"));
    }
    
    @Test
    public void testCapricciosaPreparation() {
        Pizza capricciosa = capricciosaPreparation.prepare();
        assertTrue(capricciosa.getToppings().contains("cooked ham"));
    }
    
}

You can see in the code how to use @Qualifier to choose between implementations.

4. Conclusion

In this post, we have seen how to use the Template Method Pattern in a real but simple case. We have also seen how to benefit from the Spring IOC to chose the solution we want to use.
Of course, you can find anything in my git hub repository.

Leave a Reply

Your email address will not be published. Required fields are marked *