Architectural Elegance in Python: Implementing Singleton and Factory Patterns Effectively
The Singleton Pattern: Managing Shared State
In Python, enforcing a single instantiation across an application lifecycle often relies on language-level execution behaviors. Because Python modules are executed exactly once per interpreter session, importing a module inherently guarantees that its top-level definitions remain unique across the entire runtime. Leveraging this characteristic allows developers to construct implicit singletons without external dependencies or complex initialization logic.
# infrastructure.py
class ConnectionPool:
def __init__(self):
self.connections = []
self.pool_capacity = 5
def acquire(self):
if len(self.connections) < self.pool_capacity:
self.connections.append(f"conn-{len(self.connections)}")
return self.connections[-1]
pool_instance = ConnectionPool()
Any submodule importing infrastructure receives the exact same pool_instance object. This approach eliminates redundant object allocation, guarantees consistent state access, and proves highly effective for configuration loaders, cache layers, or resource managers.
Thread-Safe Implementation
When explicit class-based singletons are required, concurrent access during initialization can trigger race conditions. The standard defensive strategy combines Python's __new__ hook with a mutual exclusion lock. A dual-check mechanism avoids blocking threads after the target object has already been allocated.
import threading
class TaskScheduler:
_instance = None
_lock = threading.Lock()
_bootstrapped = False
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._bootstrapped = True
return cls._instance
def __init__(self):
if not self._bootstrapped:
self.queue = []
self._bootstrapped = True
def schedule(self, task_id: str):
self.queue.append(task_id)
return f"Scheduled {task_id}"
scheduler = TaskScheduler()
print(scheduler.schedule("process-alpha"))
The explicit _bootstrapped flag separates memory allocation from constructor execution, preventing multiple runs of initialization code under heavy concurrency. This pattern maintains strict object identity while guaranteeing thread safety without compromising performance.
Factory Patterns: Decoupling Object Creation
Creational patterns delegate instantiation responsibilities away from client code, reducing coupling between interfaces and concrete implementations. The Factory pattern provides a unified routing layer for generating specialized objects based on dynamic input parameters.
Conditional Dispatch to Mapping Tables
A naive implementation typically relies on sequential branching. While functional for small scopes, this structure violates the Open/Closed Principle, forcing repeated modifications to the factory whenever new product variants are introduced.
class ReportGenerator:
def generate_report(self, data: list) -> str:
raise NotImplementedError
class PdfReport(ReportGenerator):
def generate_report(self, data: list) -> str:
return f"[PDF] Compiled report with {len(data)} entries"
class CsvReport(ReportGenerator):
def generate_report(self, data: list) -> str:
return f"[CSV] Exported {len(data)} records"
def create_report(format_type: str) -> ReportGenerator:
if format_type == "pdf":
return PdfReport()
elif format_type == "csv":
return CsvReport()
raise ValueError(f"Unsupported format: {format_type}")
Replacing conditional chains with a dictionary lookup improves readability, reduces cyclomatic complexity, and centralizes type resolution. Direct hash-map indexing eliminates sequential comparisons and accelerates dispatch throughput.
class ReportFactory:
_registry = {
"pdf": PdfReport,
"csv": CsvReport
}
@classmethod
def build(cls, fmt: str) -> ReportGenerator:
generator_class = cls._registry.get(fmt)
if not generator_class:
raise KeyError(f"No handler registered for '{fmt}'")
return generator_class()
Extending this system now requires only adding new subclasses and appending entries to the registry dictionary, leaving the factory routing logic entirely untouched.
Dynamic Registration System
For highly modular architectures, embedding the registry directly into a static class can create rigid dependencies. A decentralized approach stores mappings within an instance, enabling hot-swapping and plugin-style expansion.
class Dispatcher:
def __init__(self):
self.strategies = {}
def register(self, name: str, strategy_class):
self.strategies[name] = strategy_class
def execute(self, name: str, payload: dict):
if name not in self.strategies:
raise LookupError(f"Unknown strategy: {name}")
instance = self.strategies[name]()
return instance.process(payload)
class EncryptionStrategy:
def process(self, payload: dict) -> str:
return f"Encrypted: {payload['text']}"
class CompressionStrategy:
def process(self, payload: dict) -> str:
return f"Compressed: {payload['text'].upper()}"
bus = Dispatcher()
bus.register("encrypt", EncryptionStrategy)
bus.register("compress", CompressionStrategy)
result = bus.execute("compress", {"text": "sensitive-data"})
print(result)
This architecture promotes strict separation of concerns. Independent modules can register their own handlers dynamically, supporting runtime configuration changes and eliminating hard-coded type relationships.
Abstract Factory with Strict Contracts
When systems require assembling families of related objects without exposing concrete constructors, Abstract Factories enforce structural consistency through inheritance hierarchies. Integrating this with Python's abc module establishes runtime validation that guarantees implementation completeness.
from abc import ABC, abstractmethod
class VehiclePart(ABC):
@abstractmethod
def assemble(self) -> str: pass
class SedanChassis(VehiclePart):
def assemble(self) -> str:
return "Sedan chassis assembled"
class SuvEngine(VehiclePart):
def assemble(self) -> str:
return "SUV engine mounted"
class PartFactory(ABC):
@abstractmethod
def create_chassis(self) -> VehiclePart: pass
@abstractmethod
def create_engine(self) -> VehiclePart: pass
class CarManufacturer(PartFactory):
def create_chassis(self) -> VehiclePart:
return SedanChassis()
def create_engine(self) -> VehiclePart:
return SuvEngine()
manufacturer = CarManufacturer()
chassis_component = manufacturer.create_chassis()
engine_component = manufacturer.create_engine()
print(chassis_component.assemble())
print(engine_component.assemble())
By defining abstract base classes for both products and factories, the framework ensures that every concrete manufacturer implements the complete assembly pipeline. Introducing a new vehicle lineage involves instantiating a dedicated factory subclass without altering existing manufacturing workflows. This isolation of responsibilities streamlines unit testing, facilitates dependency injection, and simplifies long-term system evolution.