LLD - Strategy Design Pattern

LLD -  Strategy Design Pattern

Understanding Strategy Design Pattern

This behavioral design pattern enables the selection of an algorithm’s implementation at runtime.

Problem it Solves: When you have multiple variations of an algorithm or behavior, embedding them into a single class using conditionals (if-else or switch-case) leads to rigid and unmaintainable code. The Strategy pattern addresses this by allowing behaviors to be selected dynamically.

Easy Explanation: Exam Preparation Strategy

In college, your exam preparation strategy varied a lot depending on how far away the date of the exam was.

Strategies:

  • 2 months before the exam: Study casually, covering the basics

  • 1 month before the exam: Study in more detail for a deeper understanding

  • 1 week before the exam: Focus on important topics and take practice tests

  • 1 day before the exam: Quick revision and review of previous year's questions

Blueprint of Strategy Design Pattern:

  1. Strategy Interface: Define an interface that represents the strategy contract.

  2. Concrete Strategies: Implement concrete classes that implement the strategy interface. Each concrete strategy represents a specific algorithm or behavior.

  3. Context Class: Create a context class that holds a reference to the current strategy object. This class interacts with the strategy objects and delegates tasks to them.

  4. Client Code: Use the context class to select and execute a specific strategy at runtime.

UML Class Diagram

Code:

// Exam Preparation Strategy Interface
public interface ExamStrategy {
    void prepare();
}

// 2 Months Before Exam Strategy
public class CasualStudyStrategy implements ExamStrategy {
    @Override
    public void prepare() {
        System.out.println("2 months before exam: Casual Study");
    }
}

// 1 Month Before Exam Strategy
public class FocusedStudyStrategy implements ExamStrategy {
    @Override
    public void prepare() {
        System.out.println("1 Month Before Exam: Focused Study");
    }
}

// 1 Week Before Exam Strategy
public class IntensiveStudyStrategy implements ExamStrategy {
    @Override
    public void prepare() {
        System.out.println("1 Week Before Exam: Intensive Study");
    }
}

// 1 Day Before Exam Strategy
public class LastMinuteRevisionStrategy implements ExamStrategy {
    @Override
    public void prepare() {
        System.out.println("1 Day Before Exam: Last Minute Revision");
    }
}

// Student (Context)
public class Student {
    private ExamStrategy examStrategy;

    // Set the strategy dynamically
    public void setExamStrategy(ExamStrategy strategy) {
        this.examStrategy = strategy;
    }

    // Perform preparation based on the selected strategy
    public void prepareForExam() {
        examStrategy.prepare();
    }
}

// Client Code
public class ExamPreparationDemo {
    public static void main(String[] args) {
        Student student = new Student();

        // 2 months before the exam
        student.setExamStrategy(new CasualStudyStrategy());
        student.prepareForExam();

        // 1 month before the exam
        student.setExamStrategy(new FocusedStudyStrategy());
        student.prepareForExam();

        // 1 week before the exam
        student.setExamStrategy(new IntensiveStudyStrategy());
        student.prepareForExam();

        // 1 day before the exam
        student.setExamStrategy(new LastMinuteRevisionStrategy());
        student.prepareForExam();
    }
}

Here are a few other examples to build intuition around Strategy Design Patterns

Navigation Strategy:

Google Maps supports different navigation options like Driving, Walking or Cycling

// Navigation Strategy Interface
public interface NavigationStrategy {
    void navigate(String start, String end);
}

// Driving Route Strategy
public class DrivingRoute implements NavigationStrategy {
    @Override
    public void navigate(String start, String end) {
        System.out.println("Calculating driving route from " + start + " to " + end);
    }
}

// Walking Route Strategy
public class WalkingRoute implements NavigationStrategy {
    @Override
    public void navigate(String start, String end) {
        System.out.println("Calculating walking route from " + start + " to " + end);
    }
}

// Cycling Route Strategy
public class CyclingRoute implements NavigationStrategy {
    @Override
    public void navigate(String start, String end) {
        System.out.println("Calculating cycling route from " + start + " to " + end);
    }
}

// Navigation App (Context)
public class NavigationApp {
    private NavigationStrategy navigationStrategy;

    public void setNavigationStrategy(NavigationStrategy strategy) {
        this.navigationStrategy = strategy;
    }

    public void navigate(String start, String end) {
        navigationStrategy.navigate(start, end);
    }
}

// Client Code
public class NavigationDemo {
    public static void main(String[] args) {
        NavigationApp app = new NavigationApp();

        // Driving Strategy
        app.setNavigationStrategy(new DrivingRoute());
        app.navigate("Home", "Office");

        // Walking Strategy
        app.setNavigationStrategy(new WalkingRoute());
        app.navigate("Home", "Park");

        // Cycling Strategy
        app.setNavigationStrategy(new CyclingRoute());
        app.navigate("Home", "Gym");
    }
}

Payment Strategy:

A payment system that supports multiple payment options like - Credit Card Payment, Paypal Payment, UPI Payment, Crypto Payment, etc

// Payment Strategy Interface
public interface PaymentStrategy {
    void pay(int amount);
}

// Credit Card Payment Strategy
public class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;

    public CreditCardPayment(String cardNumber) {
        this.cardNumber = cardNumber;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using Credit Card " + cardNumber);
    }
}

// PayPal Payment Strategy
public class PayPalPayment implements PaymentStrategy {
    private String email;

    public PayPalPayment(String email) {
        this.email = email;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using PayPal account: " + email);
    }
}

// UPI Payment Strategy
public class UPIPayment implements PaymentStrategy {
    private String upiId;

    public UPIPayment(String upiId) {
        this.upiId = upiId;
    }

    @Override
    public void pay(int amount) {
        System.out.println("Paid " + amount + " using UPI ID: " + upiId);
    }
}

// Checkout System (Context)
public class CheckoutSystem {
    private PaymentStrategy paymentStrategy;

    public void setPaymentStrategy(PaymentStrategy strategy) {
        this.paymentStrategy = strategy;
    }

    public void checkout(int amount) {
        paymentStrategy.pay(amount);
    }
}

// Client Code
public class PaymentDemo {
    public static void main(String[] args) {
        CheckoutSystem checkout = new CheckoutSystem();

        // Pay via Credit Card
        checkout.setPaymentStrategy(new CreditCardPayment("1234-5678-9876"));
        checkout.checkout(200);

        // Switch to PayPal
        checkout.setPaymentStrategy(new PayPalPayment("user@example.com"));
        checkout.checkout(150);

        // Switch to UPI
        checkout.setPaymentStrategy(new UPIPayment("user@upi"));
        checkout.checkout(300);
    }
}

Compression Strategy:

A system that supports different file compression strategies like Zip, RAR, 7z, etc

// Compression Strategy Interface
public interface CompressionStrategy {
    void compress(String fileName);
}

// ZIP Compression Strategy
public class ZIPCompression implements CompressionStrategy {
    @Override
    public void compress(String fileName) {
        System.out.println("Compressing " + fileName + " using ZIP format");
    }
}

// RAR Compression Strategy
public class RARCompression implements CompressionStrategy {
    @Override
    public void compress(String fileName) {
        System.out.println("Compressing " + fileName + " using RAR format");
    }
}

// 7z Compression Strategy
public class SevenZCompression implements CompressionStrategy {
    @Override
    public void compress(String fileName) {
        System.out.println("Compressing " + fileName + " using 7z format");
    }
}

// Compression Utility (Context)
public class CompressionUtility {
    private CompressionStrategy compressionStrategy;

    public void setCompressionStrategy(CompressionStrategy strategy) {
        this.compressionStrategy = strategy;
    }

    public void compressFile(String fileName) {
        compressionStrategy.compress(fileName);
    }
}

// Client Code (Main Method)
public class CompressionDemo {
    public static void main(String[] args) {
        CompressionUtility utility = new CompressionUtility();

        // Using ZIP compression
        utility.setCompressionStrategy(new ZIPCompression());
        utility.compressFile("file1.txt");

        // Switch to RAR compression
        utility.setCompressionStrategy(new RARCompression());
        utility.compressFile("file2.txt");

        // Switch to 7z compression
        utility.setCompressionStrategy(new SevenZCompression());
        utility.compressFile("file3.txt");
    }
}

Text Formatting Strategy:

Text Editor supports different text formatting options like Bold, Italic, and Underline.

// Text Formatting Strategy Interface
public interface TextFormattingStrategy {
    String format(String text);
}

// Bold Text Formatting Strategy
public class BoldTextStrategy implements TextFormattingStrategy {
    @Override
    public String format(String text) {
        return "**" + text + "**";  // Markdown-style bold
    }
}

// Italic Text Formatting Strategy
public class ItalicTextStrategy implements TextFormattingStrategy {
    @Override
    public String format(String text) {
        return "*" + text + "*";  // Markdown-style italic
    }
}

// Underline Text Formatting Strategy
public class UnderlineTextStrategy implements TextFormattingStrategy {
    @Override
    public String format(String text) {
        return "__" + text + "__";  // Markdown-style underline
    }
}

// Text Editor (Context)
public class TextEditor {
    private TextFormattingStrategy formattingStrategy;

    public void setFormattingStrategy(TextFormattingStrategy strategy) {
        this.formattingStrategy = strategy;
    }

    public String formatText(String text) {
        return formattingStrategy.format(text);
    }
}

// Client Code
public class TextEditorDemo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();

        // Bold Text Strategy
        editor.setFormattingStrategy(new BoldTextStrategy());
        System.out.println(editor.formatText("Hello World!"));  // Output: **Hello World!**

        // Italic Text Strategy
        editor.setFormattingStrategy(new ItalicTextStrategy());
        System.out.println(editor.formatText("Hello World!"));  // Output: *Hello World!*

        // Underline Text Strategy
        editor.setFormattingStrategy(new UnderlineTextStrategy());
        System.out.println(editor.formatText("Hello World!"));  // Output: __Hello World!__
    }
}

Benefits:

  1. OCP: Strategy pattern promotes the Open/Closed Principles as new algorithms or strategies can be added without modifying the existing code

  2. Flexibility: It provides flexibility to switch strategies dynamically at run time

  3. Avoids Conditional Logic: It helps to avoid multiple conditionals like if-else or switch by encapsulating behaviors in separate classes.