Skip to Content
Course content

371: The Strategy Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I’ve spent a lot of time cleaning up legacy codebases where a single function has grown into a 500-line monster of if/elif/else statements. Usually, this happens because the developers were trying to handle different "modes" of doing the same thing. That's exactly where the Strategy Pattern comes in. It lets you define a family of algorithms, encapsulate each one, and make them interchangeable.

Let's build a payment processing system for a small e-commerce store. We need to support Credit Cards and PayPal, and eventually, maybe Crypto.

The "Quick and Dirty" Approach

When I first start a project, I'm tempted to just jam everything into one class. It feels faster. Here is how I'd probably write this if I weren't thinking about the long term:

class Order:
    def __init__(self, amount):
        self.amount = amount

    def process_payment(self, method):
        if method == "credit_card":
            print(f"Processing ${self.amount} via Credit Card... charging bank fees.")
        elif method == "paypal":
            print(f"Processing ${self.amount} via PayPal... redirecting to portal.")
        else:
            raise ValueError("Unknown payment method")

# Usage
order = Order(100)
order.process_payment("paypal")

This works fine for two methods. But here is where I usually trip up: the moment the business asks for "Apple Pay," "Google Pay," and "Bitcoin," this process_payment method becomes a nightmare to maintain. Every time we add a payment method, we have to modify the Order class. That's a violation of the Open/Closed Principle—classes should be open for extension, but closed for modification.

Decoupling the Logic with Strategies

Instead of the Order class knowing how to pay, it should just know that it can pay. I'll create a common interface for all payment strategies using an Abstract Base Class (ABC). This ensures every new payment method I add follows the same contract.

from abc import ABC, abstractmethod

class PaymentStrategy(ABC):
    @abstractmethod
    def pay(self, amount):
        pass

class CreditCardPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paying ${amount} using Credit Card: Validating CVV...")

class PayPalPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paying ${amount} using PayPal: Checking email token...")

class BitcoinPayment(PaymentStrategy):
    def pay(self, amount):
        print(f"Paying ${amount} using Bitcoin: Waiting for blockchain confirmation...")

Now, my Order class doesn't need to care which method is being used. It just calls pay() on whatever strategy object it was given.

Connecting the Order to the Strategy

I'll modify the Order class to accept a strategy. I prefer passing the strategy into the method rather than the constructor, as it allows the user to change their mind about the payment method at the very last second without recreating the whole order object.

class Order:
    def __init__(self, amount):
        self.amount = amount

    def checkout(self, payment_strategy: PaymentStrategy):
        # The Order class doesn't know the details, 
        # it just triggers the strategy's contract.
        payment_strategy.pay(self.amount)

# Now we can swap them easily
my_order = Order(250)

# User chooses PayPal
my_order.checkout(PayPalPayment())

# User changes mind and uses Bitcoin
my_order.checkout(BitcoinPayment())

Notice how clean the Order class is now. If we need to add a new payment method next week, I don't touch a single line of the Order class. I just create a new class that inherits from PaymentStrategy. That's the power of this pattern—it isolates the volatility of the business logic from the core stability of your application.




📋 Practical Task

Build a Dynamic File Export System

You are building a data reporting tool. The application needs to be able to export a list of user records in different formats: JSON, CSV, and XML.

Your task is to implement the Strategy Pattern to handle these exports. Please complete the following:

  • Create an abstract base class ExportStrategy with an abstract method export(self, data).
  • Implement three concrete strategies: JsonExport, CsvExport, and XmlExport. Each should simply print a message like "Exporting [data] to JSON format".
  • Create a ReportGenerator class that takes a list of data in its constructor and has a method generate(self, strategy) that uses the provided strategy to export the data.
  • Demonstrate the system by exporting the same list of data using at least two different strategies.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.