Software Design Patterns and Principles - Part 13 (Strategy Design Pattern)
Source: Digital Ocean

Software Design Patterns and Principles - Part 13 (Strategy Design Pattern)


**** Previous Parts ****

Part 1: Introduction

Part 2: List of Most Used Patterns and Singleton Design Pattern

Part 3: Software Design Patterns and Principles - Part 3 (Factory Method and Abstract Factory Method Design Pattern)

Part 4: Software Design Patterns and Principles - Part 4 (Builder Design Pattern)

Part 5: Software Design Patterns and Principles - Part 5 (Adapter Design Pattern)

Part 6: Software Design Patterns and Principles - Part 6 (Decorator Design Pattern)

Part 7: Software Design Patterns and Principles - Part 7 (Composite Design Pattern)

Part 8: Software Design Patterns and Principles - Part 8 (Fa?ade Design Pattern)

Part 9: Software Design Patterns and Principles - Part 9 (Proxy Design Pattern)

Part 10: Software Design Patterns and Principles - Part 10 (Command Design Pattern)

Part 11: Software Design Patterns and Principles - Part 11 (Mediator Design Pattern)

Part 12: Software Design Patterns and Principles - Part 12 (Observer Design Pattern)


Story:

Sharif is a cunning person works as a salesman of a marketing team. His main job is convince people and sale goods. As we all know convince people is a tough task and different people convince in different way. So Sharif needs to meet many people with different thoughts and observe them closely. After that he needs to shift or switch his way of convincing based on peoples mode and choice. But the ultimate goal is convince and sell goods.

Strategy Design Pattern:

Definition:

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.

As the definition stat, strategy pattern gives a way of runtime algorithm change of some processing mechanism or so called algorithm. Without breaking any code or working procedure client just change the algorithm class and that's it.

Problem:

In the above story, one of the biggest problem of Sharif is different types of people handling. Not everyone convince with same approach. So he need different approach. But switching frequently between modes is tough. How can he handle that?

The Strategy design pattern is incredibly versatile and can be applied in various scenarios across software development. Here are some common situations where the Strategy pattern can be used effectively:

  1. Sorting Algorithms: Different sorting algorithms (such as bubble sort, quick sort, merge sort) can be encapsulated within separate strategy classes. Depending on the size or characteristics of the dataset, the appropriate sorting strategy can be selected dynamically.
  2. Data Validation: In a system where data validation rules may vary based on user roles or input sources, using the Strategy pattern allows you to encapsulate different validation strategies. For example, one strategy might enforce stricter validation for administrative users, while another might allow more leniency for regular users.
  3. File Compression: When implementing file compression utilities, different compression algorithms (e.g., gzip, zlib, bzip2) can be encapsulated as strategies. Users can then choose the compression strategy based on factors such as file type or desired compression ratio.
  4. Authentication and Authorization: In a system with multiple authentication methods (e.g., username/password, OAuth, JWT), each authentication method can be implemented as a separate strategy. Similarly, different authorization strategies (e.g., role-based access control, attribute-based access control) can be encapsulated using the Strategy pattern.
  5. Payment Processing: In an e-commerce application, various payment gateways (e.g., PayPal, Stripe) can be encapsulated as separate strategies. This allows the system to switch between payment processors seamlessly or even use multiple processors concurrently.
  6. Pathfinding Algorithms: In a game or navigation system, different pathfinding algorithms (e.g., A*, Dijkstra's algorithm, breadth-first search) can be implemented as strategies. The choice of algorithm may depend on factors such as computational resources or desired path optimality.
  7. Logging and Error Handling: Strategies can be used to handle logging and error handling in different ways. For example, one strategy might log errors to a local file, while another might send them to a remote server for monitoring and analysis.
  8. Cache Management: In systems where caching is used to improve performance, different caching strategies (e.g., least recently used, least frequently used, time-based expiration) can be encapsulated as strategies. This allows the system to adapt its caching strategy based on usage patterns or resource constraints.
  9. Game AI: In a game with AI opponents, different strategies can be employed based on the behavior of the player or other game conditions. Each AI strategy, such as aggressive, defensive, or balanced, can be implemented separately and switched dynamically during gameplay.
  10. Text Processing: In a text editor application, you might want to implement different text formatting strategies (e.g., Markdown, HTML, plain text) for displaying or exporting documents. Users can choose the desired formatting strategy based on their needs.
  11. Image Processing: In an image editing application, various image manipulation algorithms (e.g., resizing, cropping, filtering) can be implemented as strategies. Users can select the appropriate manipulation strategy depending on the desired effect for their images.

These are just a few examples of how the Strategy design pattern can be applied in software development to achieve flexibility, maintainability, and reusability in code.

Solution:

In the above story, Sharif can define different strategy for convince people of a different type. All the strategy's end goal is convincing. So Sharif can easily switch between these strategy based on the scenario and problem solved.

We can also use or implement strategy pattern as a solution of the problems stated earlier sections as well.

UML Diagram:

Source: dotnettricks.com

Here in UML diagram we can see that, Client (sometime also called context) holds a reference of Strategy interface. And other ConcreteStrategy class implements the same Strategy interface. So Client can change the ConcreteStrategy in runtime for algorithm change.

When to use:

  1. Use the Strategy pattern when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime.
  2. Use the Strategy when you have a lot of similar classes that only differ in the way they execute some behavior.
  3. Use the pattern to isolate the business logic of a class from the implementation details of algorithms that may not be as important in the context of that logic.
  4. Use the pattern when your class has a massive conditional statement that switches between different variants of the same algorithm.

Implementation:

// Define the strategy interface
interface PaymentStrategy {
    void pay(double amount);
}

// Concrete strategy: Credit Card payment
class CreditCardPayment implements PaymentStrategy {
    private String cardNumber;
    private String expiryDate;
    private String cvv;

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

    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using Credit Card");
        // Additional logic for processing credit card payment
    }
}

// Concrete strategy: PayPal payment
class PayPalPayment implements PaymentStrategy {
    private String email;
    private String password;

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

    @Override
    public void pay(double amount) {
        System.out.println("Paid $" + amount + " using PayPal");
        // Additional logic for processing PayPal payment
    }
}

// Context class
class ShoppingCart {
    private PaymentStrategy paymentStrategy;

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

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

// Client code
public class StrategyPatternExample {
    public static void main(String[] args) {
        ShoppingCart cart = new ShoppingCart();

        // Customer wants to pay using Credit Card
        cart.setPaymentStrategy(new CreditCardPayment("1234567890123456", "12/25", "123"));
        cart.checkout(100.0);

        // Customer changes payment method to PayPal
        cart.setPaymentStrategy(new PayPalPayment("[email protected]", "password123"));
        cart.checkout(50.0);
    }
}        

Another C# implementation of Strategy design pattern.


Achieved Design Principles:

The Strategy design pattern achieves several Object-Oriented Programming (OOP) and software design principles, including:

  1. Encapsulation: Each strategy (or algorithm) is encapsulated within its own class, allowing its implementation details to be hidden from the client code. This promotes encapsulation, a fundamental principle of OOP, by preventing direct access to strategy internals and ensuring that changes to one strategy do not affect others.
  2. Abstraction: The Strategy pattern abstracts the behavior of each algorithm into a common interface or base class. This abstraction allows clients to interact with different strategies using a unified interface, without needing to know the specific details of each strategy's implementation.
  3. Inheritance/Polymorphism: Strategies are typically implemented using inheritance and polymorphism. Each strategy class inherits from a common base class or implements a shared interface, enabling polymorphic behavior. This allows clients to use strategies interchangeably, treating them uniformly based on their common interface.
  4. Composition over Inheritance: The Strategy pattern favors composition over inheritance by allowing algorithms to be composed with client objects. Instead of inheriting behavior directly, clients can hold a reference to a strategy object and delegate behavior to it dynamically. This promotes code reuse and flexibility, as strategies can be easily swapped or extended without modifying client code.
  5. Single Responsibility Principle (SRP): Each strategy class has a single responsibility: encapsulating a specific algorithm or behavior. This adherence to the SRP promotes code maintainability and modularity, as changes to one strategy are isolated and do not affect other parts of the system.
  6. Open/Closed Principle (OCP): The Strategy pattern supports the Open/Closed Principle by allowing new strategies to be added without modifying existing client code. Clients depend on abstractions (interfaces or base classes) rather than concrete implementations, making it easy to extend the system with new strategies without altering the existing codebase.
  7. Dependency Inversion Principle (DIP): The Strategy pattern facilitates the Dependency Inversion Principle by decoupling client code from concrete strategy implementations. Clients depend on abstractions (interfaces or base classes) rather than concrete classes, allowing them to be independent of specific implementations and promoting flexibility and extensibility.

By adhering to these principles, the Strategy design pattern promotes modular, flexible, and maintainable software designs, making it a valuable tool in the object-oriented programmer's toolbox.


Happy Learning !!!

Happy Coding !!!

Happy Programming !!!

Saiful Islam Rasel

Senior Engineer, SDE @ bKash | Ex: AsthaIT | Sports Programmer | Problem Solver | FinTech | Microservice | Java | Spring-boot | C# | .NET | PostgreSQL | DynamoDB | JavaScript | TypeScript | React.js | Next.js | Angular

8 个月
回复

要查看或添加评论,请登录

社区洞察

其他会员也浏览了