Understanding the Strategy Design Pattern in Java
Problem
The Strategy pattern addresses the need to define a family of algorithms, encapsulate each one, and make them interchangeable. This pattern lets the algorithm vary independently from the clients that use it. It is useful when you have multiple ways to perform a specific task and want to choose the algorithm at runtime.
Solution
The Strategy pattern involves three main components:
- Context: The object that contains a reference to a strategy object and uses it to execute the algorithm.
- Strategy: An interface common to all supported algorithms. The context uses this interface to call the algorithm defined by a concrete strategy.
- Concrete Strategy: Classes that implement the strategy interface, providing specific algorithms.
The context delegates the execution of the algorithm to the strategy object, which allows the algorithm to be selected at runtime.
Pros and Cons
Pros
- Encapsulation of Algorithms: Each algorithm is encapsulated in its own class, making it easy to switch between them and to add new algorithms without changing the context.
- Single Responsibility Principle: The context class is simplified as it delegates the algorithm implementation to strategy classes.
- Open/Closed Principle: New strategies can be introduced without altering the existing context or strategy classes.
Cons
- Increased Number of Classes: The pattern increases the number of classes in the codebase due to the creation of new strategy classes for each algorithm.
- Complexity in Switching Strategies: If not managed well, switching strategies dynamically at runtime can introduce complexity and potential errors.
Example of Real-World Application
A practical example of the Strategy pattern is in a payment processing system where different payment methods (e.g., credit card, PayPal, bank transfer) are implemented as different strategies. The client can choose the appropriate payment strategy at runtime.
Example Code in Java
Strategy pattern in code:
java
// Strategy Interface
public interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategy 1
public class CreditCardPayment implements PaymentStrategy {
private String cardNumber;
public CreditCardPayment(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card " + cardNumber);
}
}
// Concrete Strategy 2
public class PayPalPayment implements PaymentStrategy {
private String email;
public PayPalPayment(String email) {
this.email = email;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal account " + email);
}
}
// Context
public class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
// Client code
public class Client {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment("1234-5678-9876-5432"));
cart.checkout(100);
cart.setPaymentStrategy(new PayPalPayment("user@example.com"));
cart.checkout(200);
}
}
Top comments (1)
Nice explanation. In real world most time the strategies can be decided either based on some input or condition that requires different strategies. People do prefer to keep a Hashmap for fetching dynamic strategy.