Design Pattern Course
Strategy is a Behavioral Design Pattern.
Strategy defines a family of algorithms, encapsulates each one behind a common interface, and makes them interchangeable at runtime. The client (the Context) holds a reference to a strategy interface and delegates the varying behaviour to whichever concrete implementation has been plugged in — without the context needing any if/else or switch logic to decide which algorithm to run.
This is one of the most frequently used patterns in real enterprise systems. A strong motivating sign that you need Strategy: a class has grown a large if/else or switch block where each branch does "the same kind of thing, just differently" — sorting, pricing, routing, notification delivery, authentication. Moving each branch into its own strategy class makes each algorithm independently testable, independently extensible (new strategies don't require editing existing code), and swappable at runtime.
package com.praveen.designpatterns.behavioral.strategy; public interface PaymentStrategy { void pay(String buyer, int amount); }
package com.praveen.designpatterns.behavioral.strategy; public class CreditCard implements PaymentStrategy { @Override public void pay(String buyer, int amount) { System.out.println("Paying with CreditCard: Buyer = " + buyer + ", Amount = " + amount); } }
package com.praveen.designpatterns.behavioral.strategy; public class DebitCard implements PaymentStrategy { @Override public void pay(String buyer, int amount) { System.out.println("Paying with DebitCard: Buyer = " + buyer + ", Amount = " + amount); } }
package com.praveen.designpatterns.behavioral.strategy; public class UPI implements PaymentStrategy { @Override public void pay(String buyer, int amount) { System.out.println("Paying with UPI: Buyer = " + buyer + ", Amount = " + amount); } }
package com.praveen.designpatterns.behavioral.strategy; public class PaymentContext { private PaymentStrategy strategy; public PaymentContext(PaymentStrategy strategy) { this.strategy = strategy; } // Strategy can also be swapped at runtime public void setStrategy(PaymentStrategy strategy) { this.strategy = strategy; } public void doPayment(String buyer, int amount) { this.strategy.pay(buyer, amount); } }
package com.praveen.designpatterns.behavioral.strategy; public class StrategyPatternTest { public static void main(String[] args) { PaymentContext context = new PaymentContext(new CreditCard()); context.doPayment("Praveen", 100); context.setStrategy(new DebitCard()); context.doPayment("Praveen", 100); context.setStrategy(new UPI()); context.doPayment("Praveen", 100); } }
Output
Paying with CreditCard: Buyer = Praveen, Amount = 100
Paying with DebitCard: Buyer = Praveen, Amount = 100
Paying with UPI: Buyer = Praveen, Amount = 100
A third payment option (UPI) was added here to illustrate the core benefit: adding a new strategy doesn't require touching PaymentContext or any other strategy. The setStrategy() method is also added to show that strategies can be swapped on the same context at runtime, not just at construction time.
In modern Java (8+), strategies with a single method are naturally expressed as functional interfaces, letting callers pass a lambda instead of a named class. PaymentStrategy above is a functional interface (one abstract method), so this is valid:
PaymentContext context = new PaymentContext( (buyer, amount) -> System.out.println("Paying with UPI: " + buyer + " " + amount) );
java.util.Comparator is the canonical JDK example: Collections.sort(list, comparator) and list.sort(comparator) accept any Comparator implementation — you supply the sorting strategy, the framework handles the rest. Since Java 8, this is almost always supplied as a lambda rather than a named class: list.sort((a, b) -> a.getName().compareTo(b.getName())).
When to Use the Strategy Pattern
- When many related classes differ only in their behaviour — Strategy provides a clean way to configure a class with one of many behaviours without subclassing.
- When you need different variants of an algorithm and want to be able to swap them at runtime or configure them via dependency injection.
- When a class contains large conditional logic that selects different variants of the same kind of operation — replace the conditions with Strategy classes.
- When you want to hide algorithm-specific data structures from clients — the client only needs to know about the strategy interface.
Strategy vs State
A commonly confused pair. Both store an interface reference on a context object and delegate behaviour to it. The distinction is who drives the change and why:
- Strategy: The algorithm is set by the client and typically stays fixed for the duration of an operation. The strategies are independent of one another and don't know about each other. The context doesn't drive switching.
- State: The context's own internal state drives switching. State objects may know about each other (one state transitions to another), and the switching is automatic, driven by the object's lifecycle or events — the client doesn't decide which state the object is in.
Strategy Pattern Examples in the JDK
java.util.Comparator#compare()— the textbook JDK Strategy example. The sorting algorithm is fixed; the comparison strategy is pluggable.java.util.function.*— all functional interfaces (Predicate,Function,Consumer,Supplier) are Strategy interfaces.javax.servlet.http.HttpServlet—doGet(),doPost(), etc. can be considered per-HTTP-method strategies plugged into the fixedservice()template.javax.servlet.Filter#doFilter()— filters in a filter chain each represent a pluggable processing strategy.