Strategy Pattern
The Strategy Pattern is a behavioral design pattern that enables selecting an algorithm at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable. This lets the algorithm vary independently from the clients that use it.
This pattern is a powerful tool for creating flexible and maintainable software. Instead of implementing a single algorithm directly, the code receives runtime instructions as to which algorithm in a family of algorithms to use.
When to Use It
- When you have multiple variants of an algorithm for a task and you want to switch between them at runtime.
- When you have a class that defines many behaviors, and these appear as multiple conditional statements in its operations. The Strategy pattern lets you move each branch of the conditional to its own class. -- When you want to isolate the business logic of a class from the implementation details of its algorithms.
Structure
- Context: The class that contains a reference to a
Strategyobject. The Context does not know the concrete type of the strategy. It should work with all strategies via theStrategyinterface. - Strategy: The common interface for all supported algorithms.
- ConcreteStrategy: Implements the
Strategyinterface, providing a specific algorithm.
Example: A Payment Processing System
Imagine an e-commerce application that needs to process payments. There could be multiple payment methods, such as Credit Card, PayPal, or a Gift Card. The Strategy pattern is perfect for encapsulating the logic for each payment method.
1. Strategy Interface (PaymentStrategy)
This interface defines a single method, pay, that all payment strategies must implement.
package com.example.patterns.strategy;
public interface PaymentStrategy {
void pay(int amount);
}
2. Concrete Strategies (CreditCardPayment, PayPalPayment)
These classes implement the PaymentStrategy interface, each providing a different payment algorithm.
CreditCardPayment.java
package com.example.patterns.strategy;
public class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
public CreditCardPayment(String name, String cardNumber) {
this.name = name;
this.cardNumber = cardNumber;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid with credit card ending in " + cardNumber.substring(cardNumber.length() - 4));
}
}
PayPalPayment.java
package com.example.patterns.strategy;
public class PayPalPayment implements PaymentStrategy {
private String emailId;
public PayPalPayment(String email) {
this.emailId = email;
}
@Override
public void pay(int amount) {
System.out.println(amount + " paid using PayPal account " + emailId);
}
}
3. Context Class (ShoppingCart)
The ShoppingCart class holds a reference to a PaymentStrategy. It doesn't know the concrete details of the payment method; it just calls the pay method on the strategy object.
package com.example.patterns.strategy;
public class ShoppingCart {
private int totalAmount = 0;
public void addItem(String item, int price) {
System.out.println("Adding item: " + item + " for $" + price);
this.totalAmount += price;
}
public int getTotalAmount() {
return totalAmount;
}
// The cart takes a payment strategy to complete the checkout
public void checkout(PaymentStrategy paymentMethod) {
paymentMethod.pay(getTotalAmount());
}
}
4. Putting It All Together
The client code creates a ShoppingCart, adds items, and then decides which payment strategy to use at runtime for the checkout process.
package com.example.patterns.strategy;
public class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.addItem("Laptop", 1200);
cart.addItem("Mouse", 25);
System.out.println("Total amount: $" + cart.getTotalAmount());
System.out.println("---");
// --- Client decides the strategy at runtime ---
// Pay with a Credit Card
System.out.println("Checking out with Credit Card:");
PaymentStrategy creditCard = new CreditCardPayment("John Doe", "1234567890123456");
cart.checkout(creditCard);
System.out.println("---");
// Pay with PayPal
System.out.println("Checking out with PayPal:");
PaymentStrategy payPal = new PayPalPayment("john.doe@example.com");
cart.checkout(payPal);
}
}
Output:
Adding item: Laptop for $1200
Adding item: Mouse for $25
Total amount: $1225
---
Checking out with Credit Card:
1225 paid with credit card ending in 3456
---
Checking out with PayPal:
1225 paid using PayPal account john.doe@example.com
This example clearly shows how the ShoppingCart is decoupled from the payment logic. We can add new payment methods (like BitcoinPayment) without changing the ShoppingCart class at all.