Open In App

Strategy Design Pattern

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Share
Report issue
Report

The Strategy Design Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing clients to switch algorithms dynamically without altering the code structure.

Strategy-Design-Pattern

What is the Strategy Design Pattern?

A strategy pattern is a behavioral design pattern that allows the behavior of an object to be selected at runtime. It is one of the Gang of Four (GoF) design patterns, which are widely used in object-oriented programming. In simpler terms, The Strategy Pattern allows you to define a family of algorithms, encapsulate each one of them, and make them interchangeable. This pattern lets the algorithm vary independently from clients that use it.

Characteristics of the Strategy Design Pattern?

The Strategy Design Pattern exhibits several key characteristics that make it distinctive and effective for managing algorithm variations in software systems:

  • It defines a family of algorithms: The pattern allows you to encapsulate multiple algorithms or behaviors into separate classes, known as strategies.
  • It encapsulates behaviors: Each strategy encapsulates a specific behavior or algorithm, providing a clean and modular way to manage different variations or implementations.
  • It enables dynamic behavior switching: The pattern enables clients to switch between different strategies at runtime, allowing for flexible and dynamic behavior changes.
  • It promotes object collaboration: The pattern encourages collaboration between a context object and strategy objects, where the context delegates the execution of a behavior to a strategy object.

Overall, the Strategy pattern is a useful design pattern that allows the behavior of an object to be selected dynamically at runtime, providing flexibility, modularity, and testability.

Components of the Strategy Design Pattern

Components-of-Strategy-Design-Pattern-

1. Context

The Context is a class or object that holds a reference to a strategy object and delegates the task to it.

  • It acts as the interface between the client and the strategy, providing a unified way to execute the task without knowing the details of how it’s done.
  • The Context maintains a reference to a strategy object and calls its methods to perform the task, allowing for interchangeable strategies to be used.

2. Strategy Interface

The Strategy Interface is an interface or abstract class that defines a set of methods that all concrete strategies must implement.

  • It serves as a contract, ensuring that all strategies adhere to the same set of rules and can be used interchangeably by the Context.
  • By defining a common interface, the Strategy Interface allows for decoupling between the Context and the concrete strategies, promoting flexibility and modularity in the design.

3. Concrete Strategies

Concrete Strategies are the various implementations of the Strategy Interface. Each concrete strategy provides a specific algorithm or behavior for performing the task defined by the Strategy Interface.

  • Concrete strategies encapsulate the details of their respective algorithms and provide a method for executing the task.
  • They are interchangeable and can be selected and configured by the client based on the requirements of the task.

4. Client

The Client is responsible for selecting and configuring the appropriate strategy and providing it to the Context.

  • It knows the requirements of the task and decides which strategy to use based on those requirements.
  • The client creates an instance of the desired concrete strategy and passes it to the Context, enabling the Context to use the selected strategy to perform the task.

Communication between the Components

In the Strategy Design Pattern, communication between the components occurs in a structured and decoupled manner. Here’s how the components interact with each other:

  • Client to Context:
    • The Client, which knows the requirements of the task, interacts with the Context to initiate the task execution.
    • The Client selects an appropriate strategy based on the task requirements and provides it to the Context.
    • The Client may configure the selected strategy before passing it to the Context if necessary.
  • Context to Strategy:
    • The Context holds a reference to the selected strategy and delegates the task to it.
    • The Context invokes a method on the strategy object, triggering the execution of the specific algorithm or behavior encapsulated within the strategy.
  • Strategy to Context:
    • Once the strategy completes its execution, it may return a result or perform any necessary actions.
    • The strategy communicates the result or any relevant information back to the Context, which may further process or utilize the result as needed.
  • Strategy Interface as Contract:
    • The Strategy Interface serves as a contract that defines a set of methods that all concrete strategies must implement.
    • The Context communicates with strategies through the common interface, promoting interchangeability and decoupling.
  • Decoupled Communication:
    • Communication between the components is decoupled, meaning that the Context does not need to know the specific details of how each strategy implements the task.
    • Strategies can be swapped or replaced without impacting the client or other strategies, as long as they adhere to the common interface.

Overall, communication in the Strategy Design Pattern involves the Context class invoking a method on the selected strategy object, which triggers the execution of a specific algorithm or behavior to perform a task. This separation of task execution from the selection and configuration of the strategy promotes flexibility, modularity, and code reusability within the software system.

Lets understand the components and communication using the Real-World example:

Real-World Analogy of Strategy Design Pattern

Imagine you’re planning a trip to a new city, and you have several options for getting there: by car, by train, or by plane. Each mode of transportation offers its own set of advantages and disadvantages, depending on factors such as cost, travel time, and convenience.

  • Context: You, as the traveler, represent the context in this analogy. You have a specific goal (reaching the new city) and need to choose the best transportation strategy to achieve it.
  • Strategies: The different modes of transportation (car, train, plane) represent the strategies in this analogy. Each strategy (mode of transportation) offers a different approach to reaching your destination.
  • Interface:
    • The interface in this analogy is the set of common criteria you consider when choosing a transportation mode, such as cost, travel time, and convenience.
    • These criteria serve as the common interface that all strategies must adhere to.
  • Flexibility:
    • Just as the Strategy Design Pattern allows you to dynamically switch between different algorithms at runtime, you have the flexibility to choose a transportation mode based on your specific requirements and constraints.
    • For example, if you value speed and are willing to pay more, you might choose to fly.
    • If you prioritize cost-effectiveness and don’t mind a longer travel time, you might opt for a train or car.
  • Dynamic Selection:
    • The Strategy Design Pattern allows you to dynamically select the best strategy (transportation mode) based on changing circumstances.
    • For instance, if your initial flight is canceled due to bad weather, you can quickly switch to an alternative mode of transportation, such as taking a train or renting a car, without having to change your overall travel plans drastically.

Strategy Design Pattern Example

Let’s consider a sorting application where we need to sort a list of integers. However, the sorting algorithm to be used may vary depending on factors such as the size of the list and the desired performance characteristics.

Challenges Without Using Strategy Pattern:

  • Limited Flexibility: Implementing sorting algorithms directly within the main sorting class can make the code inflexible. Adding new sorting algorithms or changing existing ones would require modifying the main class, which violates the Open/Closed Principle.
  • Code Duplication: Without a clear structure, you may end up duplicating sorting logic to handle different algorithms. This can lead to maintenance issues and inconsistency in the system.
  • Hard-Coded Logic: Implementing sorting logic directly within the main sorting class can make the code rigid and difficult to extend or modify. Making changes to the sorting algorithm becomes cumbersome and error-prone.

How Strategy Pattern helps to solve above challenges :

The Strategy Design Pattern addresses these challenges by encapsulating each sorting algorithm into separate classes. This allows for better organization, code reuse, and flexibility in the system. Here’s how the Strategy Pattern helps:

  • Code Reusability: By encapsulating sorting algorithms into separate strategy classes, you can reuse these strategies across different parts of the system. This reduces code duplication and promotes maintainability.
  • Flexibility and Extensibility: With the Strategy Pattern, you can easily add new sorting algorithms or change existing ones without modifying existing code. Each strategy is independent and can be swapped or extended without affecting other parts of the system.
  • Separation of Concerns: The Strategy Pattern promotes a clean separation of concerns by isolating sorting logic into separate strategy classes. This improves code readability, testability, and maintainability.

StrategyDesignPatternExample

Complete Code of the above example:

1. Context(SortingContext)

Java




public class SortingContext {
    private SortingStrategy sortingStrategy;
 
    public SortingContext(SortingStrategy sortingStrategy) {
        this.sortingStrategy = sortingStrategy;
    }
 
    public void setSortingStrategy(SortingStrategy sortingStrategy) {
        this.sortingStrategy = sortingStrategy;
    }
 
    public void performSort(int[] array) {
        sortingStrategy.sort(array);
    }
}


2. Strategy Interface(SortingStrategy)

Java




public interface SortingStrategy {
    void sort(int[] array);
}


3. Concrete Strategies

Java




// BubbleSortStrategy
public class BubbleSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Bubble Sort algorithm
        System.out.println("Sorting using Bubble Sort");
    }
}
 
// MergeSortStrategy
public class MergeSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Merge Sort algorithm
        System.out.println("Sorting using Merge Sort");
    }
}
 
// QuickSortStrategy
public class QuickSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Quick Sort algorithm
        System.out.println("Sorting using Quick Sort");
    }
}


4. Client Component

Java




public class Client {
    public static void main(String[] args) {
        // Create SortingContext with BubbleSortStrategy
        SortingContext sortingContext = new SortingContext(new BubbleSortStrategy());
        int[] array1 = {5, 2, 9, 1, 5};
        sortingContext.performSort(array1); // Output: Sorting using Bubble Sort
 
        // Change strategy to MergeSortStrategy
        sortingContext.setSortingStrategy(new MergeSortStrategy());
        int[] array2 = {8, 3, 7, 4, 2};
        sortingContext.performSort(array2); // Output: Sorting using Merge Sort
 
        // Change strategy to QuickSortStrategy
        sortingContext.setSortingStrategy(new QuickSortStrategy());
        int[] array3 = {6, 1, 3, 9, 5};
        sortingContext.performSort(array3); // Output: Sorting using Quick Sort
    }
}


Complete code for the above example

Below is the complete code for the above example:

Java




// SortingContext.java
class SortingContext {
    private SortingStrategy sortingStrategy;
 
    public SortingContext(SortingStrategy sortingStrategy) {
        this.sortingStrategy = sortingStrategy;
    }
 
    public void setSortingStrategy(SortingStrategy sortingStrategy) {
        this.sortingStrategy = sortingStrategy;
    }
 
    public void performSort(int[] array) {
        sortingStrategy.sort(array);
    }
}
 
// SortingStrategy.java
interface SortingStrategy {
    void sort(int[] array);
}
 
// BubbleSortStrategy.java
class BubbleSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Bubble Sort algorithm
        System.out.println("Sorting using Bubble Sort");
        // Actual Bubble Sort Logic here
    }
}
 
// MergeSortStrategy.java
class MergeSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Merge Sort algorithm
        System.out.println("Sorting using Merge Sort");
        // Actual Merge Sort Logic here
    }
}
 
// QuickSortStrategy.java
class QuickSortStrategy implements SortingStrategy {
    @Override
    public void sort(int[] array) {
        // Implement Quick Sort algorithm
        System.out.println("Sorting using Quick Sort");
        // Actual Quick Sort Logic here
    }
}
 
// Client.java
public class Client {
    public static void main(String[] args) {
        // Create SortingContext with BubbleSortStrategy
        SortingContext sortingContext = new SortingContext(new BubbleSortStrategy());
        int[] array1 = {5, 2, 9, 1, 5};
        sortingContext.performSort(array1); // Output: Sorting using Bubble Sort
 
        // Change strategy to MergeSortStrategy
        sortingContext.setSortingStrategy(new MergeSortStrategy());
        int[] array2 = {8, 3, 7, 4, 2};
        sortingContext.performSort(array2); // Output: Sorting using Merge Sort
 
        // Change strategy to QuickSortStrategy
        sortingContext.setSortingStrategy(new QuickSortStrategy());
        int[] array3 = {6, 1, 3, 9, 5};
        sortingContext.performSort(array3); // Output: Sorting using Quick Sort
    }
}


Output




Sorting using Bubble Sort
Sorting using Merge Sort
Sorting using Quick Sort


When to use the Strategy Design Pattern?

Here are some situations where you should consider using the Strategy pattern:

  • Multiple Algorithms:
    • When you have multiple algorithms that can be used interchangeably based on different contexts, such as sorting algorithms (bubble sort, merge sort, quick sort), searching algorithms, compression algorithms, etc.
  • Encapsulating Algorithms:
    • When you want to encapsulate the implementation details of algorithms separately from the context that uses them, allowing for easier maintenance, testing, and modification of algorithms without affecting the client code.
  • Runtime Selection:
    • When you need to dynamically select and switch between different algorithms at runtime based on user preferences, configuration settings, or system states.
  • Reducing Conditional Statements:
    • When you have a class with multiple conditional statements that choose between different behaviors, using the Strategy pattern helps in eliminating the need for conditional statements and making the code more modular and maintainable.
  • Testing and Extensibility:
    • When you want to facilitate easier unit testing by enabling the substitution of algorithms with mock objects or stubs. Additionally, the Strategy pattern makes it easier to extend the system with new algorithms without modifying existing code.

When not to use the Strategy Design Pattern?

Here are some situations where you should consider not using the Strategy pattern:

  1. Single Algorithm:
    • If there is only one fixed algorithm that will be used throughout the lifetime of the application, and there is no need for dynamic selection or switching between algorithms, using the Strategy pattern might introduce unnecessary complexity.
  2. Overhead:
    • If the overhead of implementing multiple strategies outweighs the benefits, especially in simple scenarios where direct implementation without the Strategy pattern is more straightforward and clear.
  3. Inflexible Context:
    • If the context class tightly depends on a single algorithm and there is no need for flexibility or interchangeability, using the Strategy pattern may introduce unnecessary abstraction and complexity.

Advantages of the Strategy Design Pattern

Below are the advantages of the strategy design pattern:

  • A family of algorithms can be defined as a class hierarchy and can be used interchangeably to alter application behavior without changing its architecture.
  • By encapsulating the algorithm separately, new algorithms complying with the same interface can be easily introduced.
  • The application can switch strategies at run-time.
  • Strategy enables the clients to choose the required algorithm, without using a “switch” statement or a series of “if-else” statements.
  • Data structures used for implementing the algorithm are completely encapsulated in Strategy classes. Therefore, the implementation of an algorithm can be changed without affecting the Context class.

Disadvantages the Strategy Design Pattern

Below are the disadvantages of the strategy design pattern:

  • The application must be aware of all the strategies to select the right one for the right situation.
  • Context and the Strategy classes normally communicate through the interface specified by the abstract Strategy base class. Strategy base class must expose interface for all the required behaviours, which some concrete Strategy classes might not implement.
  • In most cases, the application configures the Context with the required Strategy object. Therefore, the application needs to create and maintain two objects in place of one.

Further Read – Strategy Pattern Set Example and Implementation



Last Updated : 09 Feb, 2024
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads