Menu

The Strategy Pattern in modern Java

Published: 
April 28, 2024
The strategy pattern is a design pattern that lets you select or change the behavior of your program at runtime. Without dealing with lots of glue code like a special factory class, I'll show you an example that is simpler and utilizes the capabilities of a DI framework like CDI.

The complete example code can be found on my GitHub.

Implement multiple strategies

At the core of the strategy pattern is the interface defining your different strategies. Let's create a simple example, where we select a strategy for our greeting based on the language:
public interface GreetingStrategy {
    boolean appliesTo(String language);
    String greet();
}
As I mentioned before, I'll use CDI for the implementation. This will create the strategies for us. Instead of using a factory that has to be extended each time a new strategy is introduced, I'll let the implementation be responsible to be selected.
@ApplicationScoped
public class EnglishGreeting implements GreetingStrategy {
    @Override
    public boolean appliesTo(String language) { return language.equals("en"); }
    @Override
    public String greet() { return "Hello!"; }
}
And a second implementation.
@ApplicationScoped
public class FrenchGreeting implements GreetingStrategy {
    @Override
    public boolean appliesTo(String language) { return language.equals("fr"); }
    @Override
    public String greet() { return "Bonjour!"; }
}

Selecting a strategy manually

When you want to select the strategy manually, you can inject all available implementations via
@Inject
Instance<GreetingStrategy> strategies;
and then select the one that applies to your needs
public String getGreeting(String language) {
    return strategies.stream()
        .filter(strategy -> strategy.appliesTo(language))
        .findAny()
        .map(GreetingStrategy::greet)
        .orElseThrow(() -> new IllegalArgumentException(language + " not supported!"));
}

Selecting a strategy from a request context

Another option is to provide contextual information, so that you'll get the right strategy injected and don't have to handle it manually. In a CDI environment, we can for example create a Producer which selects the strategy based on query parameters for us:
public class GreetingProducer {
    @Context UriInfo uriInfo;
    @Inject Instance strategies;

    @Produces
    @RequestScoped
    @FromQueryParams
    public GreetingStrategy getInstanceFromQueryContext() {
    List languageParameters = uriInfo.getQueryParameters().get("lang");
    String language = languageParameters == null ? "en" : languageParameters.getFirst();
    return strategies.stream()
            .filter(strategy -> strategy.appliesTo(language))
            .findAny()
            .orElseThrow();
    }

}
Note that I also added a custom Qualifier @FromQueryParams. This qualifier needs to be used at the producer method and at the injection point to get a correct strategy resolution via this producer method. The injection now looks like the following and the injected instance can directly be used:
@Inject
@FromQueryParams
GreetingStrategy greetingStrategy;

Summary

We saw an implementation of the strategy pattern and two approaches how to select the strategy. A manual selection of the strategy is possible of course, but in my opinion not very often seen in real world projects. In most cases, there is some kind of context available, which can be used to select the right strategy for us. The implicit strategy selection removes the complexity from the code that is read frequently, and replaces it with a "magical" annotation @FromQueryParams. This is a kind of trade-off, which is often seen in frameworks. It allows the reader to stay on a higher level of abstraction, leading to cleaner and more maintainable code.

These are only two possible ways how to implement the strategy pattern in Java and CDI/Quarkus. Of course there are more ways and you should always adjust the pattern to your requirements and environment.

What's next? Experiment on your own and adapt the pattern to your language, framework and whatever you are working with!

© Benjamin Bulk
Legal