Welcome back! We are continuing our journey through Behavioral Patterns in software design. In previous lessons, we explored the Command and Observer patterns, focusing on object communication and state changes. Now, we will learn how to implement the Strategy Pattern in Python. We will break down the pattern into manageable parts and illustrate its practical application through a clear example.
Consider a scenario where you have a ShoppingCart class that can handle payments through different methods, such as credit cards or PayPal. Using the Strategy Pattern, we can encapsulate these payment methods into separate classes and have the ShoppingCart
class use any of these strategies interchangeably.
First, we need to define an abstract base class that all payment strategies will inherit from. This ensures that all payment methods follow a common interface and can be used interchangeably.
Python1from abc import ABC, abstractmethod 2 3class PaymentStrategy(ABC): 4 @abstractmethod 5 def pay(self, amount): 6 pass
In this snippet, we create an abstract class PaymentStrategy
with an abstract method pay
. Any class that inherits from PaymentStrategy
must implement the pay
method.
Next, we implement concrete strategies that encapsulate different payment methods. Here, we define two strategies: CreditCardStrategy
and PayPalStrategy
.
Python1class CreditCardStrategy(PaymentStrategy): 2 def __init__(self, card_number): 3 self.card_number = card_number 4 5 def pay(self, amount): 6 print(f"Paid {amount} using Credit Card: {self.card_number}")
In the CreditCardStrategy
class, we implement the pay
method to handle credit card transactions. This class requires a card number upon initialization.
Python1class PayPalStrategy(PaymentStrategy): 2 def __init__(self, email): 3 self.email = email 4 5 def pay(self, amount): 6 print(f"Paid {amount} using PayPal: {self.email}")
Similarly, the PayPalStrategy
class implements the pay
method for PayPal transactions. It requires an email address to initialize.
The ShoppingCart
class is our context class that will use any given payment strategy. This class keeps a reference to a PaymentStrategy
object and can switch strategies at runtime.
Python1class ShoppingCart: 2 def __init__(self): 3 self.strategy = None 4 5 def set_payment_strategy(self, strategy): 6 self.strategy = strategy 7 8 def checkout(self, amount): 9 if self.strategy: 10 self.strategy.pay(amount) 11 else: 12 print("No payment strategy set.")
In the ShoppingCart
class, the set_payment_strategy
method allows us to set the payment strategy, and the checkout
method uses the selected strategy to make a payment.
Below is the complete code for our example, integrating all the parts we've discussed:
Python1from abc import ABC, abstractmethod 2 3class PaymentStrategy(ABC): 4 @abstractmethod 5 def pay(self, amount): 6 pass 7 8class CreditCardStrategy(PaymentStrategy): 9 def __init__(self, card_number): 10 self.card_number = card_number 11 12 def pay(self, amount): 13 print(f"Paid {amount} using Credit Card: {self.card_number}") 14 15class PayPalStrategy(PaymentStrategy): 16 def __init__(self, email): 17 self.email = email 18 19 def pay(self, amount): 20 print(f"Paid {amount} using PayPal: {self.email}") 21 22class ShoppingCart: 23 def __init__(self): 24 self.strategy = None 25 26 def set_payment_strategy(self, strategy): 27 self.strategy = strategy 28 29 def checkout(self, amount): 30 if self.strategy: 31 self.strategy.pay(amount) 32 else: 33 print("No payment strategy set.") 34 35if __name__ == "__main__": 36 cart = ShoppingCart() 37 38 credit_card = CreditCardStrategy("1234-5678-9876-5432") 39 paypal = PayPalStrategy("user@example.com") 40 41 cart.set_payment_strategy(credit_card) 42 cart.checkout(100) # Outputs: Paid 100 using Credit Card: 1234-5678-9876-5432 43 44 cart.set_payment_strategy(paypal) 45 cart.checkout(200) # Outputs: Paid 200 using PayPal: user@example.com
Understanding the Strategy Pattern is crucial because it promotes flexibility and reusability in your code. Instead of hardcoding multiple algorithms within a class, you can encapsulate them into separate strategy classes. This makes your code more maintainable and scalable. Consider a real-world example: an e-commerce platform. One customer might prefer to pay using a credit card, while another might choose PayPal. With the Strategy Pattern, you can easily switch payment methods without altering the underlying business logic of the shopping cart. By mastering the Strategy Pattern, you'll be equipped to build systems that can adapt to varying requirements with minimal changes to the codebase.