Fundamentals 14 min read

Strategy Pattern: Replacing Conditional Branches with Pluggable Algorithms

The article explains how the Strategy design pattern solves the maintenance nightmare of tangled if‑else or switch statements by extracting each algorithm into its own strategy class, detailing the roles, selection mechanisms, Java examples, and when the pattern is appropriate.

Dabaoshi
Dabaoshi
Dabaoshi
Strategy Pattern: Replacing Conditional Branches with Pluggable Algorithms

Behavioral design patterns focus on how objects collaborate at runtime; among them, the Strategy pattern is the most practical and widely used. It addresses the common problem of having multiple algorithms for the same task that need to be selected dynamically, which often manifests as bulky if‑else or switch blocks.

1. The Shipping Cost If‑Else Nightmare

A sample ShippingService.calculate method shows four pricing rules (free, weight‑based, distance‑based, remote) implemented with nested if‑else statements. This design violates the Open/Closed Principle, piles responsibilities in one class, and makes testing individual rules difficult.

public class ShippingService {
    public double calculate(String type, double weight, double distance) {
        if (type.equals("free")) { return 0; }
        else if (type.equals("weight")) { return weight * 5; }
        else if (type.equals("distance")) { return distance * 0.5; }
        else if (type.equals("remote")) { return distance * 0.8 + 20; }
        throw new IllegalArgumentException("未知运费类型");
    }
}

The core issue is that parallel algorithms are hard‑coded in a single if‑else chain, preventing independent extension.

2. Refactoring with Strategy

The solution is to define a strategy interface and implement each pricing rule as a separate class. The context holds a reference to the interface and delegates the calculation.

// Strategy interface
public interface ShippingStrategy {
    double calculate(double weight, double distance);
}
public class FreeShipping implements ShippingStrategy {
    public double calculate(double w, double d) { return 0; }
}
public class WeightShipping implements ShippingStrategy {
    public double calculate(double w, double d) { return w * 5; }
}
public class DistanceShipping implements ShippingStrategy {
    public double calculate(double w, double d) { return d * 0.5; }
}
public class RemoteShipping implements ShippingStrategy {
    public double calculate(double w, double d) { return d * 0.8 + 20; }
}
public class ShippingService {
    private ShippingStrategy strategy;
    public void setStrategy(ShippingStrategy strategy) { this.strategy = strategy; }
    public double calculate(double weight, double distance) {
        return strategy.calculate(weight, distance);
    }
}

Client code now selects a strategy without any if‑else:

ShippingService service = new ShippingService();
service.setStrategy(new WeightShipping());
System.out.println(service.calculate(3, 100)); // 15
service.setStrategy(new DistanceShipping());
System.out.println(service.calculate(3, 100)); // 50

This refactor cleanly separates algorithm logic from selection logic, fully complying with the Open/Closed Principle.

3. Roles and the "Who Chooses Strategy" Question

Strategy Interface (Strategy) : ShippingStrategy – defines the common contract for all algorithms.

Concrete Strategy : classes such as WeightShipping, DistanceShipping – each implements one algorithm.

Context : ShippingService – holds a reference to ShippingStrategy and delegates the call.

The pattern itself does not decide which strategy to use. Selection can be handled in three ways:

Client directly creates and injects the desired strategy.

A factory (e.g., ShippingStrategyFactory) encapsulates the creation logic, often using its own if‑else or a Map.

A registration map ( Map<String, ShippingStrategy>) eliminates conditional logic entirely by looking up the strategy by key.

private static final Map<String, ShippingStrategy> STRATEGIES = Map.of(
    "free", new FreeShipping(),
    "weight", new WeightShipping(),
    "distance", new DistanceShipping(),
    "remote", new RemoteShipping()
);
public double calculate(String type, double weight, double distance) {
    ShippingStrategy strategy = STRATEGIES.get(type);
    if (strategy == null) throw new IllegalArgumentException("未知运费类型");
    return strategy.calculate(weight, distance);
}

In Spring, the map can be auto‑wired: all strategy beans are collected into a Map<String, ShippingStrategy>, so adding a new algorithm requires only a new bean.

4. Strategy in Everyday Java: Comparator and Lambda

Many Java APIs already use Strategy. Comparator is a textbook example: Collections.sort(list, comparator) delegates ordering to the supplied comparator. Since Java 8, lambdas let you provide a lightweight strategy without creating a separate class:

orders.sort((a, b) -> Double.compare(a.getAmount(), b.getAmount())); // by amount
orders.sort(Comparator.comparing(Order::getCreateTime)); // by time

Thus, functional interfaces plus lambdas are syntactic sugar for the Strategy pattern.

5. Strategy vs. Factory vs. State

Strategy vs. Factory : Factories create objects; strategies define behavior. They are often combined so the factory decides which strategy instance to supply.

Strategy vs. State : Both have similar structure, but strategies represent independent, parallel algorithms chosen externally, whereas state objects represent linked stages that transition internally.

6. When to Use Strategy

Multiple interchangeable algorithms exist and may change over time.

Code contains if‑else or switch that selects behavior based on type.

Each algorithm should be independently extensible, reusable, and testable.

Do not apply Strategy when the number of branches is fixed and unlikely to grow, or when the algorithm is trivial (a single line) and used only once; in those cases a simple conditional or a lambda is preferable.

7. Summary

Strategy pattern solves the "hard‑coded if‑else" dilemma by extracting each algorithm into its own strategy class, letting the context delegate work via polymorphism. It decouples algorithm logic from selection logic, enabling independent extension, reuse, and testing, and aligns with the Open/Closed Principle. In modern Java, many built‑in APIs (Comparator, ThreadPoolExecutor's rejection policies, Spring resource loading) already embody Strategy, and lambdas provide a concise way to express simple strategies.

Strategy pattern diagram
Strategy pattern diagram
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 PatternlambdaDependency InjectionOpen/Closed PrincipleComparator
Dabaoshi
Written by

Dabaoshi

Practical utilities

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.