In this post we are going to see the Strategy Pattern in Java. We can use Strategy Pattern to change behaviors at runtime.
What is the Strategy Pattern?
The Strategy Pattern is a design pattern that defines the interchange of messages between different objects to achieve a specific behavior.
Let’s imagine for a moment that we are a general from ancient Rome, planning an attack on Numancia. To do so, we need to have a clear strategy for the different situations we will encounter.
As a good general, we will define a series of behaviors for each situation, such as when arrows are shot at us or when the enemy uses cavalry or infantry.
If we translate the battlefield strategy to software development, we can see that the objective of this pattern is to define a series of behaviors to choose a specific algorithm based on a context.
When use Strategy Pattern?
The Strategy Pattern in Java is commonly used in situations where you need to dynamically change algorithms or behaviors at runtime. Here are a few scenarios where the Strategy Pattern can be beneficial:
- Multiple algorithm variations: When you have a set of algorithms that can be used interchangeably based on specific conditions or requirements, the Strategy Pattern allows you to encapsulate each algorithm into separate classes. This promotes better code organization and maintainability.
- Runtime algorithm selection: If you need to choose an algorithm dynamically at runtime, the Strategy Pattern is an excellent choice. It allows you to switch between different strategies based on user input, configuration settings, or other runtime factors.
- Avoiding conditional statements: The Strategy Pattern helps you avoid large conditional statements or switch-case blocks that would otherwise be necessary to determine which algorithm to execute. Instead, you delegate the responsibility to the strategy objects themselves.
- Open for extension, closed for modification: The Strategy Pattern follows the Open/Closed Principle, allowing you to introduce new strategies without modifying existing code. The Strategy Pattern allows easy addition of new strategies without modifying existing code.
- Testing and maintenance: By encapsulating algorithms into separate classes, each strategy can be tested independently. This promotes easier unit testing and simplifies maintenance since modifications to one strategy do not affect others.
Overall, the Strategy Pattern is valuable in scenarios where you have multiple interchangeable algorithms, require runtime flexibility in choosing algorithms, and want to achieve code modularity and extensibility.
Example with Spring Boot
In this example, we will implement this pattern using Dependency Injection with Spring Boot. If you have any doubts about how Dependency Injection works in Spring, click here.
Strategy Pattern
Create the Strategy Pattern interface
The first thing we’ll do is create an interface that the rest of the classes will implement.
public interface AnimalStrategy { void doStuff(); StrategyType getType(); }
Next, we define an enum class that will be used to decide which type (“getType”) we should inject.
public enum StrategyType { DOG, CAT }
Implementation of the algorithms
Next, we’ll implement two algorithms based on whether the type is a dog or a cat:
public class IamADog implements AnimalStrategy{ @Override public void doStuff() { //implement algorithm Dog here } @Override public StrategyType getStrategyType() { return StrategyType.DOG; } }
public class IamACat implements AnimalStrategy{ @Override public void doStuff() { //implement algorithm Dog here } @Override public StrategyType getStrategyType() { return StrategyType.CAT; } }
We have now defined two different classes with two distinct algorithms that will be executed based on a type.
These two algorithms implement an interface with a set of common methods because the idea of this pattern is to use one or the other depending on a behavior.
Creating the factory
At this point, we’ll inject one algorithm or the other by storing the strategy type in a map, although we could use some other mechanism like a switch statement.
@Component public class StrategyFactory { private Map<StrategyType, AnimalStrategy> strategies = new HashMap<StrategyType, AnimalStrategy>(); public StrategyFactory(Set<AnimalStrategy> animalStrategy) { createStrategy(animalStrategy); } public AnimalStrategy findAnimalStrategy(StrategyType strategyType) { return strategies.get(strategyType); } private void createStrategy(Set<AnimalStrategy> animalStrategy) { animalStrategy.forEach( strategy ->strategies.put(strategy.getStrategyType(), strategy)); } }
Using the Strategy Pattern
Using Spring and its annotations, we’ll inject the factory we created. In this case, we’ll inject the factory via constructor using the Lombok annotation @RequiredArgsConstructor. Here, we’ll return a Dog animal type.
@Service @RequiredArgsConstructor public class AnimalService { private final StrategyFactory strategyFactory; public void findAnimal(String type){ if (type.equalsIgnoreCase("dog") { strategyFactory.findAnimalStrategy(StrategyType.DOG); } } }
Conclusion
In this post, we have seen the implementation of the Strategy Pattern with Spring. We can appreciate that it is one of the most useful and commonly used algorithms.
If you need more information, you can leave us a comment or send an email to refactorizando.web@gmail.com You can also contact us through our social media channels on Facebook or twitter and we will be happy to assist you!!