The Strategy Pattern is a behavioral design pattern that allows a family of algorithms to be defined and encapsulated within a class hierarchy. The pattern enables an algorithm to vary independently from clients that use it by allowing the behavior (or “strategy”) to be selected at runtime.
Key Components of the Strategy Pattern:
- Strategy Interface: Defines an interface common to all supported algorithms. The context uses this interface to call the algorithm defined by a concrete strategy.
- Concrete Strategies: Implement the algorithm using the Strategy interface.
- Context: Maintains a reference to a Strategy object and calls its algorithm when required.
Example: Implementing Strategy Pattern in Java
We’ll implement a simple payment system where users can choose different payment strategies like Credit Card or PayPal to process payments.
Step 1: Define the Strategy
Interface
// PaymentStrategy.java
public interface PaymentStrategy {
void pay(double amount);
}
Step 2: Create Concrete Strategy Classes
Each concrete strategy implements the PaymentStrategy
interface to define specific payment behaviors.
- CreditCardPayment:
// CreditCardPayment.java
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
private String cardHolderName;
public CreditCardPayment(String cardNumber, String cardHolderName) {
this.cardNumber = cardNumber;
this.cardHolderName = cardHolderName;
}
@Override
public void pay(double amount) {
System.out.println(amount + " paid with credit card (" + cardHolderName + ").");
}
}
- PayPalPayment:
// PayPalPayment.java
public class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(double amount) {
System.out.println(amount + " paid using PayPal (" + email + ").");
}
}
Step 3: Create the Context
Class
The Context
class (e.g., ShoppingCart
) will interact with the PaymentStrategy
and delegate the payment to the selected strategy at runtime.
// ShoppingCart.java
import java.util.ArrayList;
import java.util.List;
public class ShoppingCart {
private List items;
private PaymentStrategy paymentStrategy;
public ShoppingCart() {
this.items = new ArrayList<>();
}
public void addItem(double price) {
items.add(price);
}
public double calculateTotal() {
return items.stream().mapToDouble(Double::doubleValue).sum();
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout() {
double total = calculateTotal();
if (paymentStrategy == null) {
System.out.println("Please select a payment method.");
} else {
paymentStrategy.pay(total);
}
}
}
Step 4: Use the Strategy Pattern in Main
Class
// Main.java
public class Main {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Adding items to the cart
cart.addItem(50.0);
cart.addItem(100.0);
// Paying with Credit Card
PaymentStrategy creditCardPayment = new CreditCardPayment("1234-5678-9012", "John Doe");
cart.setPaymentStrategy(creditCardPayment);
cart.checkout(); // Output: 150.0 paid with credit card (John Doe).
// Paying with PayPal
PaymentStrategy payPalPayment = new PayPalPayment("john.doe@example.com");
cart.setPaymentStrategy(payPalPayment);
cart.checkout(); // Output: 150.0 paid using PayPal (john.doe@example.com).
}
}
Key Points of the Example:
- Strategy Interface:
PaymentStrategy
defines thepay()
method, which all concrete strategies implement. - Concrete Strategies:
CreditCardPayment
andPayPalPayment
implement the specific payment algorithms. - Context:
ShoppingCart
is the context class that interacts with thePaymentStrategy
interface and allows for switching payment strategies dynamically.
Output:
150.0 paid with credit card (John Doe).
150.0 paid using PayPal (john.doe@example.com).
Benefits of the Strategy Pattern:
- Flexible: Algorithms can be changed dynamically at runtime.
- Maintainable: Each algorithm is encapsulated in its own class, adhering to the Single Responsibility Principle.
- Extensible: New strategies can be easily introduced without changing existing code.
When to Use:
- When you have multiple ways to perform an operation, and you need the flexibility to switch between these methods at runtime.
- To avoid using many conditional statements like
if-else
orswitch-case
when dealing with different behaviors.
+---------------+ +------------------+
| Client | | Strategy |
| |<--------->| + operation() |
+---------------+ +------------------+
| |
v v
+---------------+ +------------------+
| Context | | ConcreteStrategyA |
| | | + operation() |
| -strategy | +------------------+
+---------------+ ^
| |
v v
+------------------+
| ConcreteStrategyB |
| + operation() |
+------------------+