The Goal
Change the algorithm an object uses to do its job.
Example
Imagine you are building a checkout system. The system knows how to calculate the total, but the behavior of how the money actually moves depends on what the user selects (Credit Card, PayPal, or Crypto).
- The Strategy interface (The interchangeable behavior)
public interface PaymentStrategy {
void pay(double amount);
}
- Different Behaviors
public class CreditCardStrategy implements PaymentStrategy {
public void pay(double amount) { System.out.println("Paid " + amount + " via Credit Card."); }
}
public class PayPalStrategy implements PaymentStrategy {
public void pay(double amount) { System.out.println("Paid " + amount + " via PayPal."); }
}
- The Context (The object doing the work)
public class CheckoutCart {
private PaymentStrategy paymentStrategy; // We hold a behavior
// We SWAP the behavior at runtime
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public void checkout(double totalAmount) {
// The BEHAVIORAL handoff
paymentStrategy.pay(totalAmount);
}
}
Setter vs Constructor
In the CheckoutCart example, a setter method (setPaymentStrategy) is used instead of a constructor. It’s written this way to emphasize that the Strategy pattern allows you to swap out behaviors on the fly at runtime.
However, it is incredibly common to pass the Strategy directly through the constructor.
NOTE
In fact, if you are building enterprise applications and using dependency injection (like in Spring Boot), passing the strategy via the constructor is the standard approach.
Here is what the CheckoutCart looks like when we use a constructor, which makes the initial setup look completely identical to the Decorator:
public class CheckoutCart {
// Marked as final because we set it once in the constructor
private final PaymentStrategy paymentStrategy;
// Here is the constructor injection!
public CheckoutCart(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(double totalAmount) {
// We still delegate the behavior to the injected strategy
paymentStrategy.pay(totalAmount);
}
}
Why this is Behavioral
- Notice how
CheckoutCartdoesn’t wrap the payment strategy to add features to it. - It delegates the _action_ of paying to the strategy.
- We aren’t building a bigger, more complex object; we are just swapping out the “brain” that handles the logic.