Drani Academy – Interview Question, Search Job, Tuitorials, Cheat Sheet, Project, eBook

Design Patterns

Tutorials – Design Patterns

 
Chapter 25: Strategy Pattern

 

The Strategy Pattern is a behavioral design pattern that enables an algorithm’s behavior to be selected at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This pattern promotes flexibility and helps ensure that the behavior of a class can change without altering its structure.

The Strategy Pattern is particularly useful when you need to switch between different algorithms or behaviors dynamically in an application.

Understanding the Strategy Pattern

In software development, you often encounter situations where different algorithms or strategies can be applied to solve a specific problem. The Strategy Pattern provides a mechanism to define a family of algorithms, encapsulate each one as a separate class, and make them interchangeable. This allows you to select the appropriate algorithm at runtime without modifying the client code.

Key Participants:

  1. Context: This is the class that contains a reference to a strategy object. The context is responsible for invoking the strategy’s algorithm.
  2. Strategy: The strategy is an interface or an abstract class that defines a common interface for all concrete strategies. It typically includes a method that represents the algorithm.
  3. Concrete Strategies: Concrete strategy classes implement the strategy interface. They provide their specific implementations of the algorithm.

How It Works

The Strategy Pattern follows a straightforward workflow:

  1. The context class contains a reference to a strategy object and has a method that invokes the algorithm defined in the strategy interface.
  2. The strategy interface declares the method that encapsulates the algorithm to be used.
  3. Concrete strategy classes implement the strategy interface, providing their unique implementations of the algorithm.
  4. At runtime, you can switch between different strategies by setting the context’s strategy to an instance of a concrete strategy class.

By using the Strategy Pattern, you can achieve more flexible and maintainable code by separating the algorithm’s logic from the client code and allowing for dynamic switching between strategies.

Use Cases

The Strategy Pattern is suitable for various scenarios where you want to provide multiple algorithms or behaviors for a specific task. Use cases include:

  1. Sorting Algorithms: Implementing various sorting algorithms like quicksort, bubblesort, or mergesort, and selecting the appropriate one at runtime.
  2. Payment Gateways: Handling payments with different payment providers (e.g., PayPal, Stripe) and allowing users to select their preferred provider.
  3. Compression Algorithms: Supporting different compression techniques (e.g., gzip, zip, or lz4) in a file compression utility.
  4. Data Validation: Implementing various validation rules for user input and allowing users to choose the validation strategy.
  5. Route Planning: Selecting different routing algorithms (e.g., shortest path, fastest route) in a navigation application.

Implementing the Strategy Pattern

To implement the Strategy Pattern, follow these steps:

  1. Create the Strategy Interface or Abstract Class: Define an interface or abstract class that declares the method to encapsulate the algorithm.
  2. Create Concrete Strategy Classes: Implement concrete strategy classes that implement the strategy interface and provide their specific algorithm implementations.
  3. Create the Context Class: The context class contains a reference to a strategy object and has a method that invokes the strategy’s algorithm.
  4. Client Code: In your application code, create instances of concrete strategy and context classes, and set the context’s strategy to the desired concrete strategy. Invoke the context’s method to execute the selected algorithm.

Benefits of the Strategy Pattern

The Strategy Pattern offers several advantages:

  1. Flexibility: It allows you to switch between different algorithms at runtime, providing dynamic behavior.
  2. Open-Closed Principle: It adheres to the open-closed principle, as you can add new strategies without modifying existing code.
  3. Separation of Concerns: It separates the algorithm’s implementation from the client code, promoting a cleaner code structure.
  4. Code Reusability: Concrete strategies can be reused in different contexts, promoting code reusability.

Drawbacks of the Strategy Pattern

While the Strategy Pattern provides many benefits, it also has some drawbacks:

  1. Complexity: Implementing the pattern may introduce additional classes, which can make the code more complex.
  2. Increased Number of Classes: If you have a large number of strategies, it can lead to a proliferation of strategy classes.

Example: Sorting Algorithms

Let’s implement the Strategy Pattern in Python for a sorting algorithm example. We’ll define a SortStrategy interface with a sort method, and create concrete strategy classes for different sorting algorithms (e.g., quicksort and bubblesort). The context class, Sorter, will utilize the selected strategy to perform sorting.

# Strategy Interface
class SortStrategy:
    def sort(self, data):
        pass
# Concrete Strategy Classes
class QuickSortStrategy(SortStrategy):
    def sort(self, data):
        return sorted(data)  # Using Python's built-in sort function
class BubbleSortStrategy(SortStrategy):
    def sort(self, data):
        n = len(data)
        for i in range(n):
            for j in range(0, n - i - 1):
                if data[j] > data[j + 1]:
                    data[j], data[j + 1] = data[j + 1], data[j]
        return data
# Context Class
class Sorter:
    def __init__(self, strategy):
        self.strategy = strategy
    def set_strategy(self, strategy):
        self.strategy = strategy
    def perform_sort(self, data):
        return self.strategy.sort(data)
# Client Code
data = [10, 2, 7, 5, 8, 1]
sorter = Sorter(QuickSortStrategy())
sorted_data = sorter.perform_sort(data)
print("QuickSort Result:", sorted_data)
sorter.set_strategy(BubbleSortStrategy())
sorted_data = sorter.perform_sort(data)
print("BubbleSort Result:", sorted_data)

In this example, we have created a SortStrategy interface and implemented two concrete strategies: QuickSortStrategy and BubbleSortStrategy. The Sorter class, which acts as the context, can switch between different sorting algorithms using the strategy pattern.

Conclusion

The Strategy Pattern is a powerful design pattern that promotes flexibility and maintainability in software development. By encapsulating algorithms as separate strategies, you can dynamically switch between behaviors at runtime without modifying existing code. This pattern is particularly useful when dealing with a family of algorithms or behaviors, allowing you to make your software more adaptable and extensible.

In the next chapter, we’ll explore the Chain of Responsibility Pattern, which deals with handling requests by passing them through a chain of handlers.

Scroll to Top