Strategy Design Pattern

Do you Know The Strategy Design Pattern?

What is Strategy Design Pattern?

Do you use Strategies?

We all have dealt with strategies in certain moments of our life. In more technical words strategy is a set of planned actions designed to achieve a specific goal. Simply, you try out many strategies when a final exam is close by. You try to collect photo copied notes from friends and study all of them within a week, you try to go to any knowledge transferring discussions for missed lessons, you try to watch some videos on YouTube and learn the subject and many more creative strategies when the exams are getting closer. From one method or another, you were able to get good marks to pass the exam. Likewise, strategy design pattern provides multiple solutions for a recurring problem in the software world.

If you are not sure about design patterns please read Introduction to Design Patterns.

 Objects are behaving

As a norm in software design and development world, isolated components are called as objects. To build a successful system we need to have many objects with the ability to communicate and co-work with each other. But in most complex systems objects’ interactions cause many issues. Behavioral design patterns provide solutions to this object communication and interaction problems.

Define Strategy Pattern

The Strategy Pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one in a separate class, and make them interchangeable at runtime. This pattern enables you to select and use the most appropriate algorithm for a particular situation without affecting the rest of the system.

In the Strategy Pattern, a context class maintains a reference to a strategy object, which represents the current algorithm to be executed. The strategy object is an instance of a class that implements a common interface, defining a consistent method signature for all algorithms in the family. This interface is used by the context class to delegate the responsibility of executing the algorithm to the strategy object.

The key components of the Strategy Pattern are:

  1. Context: The class that uses a strategy object to perform a specific task or operation. It maintains a reference to the current strategy and interacts with the strategy through the common interface.
  2. Strategy Interface: The common interface that all strategy classes implement. It defines the method signature for executing the algorithm.
  3. Concrete Strategies: The classes that implement the strategy interface, each encapsulating a specific algorithm or behavior. These classes are interchangeable, allowing the context to use any algorithm from the family without modifying its own implementation.

The Strategy Pattern promotes modularity, flexibility, and maintainability in the codebase by decoupling the algorithms from the classes that use them. It allows you to add, remove, or modify algorithms easily without affecting other parts of the code, and it enables the selection of the most suitable algorithm at runtime based on various factors.

This is the more formal definition from the GOF team,


Strategy pattern defines a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.


How Strategy Pattern Works

This pattern is most suitable when we have multiple solutions to one problem. In a more technical way there are multiple algorithms to achieve a specific task. Each algorithm is independent of one another and enclosed with own implantation. But all of those algorithms is capable of resolving that specific problem likewise anyone can replace one algorithm from another depending on their requirement. Due to that feature, each algorithm is independent of the client who uses it. Algorithm content and implementation are decoupled from the client who uses it and the client can select any upon runtime based on individual parameters and conditions. Below figure describes the overall mechanism of the strategy pattern.

Strategy Design Pattern Overview
Strategy Design Pattern Overview

What is Algorithm in Strategy Pattern?

In the context of the Strategy Pattern, an algorithm refers to a specific method, behavior, or technique that solves a problem or achieves a desired outcome. The Strategy Pattern is a behavioral design pattern that enables you to define a family of algorithms, encapsulate each one in a separate class (called a strategy), and make them interchangeable at runtime. This allows you to choose and apply the most suitable algorithm for a particular situation without affecting the rest of the system.

Each strategy class implements a common interface, which ensures that all strategies have a consistent method signature. This interface is then used by the context class to call the desired algorithm. The context class delegates the responsibility of executing the algorithm to the strategy class, so it does not need to know the specifics of the algorithm’s implementation.

By encapsulating different algorithms within separate strategy classes, the Strategy Pattern promotes modularity, flexibility, and maintainability. It enables you to add, remove, or modify algorithms easily without affecting other parts of the code, and it allows for the selection of the most appropriate algorithm at runtime based on various factors such as user input, configuration settings, or environmental conditions.

When to use the Strategy Pattern?

The Strategy Pattern is best used in the following scenarios:

  • Different behaviors or algorithms: When you have multiple behaviors or algorithms that can be applied to solve a particular problem and these behaviors or algorithms can be selected interchangeably during runtime.
  • Open/Closed Principle: When you want to adhere to the Open/Closed Principle, which means that your code should be open for extension but closed for modification. The Strategy Pattern allows you to introduce new strategies without changing the context class or modifying existing strategies.
  • Decoupling: When you want to decouple the behavior or algorithm from the class that uses it, making your code more modular and easier to maintain and extend.
  • Reduce conditional complexity: When your codebase has complex conditional logic that can be organized into separate strategy classes. This simplifies the code and makes it more readable, maintainable, and testable.
  • Runtime strategy selection: When you need to select a behavior or algorithm at runtime based on user input, configuration settings, or other factors.
  • Reusability: When you want to reuse the same behavior or algorithm across different classes or applications. By encapsulating the behavior in a strategy class, you can easily reuse it in multiple contexts.
  • Testability: When you want to improve the testability of your code. The Strategy Pattern makes it easier to test each strategy independently, and you can also test the context class with mock strategies.
  • Flexibility and adaptability: When you need your application to be flexible and adaptable to changing requirements or dynamic environments, the Strategy Pattern enables you to swap out or update strategies without affecting the rest of the system.
  • Encapsulating domain knowledge: When domain knowledge is spread across multiple classes or modules, the Strategy Pattern can help you encapsulate that knowledge into separate strategy classes. This can make the code easier to understand, maintain, and evolve.
  • Separation of concerns: When you want to achieve a clear separation of concerns, the Strategy Pattern enables you to separate the implementation details of an algorithm from the class that uses it. This can lead to a cleaner, more organized codebase.
  • Compliance with Interface Segregation Principle: When you want to comply with the Interface Segregation Principle, which states that a class should not be forced to implement interfaces it does not use. The Strategy Pattern allows you to implement specific behaviors or algorithms in separate strategy classes, rather than forcing a single class to implement multiple behaviors.
  • Simplifying unit testing: When you want to simplify unit testing for classes with multiple behaviors or algorithms. The Strategy Pattern allows you to test each strategy independently and mock strategies when testing the context class, resulting in more focused and manageable unit tests.
  • Enhancing code readability: When the code becomes difficult to read due to multiple nested conditionals or complex logic. The Strategy Pattern can help improve code readability by encapsulating the different behaviors in separate classes, making the code easier to understand and navigate.
  • Promoting code reusability and DRY principle: When you have similar code repeated in multiple places, the Strategy Pattern can help you promote code reusability and adhere to the DRY (Don’t Repeat Yourself) principle by encapsulating the common behavior in a single strategy class.

List Class use Strategy Pattern?

Yes. You can see from the below diagram how the List class has used the strategy pattern. Let’s assume that you want to sort some numbers and you decide to use List class to achieve this.

List class uses the strategy pattern via SortingStrategy interface. There are 3 candidate sorting classes as Bubble Sort, Selection Sort and Insertion Sort which contain different sorting mechanisms.

  • Bubble Sort: Repeatedly compare neighbour pairs and swap if necessary
  • Selection Sort: Repeatedly pick the smallest element to append to the result
  • Insertion Sort: Repeatedly add new element to the sorted result

All 3 sorting algorithms will implement the Sorting Strategy interface. The sorting algorithm to use with our List class will be decided at the runtime depending on the requirement and conditions dynamically based on client’s selection.

List Object Strategy
List Object Strategy

Take the implementation of the collection. If the list has a sorting algorithm built into it – you cannot change the sorting algorithm. But when strategy pattern is injected, the sorting algorithm can be used independently while keeping the List implementation stable.

Real World Example

Strategy pattern usage can be seen in many real world scenarios as well. Let’s take an item purchasing at a shopping mall.

Basic Steps to Perform

  1. Customer comes to the cashier
  2. System accepts customer details
  3. System calculates bill amount
  4. System apply discount based on day of the week (This is where the strategy pattern comes into the picture)
    1. Monday – 10 %
    2. Friday – 50 %
  5. System output the total bill for the customer

System has to select which discount option assign to the bill based on the day of the week

  • Problem – System has to apply discounts to bills
  • Solution – There are 3 options for discounts based on days – system has to select one and calculate the bill amount

High-Level Class Diagram for the Shopping Mall Scenario

Above figure contains the code samples for the main interface and implementing concrete classes. The rest of the class implementations are described later.

  • Context – includes client or decision logic to select the correct discount based on the day

 

Class Diagram ShoppingMall
Class Diagram ShoppingMall

LowDiscountStrategy.java

HighDiscountStrategy.java

 Context Class

This class bridges a client with the strategy keeping a reference to the DiscountStrategy object. Hence, the ShoppingMallContext class will be initialized with a DiscountStrategy object in order to forward the client’s request to the strategy.

ShoppingMallContext
ShoppingMallContext

ShoppingMallContext.java

 

Client Class

This class is the main user of the functionality or the particular algorithm. It requests the exact strategy based on certain requirements and conditions. Client class should first invoke the Context class in order to grab the decided strategy at runtime.

 

Client Class
Client Class

ShoppingMallClient.java

 

Advantages of Strategy Pattern

1. Enhanced Flexibility

The Strategy Pattern enables you to switch between different algorithms at runtime, providing greater flexibility in your application. By encapsulating the varying behavior in separate strategy classes, you can easily add or modify algorithms without changing the client code or the context class that uses these strategies.

2. Improved Maintainability

By separating the algorithms from the classes that use them, the Strategy Pattern promotes a clean separation of concerns. This separation makes the code more modular, easier to understand, and simpler to maintain. When you need to update or add a new algorithm, you can do so without affecting the existing codebase.

3. Better Code Reusability

Since the Strategy Pattern encapsulates algorithms in separate classes, you can reuse these strategies across different parts of your application. This promotes code reusability and reduces code duplication, leading to a more efficient and manageable codebase.

4. Simplified Unit Testing

The Strategy Pattern makes it easier to test your code, as each strategy can be tested independently. By isolating the varying behavior in separate classes, you can create unit tests for each algorithm without the need to create complex test scenarios or set up the entire system. This leads to more effective and reliable tests.

5. Reduced Conditional Complexity

By encapsulating algorithms in separate strategy classes, the Strategy Pattern helps to eliminate complex conditional statements that may arise when selecting an algorithm at runtime. This leads to cleaner, more readable, and less error-prone code, improving overall code quality.

Challenges of Strategy Pattern

Despite its advantages, there are certain challenges associated with using the Strategy Pattern. In this article, we will discuss these challenges in detail.

1. Increased Number of Classes

One of the main challenges when using the Strategy Pattern is the increased number of classes in your codebase. Each strategy requires a separate class, which may lead to a proliferation of classes if there are multiple algorithms to choose from. This can make your codebase more challenging to manage, understand, and navigate.

2. Difficulty in Choosing the Right Strategy

When implementing the Strategy Pattern, selecting the most suitable strategy at runtime can be a challenge. You need to ensure that your application has the necessary logic to determine the best strategy for a particular situation. This may involve evaluating various factors, such as performance, resource constraints, and user preferences. Developing such logic can be complex and may require a deep understanding of the problem domain.

3. Initialization and Configuration Overhead

Each strategy class may have its initialization and configuration requirements. This can introduce additional overhead when setting up the strategy instances at runtime. In some cases, this might lead to performance issues or increased memory consumption, especially if there are many strategy classes or if the initialization process is resource-intensive.

4. Communication Between Strategies

In certain situations, strategies may need to communicate with each other or share common resources. Managing such communication can be challenging, as the Strategy Pattern inherently promotes separation of concerns between strategy classes. This may require careful design and planning to ensure that strategies can efficiently collaborate without violating the pattern’s principles.

5. Integration with Existing Code

Integrating the Strategy Pattern into an existing codebase can be challenging, particularly if the code does not adhere to the principles of modularity and separation of concerns. Refactoring existing code to accommodate the Strategy Pattern may require significant effort and time, which could impact project timelines and budgets.

6. Increased Complexity

While the Strategy Pattern can help reduce conditional complexity, it can also introduce additional complexity in the form of class hierarchies and interfaces. Developers need to be familiar with the pattern and understand how to work with the various strategy classes and interfaces. This may result in a steeper learning curve for those who are new to the pattern or the codebase.

7. Potential for Overengineering

It is essential to strike a balance between flexibility and simplicity when using the Strategy Pattern. Overusing the pattern or applying it in situations where it is not necessary can lead to overengineering and increased complexity. This can make the code harder to maintain, understand, and modify.

8. Trade-offs Between Performance and Flexibility

Using the Strategy Pattern can sometimes result in trade-offs between performance and flexibility. While the pattern enables you to switch between algorithms at runtime, this flexibility may come at the cost of additional overhead, such as object instantiation and method invocations. It is essential to carefully evaluate the performance implications of using the Strategy Pattern in performance-critical scenarios.

9. Scalability Concerns

As the number of strategies in your application grows, managing and maintaining them can become more difficult. It is important to ensure that the Strategy Pattern is scalable, particularly in large or rapidly evolving projects. This may involve organizing strategies into packages or modules and carefully managing dependencies between them.

10. Testing Challenges

Testing can be more challenging when using the Strategy Pattern, as each strategy class may need to be tested independently. Additionally, you may need to test the context class and the integration between strategies and the context. This can increase the overall testing effort and complexity, requiring well-designed test cases and thorough test coverage.

11. Documentation and Knowledge Transfer

When using the Strategy Pattern, it is crucial to maintain up-to-date documentation that clearly explains the purpose and usage of each strategy class. This can facilitate knowledge transfer and help new team members understand the codebase more quickly. It also helps prevent misunderstandings and ensures that the strategies are used correctly and consistently across the application.

12. Ensuring Consistency Across Strategies

It is essential to ensure consistency across different strategy implementations, particularly in terms of input and output requirements, error handling, and performance expectations. Inconsistencies between strategies can lead to unexpected behavior and make it more difficult to switch between them at runtime. This may require the use of interfaces, abstract classes, or other mechanisms to enforce consistency and reduce the potential for errors.

13. Maintainability and Refactoring

Over time, the requirements of your application may change, necessitating updates or modifications to existing strategies. Ensuring that your strategy classes are maintainable and easy to refactor is crucial for long-term project success. This may involve adhering to best practices such as SOLID principles, using clear and descriptive naming conventions, and keeping each strategy focused on a single responsibility.

14. Balancing Abstraction and Concreteness

Finding the right balance between abstraction and concreteness is essential when using the Strategy Pattern. While abstraction can provide flexibility and extensibility, it can also introduce complexity and make the code harder to understand. Striking the right balance requires careful consideration of the problem domain, the needs of the application, and the potential for future changes and enhancements.

15. Security Considerations

When using the Strategy Pattern, it is important to consider the security implications of allowing runtime selection of algorithms. This may involve ensuring that only authorized users can change the active strategy, validating input and output data, and properly handling sensitive information. Taking a proactive approach to security can help prevent potential vulnerabilities and protect your application and its data.

Conclusion

That being said, it’s important to weigh the costs and benefits of using the strategy pattern in any particular case. It’s a powerful pattern that is widely used when it’s applied in the correct context.

It’s also important to be mindful of the design patterns used and their limitations, to ensure the codebase is maintainable, readable and efficient.

Source Code:

You can find the source code used in this tutorial on github : https://github.com/t-tak/javagists/tree/master/java-design-patterns/strategy-design-pattern

10 thoughts on “Strategy Design Pattern”

  1. ShoppingMallContext constructor should receive the strategy, I understand it’s a simple mistake, please rectify.
    A very good and well explained article though.
    Thank you.

    Reply
    • Thanks for pointing out the mistake.Really glad that you liked the article. It is readers who complete the article, by pointing out the mistakes, typos. Thanks

      Reply
    • Strategy is a behavioral pattern and Builder is a creational pattern. You can use them together. Like Strategy can be used to pick up an Algorithm and then builder can be create objects for those algorithms

      Reply
  2. Nice read. Really helps to brush up the concepts that I know but somehow don’t remember the vocabularies. Good examples.

    Reply
  3. I think the creation of too many objects isn’t probably a concern for modern languages. I would really like to know if java can’t handle creation of objects?

    Reply

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.