Strategy Pattern by Lambda Expression - Part 1
Design patterns help solving common problems in designing software, A major contribution of this knowledge was made by the book Design Patterns - Elements of Reusable Object-Oriented Software written by the "Gang of Four" in 1994, and that was a time before the initial release of Java. In the book, there are 23 patterns documented, and one of those is called Strategy. In this blog post, I would like to demonstrate how the Strategy pattern can be written, and how this object-oriented design can be transformed into Lambda Expression. Since the first release in 2014, Lambda Expression has been an important feature of the Java core language.
The Strategy Design Pattern
As an example, let's imagine that there is a restaurant that requires organizing its workflows. The restaurant requires having a BurgerStrategy that is an interface. The concrete classes that implement the BurgerStrategy are OperationBeefBurger, OperationFishBurger, and OperationChickenBurger. The context that uses the BurgerStrategy to make burgers is FoodOrder.
These code snippets are the complete program.
The above program produces this result:
Beef burger made of: beef ketchup lettuce cheese pickles
Fish burger made of: fish tartar cheese
Chicken burger made of: chicken mayo lettuce
Further to a market research, the restaurant decided to go ahead with offering a recipe of vegetarian burger. Given that the Strategy Pattern is used, it will be quite straightforward to create a new concrete class called OperationVeggieBurger to implement the BurgerStrategy.
Re-writing the program by Lambda Expression
Notice that the BurgerStrategy interface defines only one method without the body.
The idea of this interface is the same as one of the predefined interfaces in java.util.functions called Function<T,R>. Inside there is a method called R apply(T t).
In other words, the purpose of BurgerStrategy is the same as Function<T,R>; and the method makeBurger the same as apply.
With Lambda Expression, we don't need to create the BurgerStrategy interface. Instead we can declare an instance of Function<T,R> called burgerOrder like this:
In this example, BurgerStategy and FoodOrder become burgerOrder.
We also don't need to create concrete classes like OperationBeefBurger. The details of the makeBurger method in the original OperationBeefBurger class can be moved to the corresponding block of Lambda Expression.
Similarly, OperationFishBurger and OperationChickenBurger can be replaced by their corresponding Lambda block.
Here is the revised StrategyPatternDemo class.
In the minimalist example above, the content written within a Lambda Expression block is small. In reality, if the content is big or complex enough, it will be good to factored that part out to one or some static methods.
By using the original object-oriented design, creating a new veggie burger operation will require a new concrete class to implement the BurgerStrategy interface. Now with Lambda, we can add the new operation by creating a new Lambda block implementing Function<T,R>.
The Strategy Design Pattern
As an example, let's imagine that there is a restaurant that requires organizing its workflows. The restaurant requires having a BurgerStrategy that is an interface. The concrete classes that implement the BurgerStrategy are OperationBeefBurger, OperationFishBurger, and OperationChickenBurger. The context that uses the BurgerStrategy to make burgers is FoodOrder.
![]() |
import java.util.List; public interface BurgerStrategy { public String makeBurger (List<String> ingredients); }
import java.util.List; public class OperationBeefBurger implements BurgerStrategy { public String makeBurger (List<String> ingredients) { String burger = "Beef burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } }
import java.util.List; public class OperationFishBurger implements BurgerStrategy { public String makeBurger (List<String> ingredients) { String burger = "Fish burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } }
import java.util.List; public class OperationChickenBurger implements BurgerStrategy { public String makeBurger (List<String> ingredients) { String burger = "Chicken burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } }
import java.util.List; public class FoodOrder { private BurgerStrategy strategy; public FoodOrder (BurgerStrategy strategy) { this.strategy = strategy; } public String executeStrategy (List<String> ingredients) { return strategy.makeBurger(ingredients); } }
import java.util.List; import java.util.Arrays; public class StrategyPatternDemo { public static void main(String[] args) { FoodOrder foodOrder; List<String> ingredients; foodOrder = new FoodOrder(new OperationBeefBurger()); ingredients = Arrays.asList("beef", "ketchup", "lettuce", "cheese", "pickles"); System.out.println( foodOrder.executeStrategy(ingredients)); foodOrder = new FoodOrder (new OperationFishBurger()); ingredients = Arrays.asList("fish", "tartar", "cheese"); System.out.println( foodOrder.executeStrategy(ingredients)); foodOrder = new FoodOrder (new OperationChickenBurger()); ingredients = Arrays.asList("chicken", "mayo", "lettuce"); System.out.println( foodOrder.executeStrategy(ingredients)); } }
The above program produces this result:
Beef burger made of: beef ketchup lettuce cheese pickles
Fish burger made of: fish tartar cheese
Chicken burger made of: chicken mayo lettuce
Further to a market research, the restaurant decided to go ahead with offering a recipe of vegetarian burger. Given that the Strategy Pattern is used, it will be quite straightforward to create a new concrete class called OperationVeggieBurger to implement the BurgerStrategy.
Re-writing the program by Lambda Expression
Notice that the BurgerStrategy interface defines only one method without the body.
public interface BurgerStrategy { public String makeBurger (List<String> ingredients); }
The idea of this interface is the same as one of the predefined interfaces in java.util.functions called Function<T,R>. Inside there is a method called R apply(T t).
In other words, the purpose of BurgerStrategy is the same as Function<T,R>; and the method makeBurger the same as apply.
With Lambda Expression, we don't need to create the BurgerStrategy interface. Instead we can declare an instance of Function<T,R> called burgerOrder like this:
Function<List<String>, String> burgerOrder;
In this example, BurgerStategy and FoodOrder become burgerOrder.
We also don't need to create concrete classes like OperationBeefBurger. The details of the makeBurger method in the original OperationBeefBurger class can be moved to the corresponding block of Lambda Expression.
burgerOrder = ( (ingredients) -> { String burger = "Beef burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; });
Similarly, OperationFishBurger and OperationChickenBurger can be replaced by their corresponding Lambda block.
Here is the revised StrategyPatternDemo class.
import java.util.List; import java.util.Arrays; import java.util.function.Function; public class StrategyPatternDemo { public static void main(String[] args) { Function<List<String>, String> burgerOrder; List<String> customIngredients; burgerOrder = ( (ingredients) -> { String burger = "Beef burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; }); customIngredients = Arrays.asList("beef", "ketchup", "lettuce", "cheese", "pickles"); System.out.println(burgerOrder.apply(customIngredients)); burgerOrder = ( (ingredients) -> { String burger = "Fish burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; }); customIngredients = Arrays.asList("fish", "tartar", "cheese"); System.out.println(burgerOrder.apply(customIngredients)); burgerOrder = ( (ingredients) -> { String burger = "Chicken burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; }); customIngredients = Arrays.asList("chicken", "mayo", "lettuce"); System.out.println(burgerOrder.apply(customIngredients)); } }
Programmers will find following the predefined interfaces beneficial. By looking at the interface name BurgerStrategy, we don't immediately know what the method is and how we can use it. Now, with the predefined interfaces, for example: Function<T,R>, we know right away what the corresponding method is and how it can be used.
In the minimalist example above, the content written within a Lambda Expression block is small. In reality, if the content is big or complex enough, it will be good to factored that part out to one or some static methods.
By using the original object-oriented design, creating a new veggie burger operation will require a new concrete class to implement the BurgerStrategy interface. Now with Lambda, we can add the new operation by creating a new Lambda block implementing Function<T,R>.

Comments
Post a Comment