Strategy
A strategy defines a family of algorithms and encapsulates each one, making them interchangeable, such that the algorithm itself will vary independently of clients that use it (in other words, it makes the objects interchangeable).
TL;DR
The strategy pattern allows you to switch the applied logic or algorithm of a case based on the context of the situation.
Problem
If you have a navigation app, you might implement different algorithms for bicycle, bus, and car routing between two points. In this scenario, the arguments passed to the route builders are exactly the same, but the specific implementation of how to get between the points will change.
When you need to implement all of these different versions of the logic it is possible to have massive conditionals and difficult logic, but this forces the application to become a bloated mess.
Solution
The solution is to use the Strategy pattern, which suggests that each specific algorithm be extracted into its own class, with a Context class (often the main business logic) given the power to select which methods to use. The main point here is to make sure that the Context class is given a field to define which strategy to use, and that all the strategies themselves implement the same interface
from abc import ABC, abstractmethod
# Strategy interface
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount):
pass
# Concrete strategies
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number, holder_name):
self.card_number = card_number
self.holder_name = holder_name
def pay(self, amount):
return f"Paid ${amount} using Credit Card ending in {self.card_number[-4:]}"
class PayPalPayment(PaymentStrategy):
def __init__(self, email):
self.email = email
def pay(self, amount):
return f"Paid ${amount} using PayPal account {self.email}"
class CashPayment(PaymentStrategy):
def pay(self, amount):
return f"Paid ${amount} in cash"
# Context class
class ShoppingCart:
def __init__(self):
self.items = []
self.payment_strategy = None
def add_item(self, item, price):
self.items.append((item, price))
def set_payment_strategy(self, strategy):
self.payment_strategy = strategy
def checkout(self):
total = sum(price for item, price in self.items)
if self.payment_strategy:
payment_result = self.payment_strategy.pay(total)
return f"Items: {[item for item, price in self.items]}\nTotal: ${total}\n{payment_result}"
else:
return "No payment method selected"
# Example usage
if __name__ == "__main__":
# Create shopping cart
cart = ShoppingCart()
cart.add_item("Laptop", 999.99)
cart.add_item("Mouse", 25.50)
# Pay with credit card
credit_card = CreditCardPayment("1234567890123456", "John Doe")
cart.set_payment_strategy(credit_card)
print(cart.checkout())
print("\n" + "="*50 + "\n")
# Pay with PayPal
paypal = PayPalPayment("john@example.com")
cart.set_payment_strategy(paypal)
print(cart.checkout())
print("\n" + "="*50 + "\n")
# Pay with cash
cash = CashPayment()
cart.set_payment_strategy(cash)
print(cart.checkout())