Strategy Pattern

June 17, 2026 in patterns 4 minutes

A intermediate-level guide to Strategy Pattern: before-and-after java code and diagrams for a CS student.

The Growing Burden of Conditional Logic

Imagine you are building a shipping calculator for an e-commerce platform. Initially, you only offer standard shipping, so the math is simple. But business grows. Now you need express and overnight options. Every time a new shipping method is added, you have to open up your existing ShippingCalculator class and add another else if block inside the calculateCost method.

This pattern creates “Conditional Complexity,” a code smell where a single method becomes a dumping ground for every possible variation of a business rule. This design violates the Open/Closed Principle, which states that software entities should be open for extension but closed for modification. In other words, you shouldn’t have to rewrite and re-test your core calculator class just because the marketing department added a “Super Express” tier.

public class ShippingCalculator {
    private final String shippingMethod;

    public ShippingCalculator(String shippingMethod) {
        this.shippingMethod = shippingMethod;
    }

    public double calculateCost(double weight) {
        // Conditional complexity: adding a new method requires modifying this class
        if (shippingMethod.equals("STANDARD")) {
            return weight * 1.5;
        } else if (shippingMethod.equals("EXPRESS")) {
            return weight * 3.0 + 5.0;
        } else if (shippingMethod.equals("OVERNIGHT")) {
            return weight * 5.0 + 15.0;
        } else {
            throw new IllegalArgumentException("Unknown shipping method");
        }
    }
}

The logic is tightly coupled to specific string names, making the code brittle and difficult to maintain as the list of methods grows:

  flowchart TD
    Start([calculateCost]) --> C1{method == STANDARD?}
    C1 -- yes --> R1[return weight * 1.5]
    C1 -- no --> C2{method == EXPRESS?}
    C2 -- yes --> R2[return weight * 3 + 5]
    C2 -- no --> C3{method == OVERNIGHT?}
    C3 -- yes --> R3[return weight * 5 + 15]
    C3 -- no --> E[throw exception]

Swapping Behaviors with the Strategy Pattern

To fix this, we use the Strategy Pattern. Think of a video game character who can switch between a sword and a bow. The character doesn’t have “sword logic” and “bow logic” hardcoded into their own attack() method via a massive if statement. Instead, the character has a single weapon slot. When they equip a sword, you put a Sword object in that slot; when they switch to a bow, you swap it for a Bow object. The character simply calls weapon.attack(), and Polymorphism—the ability of an object to take on many forms—ensures the correct math happens at runtime.

In our Java refactor, we extract each shipping calculation into its own class that implements a common interface. The ShippingCalculator no longer contains any math; it simply holds a reference to a ShippingStrategy and asks it to do the work. This is known as Delegation.

public interface ShippingStrategy {
    double calculate(double weight);
}

class StandardShipping implements ShippingStrategy {
    @Override
    public double calculate(double weight) { return weight * 1.5; }
}

class ExpressShipping implements ShippingStrategy {
    @Override
    public double calculate(double weight) { return weight * 3.0 + 5.0; }
}

class OvernightShipping implements ShippingStrategy {
    @Override
    public double calculate(double weight) { return weight * 5.0 + 15.0; }
}

public class ShippingCalculator {
    private final ShippingStrategy strategy;

    public ShippingCalculator(ShippingStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculateCost(double weight) {
        // Delegation: the calculator no longer cares about specific math logic
        return strategy.calculate(weight);
    }
}

By using an interface, we have decoupled the “how” (the specific math) from the “when” (the request for a cost). The ShippingCalculator now relies on Composition—building complex objects by combining simpler ones—rather than hardcoded logic.

  classDiagram
    direction BT
    StandardShipping ..|> ShippingStrategy : implements
    ExpressShipping ..|> ShippingStrategy : implements
    OvernightShipping ..|> ShippingStrategy : implements
    ShippingCalculator o-- ShippingStrategy : uses

When the application runs, the ShippingCalculator delegates the calculation to whichever concrete implementation was provided during its construction. This interaction looks like this:

  sequenceDiagram
    participant C as ShippingCalculator
    participant S as StandardShipping
    C->>S: calculate(weight)
    S-->>C: return cost

When to Use It, and When Not To

The Strategy Pattern is a powerful tool for managing behavioral variation, but it is not a silver bullet.

Use it when:

  • You have many related classes that differ only in their behavior.
  • You want to hide complex, algorithm-specific data from the client using the object that uses it.
  • You find yourself frequently adding new cases to large if-else or switch blocks.

Avoid it when:

  • The variations are trivial and unlikely to ever change (e.g., a simple true/false toggle). Adding an interface, multiple classes, and dependency injection for a single line of logic is “over-engineering” and adds unnecessary boilerplate code.
  • You can achieve the same result with a simple function or a constant without adding structural complexity to your class hierarchy.

Takeaways

  • Use the Strategy Pattern to replace complex conditional branching with interchangeable objects.
  • Favor composition (having an object) over hardcoded logic to satisfy the Open/Closed Principle.
  • Leverage Polymorphism to allow the calling class to execute different behaviors through a single interface.

Usage

Demonstrating how the client swaps behaviors via constructor injection

ShippingCalculator standard = new ShippingCalculator(new StandardShipping());
double cost = standard.calculateCost(10.0); // 15.0

// We can swap behavior by injecting a different strategy
ShippingCalculator express = new ShippingCalculator(new ExpressShipping());