Tuesday, July 12, 2022

Behavioral Design Patterns # Strategy

Strategy is a behavioral design pattern that lets you define a family of algorithms, put each of them into a separate class, and make their objects interchangeable.



Problem

One day you decided to create a navigation app for casual travelers. The app was centered around a beautiful map which helped users quickly orient themselves in any city.

One of the most requested features for the app was automatic route planning. A user should be able to enter an address and see the fastest route to that destination displayed on the map.

The first version of the app could only build the routes over roads. People who traveled by car were bursting with joy. But apparently, not everybody likes to drive on their vacation. 

So with the next update, you added an option to build walking routes. Right after that, you added another option to let people use public transport in their routes.

However, that was only the beginning. Later you planned to add route building for cyclists. And even later, another option for building routes through all of a city’s tourist attractions.


While from a business perspective the app was a success, the technical part caused you many headaches. 

Each time you added a new routing algorithm, the main class of the navigator doubled in size. At some point, the beast became too hard to maintain.

Any change to one of the algorithms, whether it was a simple bug fix or a slight adjustment of the street score, affected the whole class, increasing the chance of creating an error in already-working code.

In addition, teamwork became inefficient. Your teammates, who had been hired right after the successful release, complain that they spend too much time resolving merge conflicts. 


Implementing a new feature requires you to change the same huge class, conflicting with the code produced by other people.


Solution


The Strategy pattern suggests that you take a class that does something specific in a lot of different ways and extract all of these algorithms into separate classes called strategies.

The original class, called context, must have a field for storing a reference to one of the strategies. The context delegates the work to a linked strategy object instead of executing it on its own.

The context isn’t responsible for selecting an appropriate algorithm for the job. Instead, the client passes the desired strategy to the context. In fact, the context doesn’t know much about strategies.

It works with all strategies through the same generic interface, which only exposes a single method for triggering the algorithm encapsulated within the selected strategy.

This way the context becomes independent of concrete strategies, so you can add new algorithms or modify existing ones without changing the code of the context or other strategies.


In our navigation app, each routing algorithm can be extracted to its own class with a single buildRoute method. The method accepts an origin and destination and returns a collection of the route’s checkpoints.

Even though given the same arguments, each routing class might build a different route, the main navigator class doesn’t really care which algorithm is selected since its primary job is to render a set of checkpoints on the map. 


The class has a method for switching the active routing strategy, so its clients, such as the buttons in the user interface, can replace the currently selected routing behavior with another one.


Structure




Pros and Cons


Strategy Design Patterns Example 

One of the best example of strategy pattern is Collections.sort() method that takes Comparator parameter. Based on the different implementations of Comparator interfaces, the Objects are getting sorted in different ways.

For our example, we will try to implement a simple Shopping Cart where we have two payment strategies – using Credit Card or using PayPal.

First of all we will create the interface for our strategy pattern example, in our case to pay the amount passed as argument.

public interface PaymentStrategy { public void pay(int amount); }

Now we will have to create concrete implementation of algorithms for payment using credit/debit card or through paypal.
public class CreditCardStrategy implements PaymentStrategy { private String name; private String cardNumber; private String cvv; private String dateOfExpiry; public CreditCardStrategy(String nm, String ccNum, String cvv, String expiryDate){ this.name=nm; this.cardNumber=ccNum; this.cvv=cvv; this.dateOfExpiry=expiryDate; } @Override public void pay(int amount) { System.out.println(amount +" paid with credit/debit card"); } }

public class PaypalStrategy implements PaymentStrategy { private String emailId; private String password; public PaypalStrategy(String email, String pwd){ this.emailId=email; this.password=pwd; } @Override public void pay(int amount) { System.out.println(amount + " paid using Paypal."); } }

Now our strategy pattern example algorithms are ready. We can implement Shopping Cart and payment method will require input as Payment strategy

public class Item { private String upcCode; private int price; public Item(String upc, int cost){ this.upcCode=upc; this.price=cost; } public String getUpcCode() { return upcCode; } public int getPrice() { return price; } }

import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; public class ShoppingCart { //List of items List<Item> items; public ShoppingCart(){ this.items=new ArrayList<Item>(); } public void addItem(Item item){ this.items.add(item); } public void removeItem(Item item){ this.items.remove(item); } public int calculateTotal(){ int sum = 0; for(Item item : items){ sum += item.getPrice(); } return sum; } public void pay(PaymentStrategy paymentMethod){ int amount = calculateTotal(); paymentMethod.pay(amount); } }

Notice that payment method of shopping cart requires payment algorithm as argument and doesn’t store it anywhere as instance variable.

Let’s test our strategy pattern example setup with a simple program.

public class ShoppingCartTest { public static void main(String[] args) { ShoppingCart cart = new ShoppingCart(); Item item1 = new Item("1234",10); Item item2 = new Item("5678",40); cart.addItem(item1); cart.addItem(item2); //pay by paypal cart.pay(new PaypalStrategy("myemail@example.com", "mypwd")); //pay by credit card cart.pay(new CreditCardStrategy("Pankaj Kumar", "1234567890123456", "786", "12/15")); } }
Output of above program is:
50 paid using Paypal. 50 paid with credit/debit card

You may also like

Kubernetes Microservices
Python AI/ML
Spring Framework Spring Boot
Core Java Java Coding Question
Maven AWS