Strategy Pattern by Lambda Expression - Part 2
Earlier it was mentioned that the restaurant decided to add a veggie burger recipe in their menu. The veggie burger turned out successful, and the ambitious restaurant owner decided to further expand the food menu by offering a number of pasta dishes.
Assume that the developer would need to follow the original Strategy pattern, a concrete class of OperationVeggieBurger would need to be created; similarly for the pasta dishes, a PastaStrategy interface and all the corresponding concrete classes.
Imagine that the restaurant would continue expanding the business by adding more food categories, say, Appetizers, Soups, Pizzas, Entrees, Deserts and so on. The more categories to add, the more food strategy interfaces and the corresponding concrete classes would need to be created.
Here is the diagram of the Strategy Pattern that contains BurgerStrategy, PastaStrategy and a potential NewFoodStrategy:
Instead of following the original Strategy Pattern, the developer would like to apply the modern technique: Lambda Expression. However, it was noticed that the strategy required for making pasta was different from making burger. When a pasta dish is delivered to the table, the server will ask the customer for the preference of adding pepper and grated cheese.
Let's look at the Interface of the burger strategy that receives a list of ingredients as a parameter:
Function<T,R> with method R apply (T t)
A list of ingredients, type T, does not complete the parameters required for a pasta dish because the table service of adding pepper and grated cheese is not the same as cooking with a list of ingredients. To make a pasta dish, the developer would need to define a list of ingredients, and a list of table service items. Because of that, an interface of two parameter types, T and U, would be required. Fortunately, there is such a predefined interface called BiFunction.
BiFunction<T, U, R> with method R apply (T t, U u)
Knowing that creating a new interface was not required, the developer continued with the standard Lambda Expression approach.
Recall from the previous post that how a burger strategy interface was written:
By following the same approach, the developer could expand the program by additional food items, like this:
But wait! There were more and more food items would need to be added. Having many Lambda blocks with all the details adding to the same class was not a good idea. By noticing that the details within each Lambda block was actually a static recipe, the developer factored out the recipe and put it into a static method in the PastaRecipes class. The above pastaOrder Lambda block became simplified to this:
By following the same approach for different pasta orders, the developer also refactored the burger dishes into the same pattern of Lambda Expression.
These code snippets are the program that contains the burger and pasta dishes.
Here is the output of the program:
Beef burger made of: beef ketchup lettuce cheese pickles
Fish burger made of: fish tartar cheese
Chicken burger made of: chicken mayo lettuce
Veggie burger made of: plant_based_meat mayo lettuce tomato cucumber green_pepper
Seafood pasta made of: seafoods herbs rose_sauce penne / Table Service of: black_pepper grated_cheese
Chicken pasta made of: chicken pesto alfredo_sauce fettucine / Table Service of: black_pepper
Pasta Bolognese made of: ground_pork onion tomato_sauce spaghetti / Table Service of: grated_cheese
By Lambda Expression, the developer was able to shorten the coding of the original Strategy pattern in a neat way that is easy to maintain. Notice that there is a way to further shorten the coding - by method references:
The short form can be considered even simpler to read for general understanding, however, it is no longer as easy as before to see how many parameters each of these methods will take in respectively. If the functional interface, like burgerOrder and pastaOrder in this example, is declared nearby, it will be quite easy to refer to how many parameters there are; otherwise, the pop-up messages while typing in an IDE can help reminding.
Assume that the developer would need to follow the original Strategy pattern, a concrete class of OperationVeggieBurger would need to be created; similarly for the pasta dishes, a PastaStrategy interface and all the corresponding concrete classes.
Imagine that the restaurant would continue expanding the business by adding more food categories, say, Appetizers, Soups, Pizzas, Entrees, Deserts and so on. The more categories to add, the more food strategy interfaces and the corresponding concrete classes would need to be created.
Here is the diagram of the Strategy Pattern that contains BurgerStrategy, PastaStrategy and a potential NewFoodStrategy:
Instead of following the original Strategy Pattern, the developer would like to apply the modern technique: Lambda Expression. However, it was noticed that the strategy required for making pasta was different from making burger. When a pasta dish is delivered to the table, the server will ask the customer for the preference of adding pepper and grated cheese.
Let's look at the Interface of the burger strategy that receives a list of ingredients as a parameter:
Function<T,R> with method R apply (T t)
A list of ingredients, type T, does not complete the parameters required for a pasta dish because the table service of adding pepper and grated cheese is not the same as cooking with a list of ingredients. To make a pasta dish, the developer would need to define a list of ingredients, and a list of table service items. Because of that, an interface of two parameter types, T and U, would be required. Fortunately, there is such a predefined interface called BiFunction.
BiFunction<T, U, R> with method R apply (T t, U u)
Knowing that creating a new interface was not required, the developer continued with the standard Lambda Expression approach.
Recall from the previous post that how a burger strategy interface was written:
burgerOrder = ( (ingredients) -> { String burger = "Beef burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; });
By following the same approach, the developer could expand the program by additional food items, like this:
pastaOrder = ( (ingredients, tableServiceItems) -> { var sb = new StringBuilder(); sb.append("Seafood pasta made of: "); for (String ingredient: ingredients) { sb.append (ingredient + " "); } sb.append(" / "); sb.append("Table Service of: "); for (String item: tableServiceItems) { sb.append (item + " "); } return sb.substring(0); });
But wait! There were more and more food items would need to be added. Having many Lambda blocks with all the details adding to the same class was not a good idea. By noticing that the details within each Lambda block was actually a static recipe, the developer factored out the recipe and put it into a static method in the PastaRecipes class. The above pastaOrder Lambda block became simplified to this:
pastaOrder = (i, t) -> PastaRecipes.makeSeafoodPasta(i, t);
By following the same approach for different pasta orders, the developer also refactored the burger dishes into the same pattern of Lambda Expression.
These code snippets are the program that contains the burger and pasta dishes.
import java.util.*; public class BurgerRecipes { static String makeBeefBurger (List<String> ingredients) { String burger = "Beef burger made of: "; for (String ingredient : ingredients) { burger += ingredient + " "; } return burger; } static String makeFishBurger (List<String> ingredients) { String burger = "Fish burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } static String makeChickenBurger (List<String> ingredients) { String burger = "Chicken burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } static String makeVeggieBurger (List<String> ingredients) { String burger = "Veggie burger made of: "; for (String ingredient: ingredients) { burger += ingredient + " "; } return burger; } }
import java.util.*; public class PastaRecipes { static String makeSeafoodPasta (List<String> ingredients, List<String> tableServiceItems) { var sb = new StringBuilder(); sb.append("Seafood pasta made of: "); for (String ingredient: ingredients) { sb.append (ingredient + " "); } sb.append(" / "); sb.append("Table Service of: "); for (String item: tableServiceItems) { sb.append (item + " "); } return sb.substring(0); } static String makeChickenPasta (List<String> ingredients, List<String> tableServiceItems) { var sb = new StringBuilder(); sb.append("Chicken pasta made of: "); for (String ingredient: ingredients) { sb.append (ingredient + " "); } sb.append(" / "); sb.append("Table Service of: "); for (String item: tableServiceItems) { sb.append (item + " "); } return sb.substring(0); } static String makeBolognesePasta (List<String> ingredients, List<String> tableServiceItems) { var sb = new StringBuilder(); sb.append("Pasta Bolognese made of: "); for (String ingredient: ingredients) { sb.append (ingredient + " "); } sb.append(" / "); sb.append("Table Service of: "); for (String item: tableServiceItems) { sb.append (item + " "); } return sb.substring(0); } }
import java.util.*; import java.util.function.*; public class StrategyPatternFoodTypesDemo { public static void main(String[] args) { Function<List<String>, String> burgerOrder; List<String> burgerIngredients; burgerOrder = i -> BurgerRecipes.makeBeefBurger(i); burgerIngredients = Arrays.asList("beef", "ketchup", "lettuce", "cheese", "pickles"); System.out.println(burgerOrder.apply(burgerIngredients)); burgerOrder = i -> BurgerRecipes.makeFishBurger(i); burgerIngredients = Arrays.asList("fish", "tartar", "cheese"); System.out.println(burgerOrder.apply(burgerIngredients)); burgerOrder = i -> BurgerRecipes.makeChickenBurger(i); burgerIngredients = Arrays.asList("chicken", "mayo", "lettuce"); System.out.println(burgerOrder.apply(burgerIngredients)); burgerOrder = i -> BurgerRecipes.makeVeggieBurger(i); burgerIngredients = Arrays.asList("plant_based_meat", "mayo", "lettuce", "tomato", "cucumber", "green_pepper"); System.out.println(burgerOrder.apply(burgerIngredients)); BiFunction<List<String>, List<String>, String> pastaOrder; List<String> pastaIngredients; List<String> tableServiceItems; pastaOrder = (i, t) -> PastaRecipes.makeSeafoodPasta(i, t); pastaIngredients = Arrays.asList("seafoods", "herbs", "rose_sauce", "penne"); tableServiceItems = Arrays.asList("black_pepper", "grated_cheese"); System.out.println(pastaOrder.apply(pastaIngredients, tableServiceItems)); pastaOrder = (i, t) -> PastaRecipes.makeChickenPasta(i, t); pastaIngredients = Arrays.asList("chicken", "pesto", "alfredo_sauce", "fettucine"); tableServiceItems = Arrays.asList("black_pepper"); System.out.println(pastaOrder.apply(pastaIngredients, tableServiceItems)); pastaOrder = (i, t) -> PastaRecipes.makeBolognesePasta(i, t); pastaIngredients = Arrays.asList("ground_pork", "onion", "tomato_sauce", "spaghetti"); tableServiceItems = Arrays.asList("grated_cheese"); System.out.println(pastaOrder.apply(pastaIngredients, tableServiceItems)); } }
Here is the output of the program:
Beef burger made of: beef ketchup lettuce cheese pickles
Fish burger made of: fish tartar cheese
Chicken burger made of: chicken mayo lettuce
Veggie burger made of: plant_based_meat mayo lettuce tomato cucumber green_pepper
Seafood pasta made of: seafoods herbs rose_sauce penne / Table Service of: black_pepper grated_cheese
Chicken pasta made of: chicken pesto alfredo_sauce fettucine / Table Service of: black_pepper
Pasta Bolognese made of: ground_pork onion tomato_sauce spaghetti / Table Service of: grated_cheese
By Lambda Expression, the developer was able to shorten the coding of the original Strategy pattern in a neat way that is easy to maintain. Notice that there is a way to further shorten the coding - by method references:
burgerOrder = BurgerRecipes::makeBeefBurger; pastaOrder = PastaRecipes::makeSeafoodPasta;
The short form can be considered even simpler to read for general understanding, however, it is no longer as easy as before to see how many parameters each of these methods will take in respectively. If the functional interface, like burgerOrder and pastaOrder in this example, is declared nearby, it will be quite easy to refer to how many parameters there are; otherwise, the pop-up messages while typing in an IDE can help reminding.
Comments
Post a Comment