Like always, let's start with questions: What is the Strategy Design Pattern, and when can we use it?
The Strategy Design Pattern helps you to define a family of algorithms by wrapping each one in its own class, and make them interchangeable. This pattern enables the algorithm to vary independently and dynamically from the clients that use it.
Let's make it more simple.
The Strategy Pattern is like choosing a method/algorithm to solve a problem. You can pick a specific solution(algorithm) based on the situation, and you can change it whenever you need.
Before moving forward, let's look at some real-life examples:
Sorting Algorithms: We have a wide variety of algorithms, but we can't use the same one in all scenarios or situations. Based on the requirements and use cases, we need to choose different algorithms.
Amazon Search Engine: Amazon operates worldwide, so the search algorithm and results should vary based on the location.
Swiggy or Zomato Pricing: The pricing algorithm changes based on factors such as time, type of city (whether it is tier 1, 2, or 3), and weather conditions. we can also use strategy for delivery partner allocation. The algorithm would change base on the customer/driver rating, premium or normal customer.
Lets also know the wrong approach
public static void main(String[] args) {
WrongApproach wrongApproach = new WrongApproach();
int busFare = wrongApproach.fairCalculator("busFare", 100);
int trainfare = wrongApproach.fairCalculator("trainFare", 200);
int taxiFare = wrongApproach.fairCalculator("taxiFare", 300);
System.out.println("Total bus fare: " + busFare);
System.out.println("Total train fare: " + trainfare);
System.out.println("Total taxi fare: " + taxiFare);
}
private int fairCalculator(String vehicleType, int distance){
int result = 0;
int rate = 0;
if(vehicleType.equals("busFare")){
//algo to calculate the bus fare
rate = 6*distance;
}else if(vehicleType.equals("trainFare")){
//algo to calculate the train fare
rate = 10*distance;
}else if(vehicleType.equals("taxiFare")){
//algo to calculate the taxi fare
rate = 10*distance;
}else {
//algo to calculate the default fare
rate = 5*distance;
}
return rate;
}
This code violates several SOLID principles:
Single Responsibility Principle (SRP): The method fairCalculator is responsible for calculating fares for all types of vehicles. Each vehicle type should have its own responsibility of calculating the fare, but they are all bundled together in a single method.
Open/Closed Principle (OCP): The code is not open for extension but is closed for modification. To add a new vehicle type for ex: bicycle or airplane. you have to modify the fairCalculator method. This violates the principle of extending without modifying existing code.
Dependency Inversion Principle (DIP): The WrongApproach class directly depends on vehicle types busFare, trainFare, etc.. The code should depend on abstractions rather than concrete classes. If you use polymorphism, you can decouple the code from specific vehicle types.
To improve this code, you can use strategy by Use of polymorphism and interfaces for the fare calculation algorithms.
Now, Let's see how we can code it so that it follows all the SOLID principles.
Structure/UML diagram:
relate the code with above uml diagram.
public interface FareCalculator {
double calculateFare(double distance);
}
public class BusFareCalAlgo implements FareCalculator{
@Override
public double calculateFare(double distance) {
System.out.println("Calculating bus fare");
return distance*6;
}
}
public class TaxiFareCalAlgo implements FareCalculator{
@Override
public double calculateFare(double distance) {
System.out.println("Calculating tarin fare for :"+ distance);
return distance*10;
}
}
public class TrainFareCalAlgo implements FareCalculator{
@Override
public double calculateFare(double distance) {
System.out.println("Calculating train fare distance: " + distance);
return distance*2;
}
}
public class FareProcessor {
private FareCalculator fareStrategy;
public FareProcessor(FareCalculator fareStrategy){
this.fareStrategy = fareStrategy;
}
double fareCal(int amount){
return fareStrategy.calculateFare(amount);
}
}
public class Client {
public static void main(String[] args) {
FareCalculator busFareCal = new BusFareCalAlgo();
FareProcessor fp = new FareProcessor(busFareCal);
double fare = fp.fareCal(100);
System.out.println("Bus fare for 100 km: " + fare);
FareCalculator trainFareCalAlgo = new TrainFareCalAlgo();
FareProcessor trainFareProcessor = new FareProcessor(trainFareCalAlgo);
double trainFare = trainFareProcessor.fareCal(150);
System.out.println("Train fare for 150 km: " + trainFare);
FareCalculator taxiFareCal = new TaxiFareCalAlgo();
FareProcessor taxiFareProcessor = new FareProcessor(taxiFareCal);
double taxiFare = taxiFareProcessor.fareCal(200);
System.out.println("Taxi fare for 200 km: " + taxiFare);
}
}
In conclusion, the Strategy Design Pattern helps to manage different algorithms/behaviors by encapsulating them in separate classes. It makes your code cleaner, more flexible, and easier to extend. Using this pattern allows you to select and change algorithms at runtime based on specific needs.
link to github: blog.lld/src/main/java/org/example at main 路 rao3355/blog.lld
Stay tuned for more on system design and design patterns in my next blogs. 馃槉