The Strategy Design Pattern is based on the principle of composition over inheritance. Instead of relying on inheritance to define different behaviors, it uses composition to delegate the behavior to different strategy objects. The pattern consists of three main components:
This is an interface that defines the contract for all concrete strategies. It declares a method or a set of methods that represent the algorithm.
// Strategy interface
interface PaymentStrategy {
void pay(double amount);
}
These are classes that implement the strategy interface. Each concrete strategy provides a different implementation of the algorithm defined in the interface.
// Concrete strategy: Credit Card Payment
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using Credit Card.");
}
}
// Concrete strategy: PayPal Payment
class PayPalPayment implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("Paid $" + amount + " using PayPal.");
}
}
The context is a class that holds a reference to a strategy object and uses it to perform a specific task. It allows the client to change the strategy at runtime.
// Context class
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public ShoppingCart(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(double amount) {
paymentStrategy.pay(amount);
}
}
To use the Strategy Design Pattern, follow these steps:
Create an interface that declares the method or methods representing the algorithm.
Create classes that implement the strategy interface and provide different implementations of the algorithm.
Create a class that holds a reference to the strategy object and uses it to perform a specific task.
Instantiate the context class with a specific strategy and call the appropriate method to execute the algorithm. You can also change the strategy at runtime if needed.
public class Main {
public static void main(String[] args) {
// Create a shopping cart with Credit Card payment strategy
ShoppingCart cart = new ShoppingCart(new CreditCardPayment());
cart.checkout(100.0);
// Change the payment strategy to PayPal
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(200.0);
}
}
Using interfaces for strategies allows for loose coupling between the context and the concrete strategies. It also makes it easier to add new strategies in the future without modifying the context class.
Pass the strategy object to the context class through the constructor or a setter method. This follows the principle of dependency injection and makes the code more testable and flexible.
Allow the client to change the strategy at runtime. This can be useful in scenarios where the algorithm needs to be adjusted based on certain conditions.
Strategies should be stateless as much as possible. This means that they should not store any state information and should only depend on the input provided to them. Stateless strategies are easier to understand, test, and reuse.
If the number of strategies is limited and well-defined, you can use enums to represent the different strategies. This can make the code more readable and less error-prone.
enum PaymentStrategies {
CREDIT_CARD(new CreditCardPayment()),
PAYPAL(new PayPalPayment());
private final PaymentStrategy strategy;
PaymentStrategies(PaymentStrategy strategy) {
this.strategy = strategy;
}
public PaymentStrategy getStrategy() {
return strategy;
}
}
// Usage
ShoppingCart cart = new ShoppingCart(PaymentStrategies.CREDIT_CARD.getStrategy());
cart.checkout(150.0);
Provide clear documentation for each strategy, including its purpose, input requirements, and any assumptions or limitations. This will make it easier for other developers to understand and use the strategies.
The Strategy Design Pattern is a powerful tool for Java developers to create flexible and maintainable code. By encapsulating algorithms in separate strategy classes and using composition to delegate behavior, the pattern allows for easy modification and extension of the application’s functionality. By following the fundamental concepts, usage methods, common practices, and best practices outlined in this blog post, you can effectively use the Strategy Design Pattern in your Java projects.