Learn how to combine Python's abstract classes with Factory and Strategy design patterns to build flexible, scalable, and testable backend systems.
When building complex systems, especially in backend development, maintaining flexibility and scalability is key. One powerful way to achieve this is by combining abstract base classes (ABCs) with design patterns like Factory and Strategy. This blog will walk you through this combo, starting with the basics and moving into real-life backend use cases.
In Python, abstract classes live in the abc
module. They're used to define interfaces or templates for other classes to follow. Why do we care? Because abstract classes let you define a common structure and force subclasses to implement specific methods.
Let’s take a look this basic example:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount):
pass
Here, PaymentProcessor
is saying: “Any class that inherits me must implement a process()
method.” If a subclass forgets to define process()
, Python will raise an error.
The Factory Pattern is about delegating the instantiation of objects to a factory method or class. It works beautifully with ABCs. Let’s say you’re building a SaaS platform that supports multiple payment providers: Stripe, PayPal, and Bank Transfer.
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process(self, amount):
pass
class StripeProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing ${amount} through Stripe.")
class PayPalProcessor(PaymentProcessor):
def process(self, amount):
print(f"Processing ${amount} through PayPal.")
def get_payment_processor(provider: str) -> PaymentProcessor:
if provider == 'stripe':
return StripeProcessor()
elif provider == 'paypal':
return PayPalProcessor()
else:
raise ValueError("Unsupported payment provider.")
processor = get_payment_processor('stripe')
processor.process(100)
This setup:
The Strategy Pattern is about choosing behavior at runtime. Instead of hardcoding logic, you inject it. Say you're building a discount system for an e-commerce backend. Different users have different discount rules.
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def apply_discount(self, amount):
pass
class NoDiscount(DiscountStrategy):
def apply_discount(self, amount):
return amount
class PercentageDiscount(DiscountStrategy):
def __init__(self, percent):
self.percent = percent
def apply_discount(self, amount):
return amount * (1 - self.percent / 100)
class Checkout:
def __init__(self, discount_strategy: DiscountStrategy):
self.discount_strategy = discount_strategy
def finalize(self, amount):
return self.discount_strategy.apply_discount(amount)
checkout = Checkout(PercentageDiscount(10))
print(checkout.finalize(200)) # Output: 180.0
Now your business logic is clean, testable, and open to extension.
Let’s combine both patterns for a notification system in a backend platform that supports Email, SMS, and Slack alerts.
from abc import ABC, abstractmethod
class Notifier(ABC):
@abstractmethod
def send(self, message: str):
pass
class EmailNotifier(Notifier):
def send(self, message: str):
print(f"Sending email: {message}")
class SMSNotifier(Notifier):
def send(self, message: str):
print(f"Sending SMS: {message}")
class SlackNotifier(Notifier):
def send(self, message: str):
print(f"Sending Slack message: {message}")
def get_notifier(channel: str) -> Notifier:
if channel == 'email':
return EmailNotifier()
elif channel == 'sms':
return SMSNotifier()
elif channel == 'slack':
return SlackNotifier()
else:
raise ValueError("Unknown channel.")
class NotificationStrategy(ABC):
@abstractmethod
def should_notify(self, user_settings: dict) -> bool:
pass
class NotifyAlways(NotificationStrategy):
def should_notify(self, user_settings):
return True
class NotifyBusinessHours(NotificationStrategy):
def should_notify(self, user_settings):
from datetime import datetime
hour = datetime.now().hour
return 9 <= hour < 17
class NotificationService:
def __init__(self, notifier: Notifier, strategy: NotificationStrategy):
self.notifier = notifier
self.strategy = strategy
def notify(self, user_settings, message):
if self.strategy.should_notify(user_settings):
self.notifier.send(message)
notifier = get_notifier('email')
strategy = NotifyBusinessHours()
service = NotificationService(notifier, strategy)
service.notify({}, "System maintenance scheduled.")
This setup allows you to:
Use abstract classes with Factory + Strategy when:
Using abstract classes in Python with Factory and Strategy patterns:
When your system grows, these patterns help bring order to the chaos.