Fundamentals 16 min read

Mastering the Strategy Pattern: When and How to Use It in Java

This article explains the Strategy design pattern, its definition, roles, UML diagram, Java code examples, real‑world applications such as payment integration and compression, and provides guidance on when to use it, its advantages, drawbacks, and how to simplify it with Java 8 lambdas.

Programmer DD
Programmer DD
Programmer DD
Mastering the Strategy Pattern: When and How to Use It in Java

Preface

A while ago I received a requirement to develop an aggregated payment service that provides a unified interface for other internal projects to invoke different payment platforms such as Alipay, WeChat Pay, and UnionPay. To keep each platform's implementation independent and easily extensible for future platforms, I introduced the Strategy design pattern and wrote this article to help learners understand it.

Why a Strategy Pattern Is Needed

In daily development we often encounter code like the following:

if(condition1){
    // do something1
} else if(condition2){
    // do something2
} else if(condition3){
    // do something3
}

Each if branch may contain dozens or hundreds of lines of business logic that are independent but share the same purpose, leading to a bloated class that is hard to maintain and extend. The Strategy pattern solves this by encapsulating each processing logic into separate classes, allowing the client to select the appropriate strategy at runtime.

What Is the Strategy Pattern

According to Wikipedia:

In computer programming, the strategy pattern (also known as the policy pattern ) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run‑time instructions as to which in a family of algorithms to use.

The pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. The client decides which algorithm to use.

UML Class Diagram

The diagram involves three roles: Strategy (abstract strategy interface), Context (holds a reference to a Strategy), and ConcreteStrategy (specific algorithm implementation).

Strategy: abstract role representing an algorithm interface or abstract class.

Context: references a Strategy object and shields external modules from direct access to the algorithm.

ConcreteStrategy: concrete implementation of the abstract strategy, often multiple classes.

Generic Source Code

// Abstract strategy role
public interface Strategy {
    void doSomething();
}

// Concrete strategy role
public class ConcreteStrategy implements Strategy {
    @Override
    public void doSomething() {
        System.out.println("ConcreteStrategy doSomething !");
    }
}

// Context role
public class Context {
    private final Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public void doAnything() {
        this.strategy.doSomething();
    }
}

Using the pattern in a client is straightforward:

public class Client {
    public static void main(String[] args) {
        Strategy strategy = new ConcreteStrategy();
        Context context = new Context(strategy);
        context.doAnything(); // prints "ConcreteStrategy doSomething !"
    }
}

Identifying the Strategy Pattern in Frameworks

JDK

The java.util.Comparator interface is a classic example of the Strategy pattern. Implementations such as NaturalOrderComparator and NullComparator are concrete strategies, while java.util.Collections acts as the Context.

Spring Framework

Spring’s bean instantiation uses

org.springframework.beans.factory.support.InstantiationStrategy

as the strategy interface, with SimpleInstantiationStrategy and CglibSubclassingInstantiationStrategy as concrete strategies. The AbstractAutowireCapableBeanFactory class serves as the Context.

protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
    // ...
    beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
    // ...
}

How to Apply the Strategy Pattern

Example: Compression/Decompression

Suppose we need to support both ZIP and GZIP compression and want to add more algorithms later. First define an abstract strategy:

public interface CompressStrategy {
    boolean compress(String source, String to);
    boolean uncompress(String source, String to);
}

Then provide concrete implementations:

public class ZipStrategy implements CompressStrategy {
    @Override
    public boolean compress(String source, String to) {
        System.out.println(source + " --> " + to + " ZIP compression succeeded!");
        return true;
    }
    @Override
    public boolean uncompress(String source, String to) {
        System.out.println(source + " --> " + to + " ZIP decompression succeeded!");
        return true;
    }
}

public class GzipStrategy implements CompressStrategy {
    @Override
    public boolean compress(String source, String to) {
        System.out.println(source + " --> " + to + " GZIP compression succeeded!");
        return true;
    }
    @Override
    public boolean uncompress(String source, String to) {
        System.out.println(source + " --> " + to + " GZIP decompression succeeded!");
        return true;
    }
}

Context class that delegates to the chosen strategy:

public class CompressContext {
    private CompressStrategy compressStrategy;

    public CompressContext(CompressStrategy compressStrategy) {
        this.compressStrategy = compressStrategy;
    }

    public boolean compress(String source, String to) {
        return compressStrategy.compress(source, to);
    }

    public boolean uncompress(String source, String to) {
        return compressStrategy.uncompress(source, to);
    }
}

Client code demonstrating strategy switching:

public class Client {
    public static void main(String[] args) {
        CompressContext context;
        System.out.println("======== Execute Algorithm ========");
        context = new CompressContext(new ZipStrategy());
        context.compress("c:\\file", "d:\\file.zip");
        context.uncompress("c:\\file.zip", "d:\\file");
        System.out.println("======== Switch Algorithm ========");
        context = new CompressContext(new GzipStrategy());
        context.compress("c:\\file", "d:\\file.gzip");
        context.uncompress("c:\\file.gzip", "d:\\file");
    }
}

The same idea applies to third‑party payment integration: each payment platform can be encapsulated as a concrete strategy.

Applicable Scenarios

When an object has many behaviors that share the same purpose but are implemented with multiple conditional statements.

When a system needs to switch algorithms dynamically at runtime.

The client should not need to know the details of each algorithm, only how to invoke it.

Strategy Pattern with Lambdas

Since Java 8, if the strategy interface is a functional interface, you can replace concrete classes with lambda expressions, making the code more concise.

public class Validator {
    private final ValidationStrategy strategy;
    public Validator(ValidationStrategy strategy) { this.strategy = strategy; }
    public boolean validate(String s) { return strategy.execute(s); }
}

@FunctionalInterface
public interface ValidationStrategy { boolean execute(String s); }

Validator numericValidator = new Validator(s -> s.matches("[a-z]+"));
bool b1 = numericValidator.validate("aaaa"); // true
Validator lowerCaseValidator = new Validator(s -> s.matches("\\d+"));
bool b2 = lowerCaseValidator.validate("bbbb"); // false

Lambdas are suitable for simple algorithms; complex logic should still be extracted into separate classes.

Things to Watch Out For

Each strategy must represent a complete, indivisible business operation and be interchangeable.

If the number of concrete strategies exceeds four, consider combining with Factory Method, Proxy, or Flyweight patterns to reduce class explosion.

Pros and Cons of the Strategy Pattern

Advantages

Enables swapping algorithms without modifying existing code, improving extensibility.

Provides a clear way to manage a family of algorithms.

Eliminates long conditional statements by delegating decision to the client.

Disadvantages

The client must be aware of all concrete strategy classes to choose one.

Can lead to a proliferation of strategy classes, increasing project size.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaStrategy PatternLambda
Programmer DD
Written by

Programmer DD

A tinkering programmer and author of "Spring Cloud Microservices in Action"

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.