Strategy pattern
Created on: Sep 22, 2024
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Let's understand this concept with an example. We have an e-commerce application that accepts different types of discounts. Say 10% discount for existing customers and 20% for new customers. Below is a service layer code that implements this.
public class CheckoutService { public double calculateTotalPrice(double totalAmount, String customerType) { double discount = 0; if ("new".equalsIgnoreCase(customerType)) { discount = totalAmount * 0.2; } else if ("regular".equalsIgnoreCase(customerType)) { discount = totalAmount * 0.1; } else { throw new IllegalArgumentException("Unknown customer type"); } return totalAmount - discount; } }
In the above code, If a requirement comes to add a new discount type, we have to modify the existing code in if else
statement. This violets Open closed principle and there is a high chance of bugs in the above code.
Let's create discount strategy for different options.
public interface DiscountStrategy { double applyDiscount(double totalPrice); } public class NewCustomerDiscount implements DiscountStrategy{ @Override public double applyDiscount(double totalPrice) { return totalPrice*0.2; } } public class RegularCustomerDiscount implements DiscountStrategy{ @Override public double applyDiscount(double totalPrice) { return totalPrice*0.1; } }
And place the DiscountStrategy directly in CheckoutService. This approach looks but adding a new DiscountStrategy would require us to modify the CheckoutService service. This leads to tightly coupled code.
public class CheckoutService { public double calculateTotalPrice(double totalPrice, DiscountStrategy discountStrategy) { return discountStrategy.applyDiscount(totalPrice); } }
Let's shift the strategy/algorithm to a new class say DiscountContext
. This class handles the logic for different discount strategies which is better as it follows the Single responsibility principle and open and closed principle.
public class DiscountContext { private DiscountStrategy discountStrategy; public DiscountContext(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public void setDiscountStrategy(DiscountStrategy discountStrategy) { this.discountStrategy = discountStrategy; } public double applyDiscount(double totalAmount){ return this.discountStrategy.applyDiscount(totalAmount); } } public class CheckoutService { private DiscountContext discountContext; public CheckoutService(DiscountContext discountContext) { this.discountContext = discountContext; } public void setDiscountContext(DiscountContext discountContext) { this.discountContext = discountContext; } public double calculateTotalPrice(double totalPrice) { return discountContext.applyDiscount(totalPrice); } }
Now if we add a new discount strategy say Seasonal Discount, it will not affect CheckoutService and we just need to add one more discount strategy.
In simple terms, the Strategy pattern allows you to define a set of algorithms, encapsulate each one, and make them interchangeable. This means you can switch between different algorithms (or strategies) without changing the context that uses them.
Important points
- It is used to change the behavior of a class without extending it.
- It is based on Open Closed design principle which promotes writing extensible code without touching existing code.
- It enables the system to switch from one algorithm to another.
- It makes easier to test code because it separates the algorithm from the client code.
Checkout entire code on my github
Feel free to connect me on linkedin. Happy coding!