Welcome to the lesson on the Strategy pattern, a key concept in Behavioral Design Patterns. In this unit, we explore behavioral design patterns that enable complex interactions between objects. Behavioral patterns focus on how objects interact and communicate with each other to distribute responsibilities. We will begin with the Strategy pattern, which helps object-oriented systems choose the appropriate algorithm at runtime.
By the end of this lesson, you will:
- Understand the concept of the Strategy pattern.
- Learn how to implement and use it in Java.
- Recognize the benefits of using the Strategy pattern in software design.
The Strategy pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one in a separate class, and make them interchangeable within the context object. This encapsulation allows you to select and change the algorithm at runtime, providing flexibility and decoupling the client's code from the specific implementations of the algorithms. The pattern is particularly useful when you have multiple ways to perform a task and want to enable the dynamic selection of the algorithm based on the application's current state or user input.
Let's look at a practical example involving different payment methods for a shopping cart. Here, the Strategy pattern allows us to switch between various payment methods—such as credit card and PayPal—dynamically during checkout. By defining a common interface for payment strategies and creating separate classes for each method, the ShoppingCart
can easily switch between them at runtime, enhancing flexibility and maintainability.
First, we create an interface representing our payment method.
Java1public interface PaymentStrategy { 2 void pay(int amount); 3}
Here, we define a PaymentStrategy
interface with a single method, pay(int amount)
. Any class implementing this interface will need to provide an implementation for the pay
method, allowing that class's own payment logic to be used.
Now, let's create two concrete strategies: one for payments via credit card and another for PayPal.
Java1public class CreditCardStrategy implements PaymentStrategy { 2 private String cardNumber; 3 4 public CreditCardStrategy(String cardNumber) { 5 this.cardNumber = cardNumber; 6 } 7 8 @Override 9 public void pay(int amount) { 10 System.out.println("Paid " + amount + " using Credit Card: " + cardNumber); 11 } 12}
The CreditCardStrategy
class implements the PaymentStrategy
interface and provides a specific implementation for the pay
method. This class uses a credit card number passed to its constructor to process the payment.
Java1public class PayPalStrategy implements PaymentStrategy { 2 private String email; 3 4 public PayPalStrategy(String email) { 5 this.email = email; 6 } 7 8 @Override 9 public void pay(int amount) { 10 System.out.println("Paid " + amount + " using PayPal: " + email); 11 } 12}
The PayPalStrategy
class also implements the PaymentStrategy
interface but processes the payment using an email address. This example demonstrates how different concrete strategies can provide their own specific implementations of the same method.
Java1public class ShoppingCart { 2 private PaymentStrategy strategy; 3 4 public void setPaymentStrategy(PaymentStrategy strategy) { 5 this.strategy = strategy; 6 } 7 8 public void checkout(int amount) { 9 if (strategy != null) { 10 strategy.pay(amount); 11 } else { 12 System.out.println("No payment strategy set."); 13 } 14 } 15}
The ShoppingCart
class uses a PaymentStrategy
reference to process payments. By using the setPaymentStrategy
method, the payment strategy can be changed at runtime. The checkout
method ensures that a payment strategy is set before attempting to process a payment, providing a safety check before calling the pay
method.
Finally, let's see how to use these classes together.
Java1public class Main { 2 public static void main(String[] args) { 3 ShoppingCart cart = new ShoppingCart(); 4 5 CreditCardStrategy creditCard = new CreditCardStrategy("1234-5678-9876-5432"); 6 PayPalStrategy payPal = new PayPalStrategy("user@example.com"); 7 8 cart.setPaymentStrategy(creditCard); 9 cart.checkout(100); 10 11 cart.setPaymentStrategy(payPal); 12 cart.checkout(200); 13 } 14}
In the Main
class, we create a ShoppingCart
instance and two payment strategies: CreditCardStrategy
and PayPalStrategy
. By changing the payment strategy at runtime with the setPaymentStrategy
method, we demonstrate the flexibility of the Strategy pattern. We process two payments, first using the credit card strategy and then using the PayPal strategy.
The Strategy pattern provides flexibility and reusability in your code:
-
Flexibility: It allows the algorithm to be selected at runtime, enabling dynamic behavior changes.
-
Reusability: Individual algorithms are encapsulated in separate classes, making them reusable across different contexts.
This pattern follows the Open/Closed Principle, as new strategies can be introduced without modifying the existing code.
Now that you understand the Strategy pattern, it's time to dive into the practice section and apply what you've learned. This hands-on approach will reinforce your understanding and help you see how powerful and flexible the Strategy pattern can be. Let's get started!