171. Implementing the Template Method pattern

The Template Method is a classical design pattern from GoF that allows us to write a skeleton of an algorithm in a method and defer certain steps of this algorithm to the client subclasses.

For example, making a pizza involves three main steps – preparing the dough, adding toppings, and baking the pizza. While the first and last step can be considered the same (fixed steps) for all pizzas, the second step is different for each type of pizza (variable step).

If we put this in code via the Template Method pattern, then we obtain something like the following (the make() method represents the template method and contains the fixed and variable steps in a well-defined order):

public abstract class PizzaMaker {

public void make(Pizza pizza) {
makeDough(pizza);
addTopIngredients(pizza);
bake(pizza);
}

private void makeDough(Pizza pizza) {
System.out.println("Make dough");
}

private void bake(Pizza pizza) {
System.out.println("Bake the pizza");
}

public abstract void addTopIngredients(Pizza pizza);
}

The fixed steps have default implementations while the variable step is represented by an abstract method called addTopIngredients(). This method is implemented by subclasses of this class. For example, a Neapolitan pizza will be abstracted as follows:

public class NeapolitanPizza extends PizzaMaker {

@Override
public void addTopIngredients(Pizza p) {
System.out.println("Add: fresh mozzarella, tomatoes,
basil leaves, oregano, and olive oil ");
}
}

On the other hand, a Greek pizza will be as follows:

public class GreekPizza extends PizzaMaker {

@Override
public void addTopIngredients(Pizza p) {
System.out.println("Add: sauce and cheese");
}
}

So, each type of pizza requires a new class that overrides the addTopIngredients() method. In the end, we can make a pizza like so:

Pizza nPizza = new Pizza();
PizzaMaker nMaker = new NeapolitanPizza();
nMaker.make(nPizza);

The drawback of this approach consists of boilerplate code and verbosity. However, we can tackle this drawback via lambdas. We can represent the variable steps of the Template Method as lambdas expressions. Depending on the case, we have to choose the proper functional interfaces. In our case, we can rely on a Consumer, as follows:

public class PizzaLambda {

public void make(Pizza pizza, Consumer<Pizza> addTopIngredients) {
makeDough(pizza);
addTopIngredients.accept(pizza);
bake(pizza);
}

private void makeDough(Pizza p) {
System.out.println("Make dough");
}

private void bake(Pizza p) {
System.out.println("Bake the pizza");
}
}

This time, there is no need to define subclasses (no need to have NeapolitanPizza, GreekPizza, or others). We just pass the variable step via a lambda expression. Let's make a Sicilian pizza:

Pizza sPizza = new Pizza();
new PizzaLambda().make(sPizza, (Pizza p)
-> System.out.println("Add: bits of tomato, onion,
anchovies, and herbs "));

Done! No more boilerplate code is needed. The lambda solution has seriously improved the solution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.170.187