CreationalPythonverifiedVerified
Factory Method Pattern in Python
Defines an interface for creating an object but lets subclasses decide which class to instantiate. Defers instantiation to subclasses.
How to Implement the Factory Method Pattern in Python
1Step 1: Define the product interface using Protocol
from abc import ABC, abstractmethod
class Transport(ABC):
@abstractmethod
def deliver(self, cargo: str) -> str: ...2Step 2: Implement concrete products
class Truck(Transport):
def deliver(self, cargo: str) -> str:
return f'Delivering "{cargo}" by road in a truck'
class Ship(Transport):
def deliver(self, cargo: str) -> str:
return f'Delivering "{cargo}" by sea in a ship'3Step 3: Create the abstract creator with the factory method
class Logistics(ABC):
@abstractmethod
def create_transport(self) -> Transport: ...
def plan_delivery(self, cargo: str) -> str:
transport = self.create_transport()
return transport.deliver(cargo)4Step 4: Wire up concrete creators and test
class RoadLogistics(Logistics):
def create_transport(self) -> Transport:
return Truck()
class SeaLogistics(Logistics):
def create_transport(self) -> Transport:
return Ship()
logistics: Logistics = RoadLogistics()
print(logistics.plan_delivery("Electronics"))
sea_logistics: Logistics = SeaLogistics()
print(sea_logistics.plan_delivery("Grain"))"""Notification Factory with validation and runtime registry."""
import logging
import re
import time
import uuid
from abc import ABC, abstractmethod
from dataclasses import dataclass
logger = logging.getLogger(__name__)
# [step] Define notification result and product interface
@dataclass(frozen=True)
class NotificationResult:
success: bool
message_id: str
timestamp: float
provider: str
error_message: str | None = None
class Notification(ABC):
@property
@abstractmethod
def channel(self) -> str: ...
@abstractmethod
def validate(self, to: str) -> bool: ...
@abstractmethod
async def send(self, to: str, subject: str, body: str) -> NotificationResult: ...
# [step] Implement concrete notification types
class EmailNotification(Notification):
@property
def channel(self) -> str:
return "email"
def validate(self, to: str) -> bool:
return bool(re.match(r"^[^\s@]+@[^\s@]+\.[^\s@]+$", to))
async def send(self, to: str, subject: str, body: str) -> NotificationResult:
if not self.validate(to):
raise ValueError(f"Invalid email: {to}")
return NotificationResult(True, f"email_{uuid.uuid4().hex[:8]}", time.time(), self.channel)
class SmsNotification(Notification):
@property
def channel(self) -> str:
return "sms"
def validate(self, to: str) -> bool:
return bool(re.match(r"^\+[1-9]\d{6,14}$", to))
async def send(self, to: str, subject: str, body: str) -> NotificationResult:
if not self.validate(to):
raise ValueError(f"Invalid phone: {to}")
return NotificationResult(True, f"sms_{uuid.uuid4().hex[:8]}", time.time(), self.channel)
# [step] Define the creator hierarchy and registry
class NotificationService(ABC):
@abstractmethod
def _create_notification(self) -> Notification: ...
async def notify(self, to: str, subject: str, body: str) -> NotificationResult:
notification = self._create_notification()
if not notification.validate(to):
raise ValueError(f"Validation failed for {notification.channel}: {to}")
return await notification.send(to, subject, body)
class EmailService(NotificationService):
def _create_notification(self) -> Notification:
return EmailNotification()
class SmsService(NotificationService):
def _create_notification(self) -> Notification:
return SmsNotification()
_SERVICE_REGISTRY: dict[str, type[NotificationService]] = {
"email": EmailService,
"sms": SmsService,
}
def get_notification_service(channel: str) -> NotificationService:
cls = _SERVICE_REGISTRY.get(channel)
if cls is None:
available = ", ".join(_SERVICE_REGISTRY)
raise ValueError(f'Unknown channel "{channel}". Available: {available}')
return cls()Factory Method Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Factory Method Pattern in the Real World
“Think of a logistics company that ships packages. The headquarters defines the shipping process but doesn’t decide the vehicle. Regional offices (subclasses) choose whether to use trucks, ships, or drones based on local conditions. The headquarters just says ‘get me a transport’ and the regional office delivers the right one.”