Beyond the Hype of Creational Patterns Design patterns fall into three buckets: behavioral, structural, and creational. While behavioral patterns like Strategy manage communication and structural patterns handle assembly, creational patterns dictate how we instantiate objects. The Singleton is perhaps the most famous creational pattern, but in the world of modern Python development, it has become a notorious anti-pattern. Understanding why it fails helps us appreciate better alternatives like the Object Pool. Prerequisites To get the most out of this tutorial, you should be comfortable with classes and inheritance in Python. Familiarity with Metaclasses, decorators, and the concept of Context Managers (the `with` statement) will help you grasp the more advanced implementation details. The Problem with Singletons A Singleton restricts a class to a single instance. While this sounds useful for loggers or database managers, it creates a global state that makes testing nearly impossible. Because you cannot easily reset the instance between test runs, your tests become coupled. Furthermore, Python lacks private constructors. Implementing a true singleton requires "trickery" like metaclasses: ```python class Singleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super().__call__(*args, **kwargs) return cls._instances[cls] class Logger(metaclass=Singleton): def log(self, msg): print(f"Log: {msg}") ``` In this snippet, we override the `__call__` method of a metaclass to intercept instantiation. If the instance exists in our dictionary, we return it; otherwise, we create it. It works, but it’s overkill. Python modules are already singletons by nature—importing a module multiple times always returns the same object. Just use a module. Implementing an Object Pool The Object Pool is a smarter generalization. Instead of one instance, it manages a cache of reusable objects. This is vital when object creation is expensive, such as maintaining Database Connections. ```python class ReusablePool: def __init__(self, size): self.free = [Reusable() for _ in range(size)] self.in_use = [] def acquire(self): if not self.free: raise Exception("No objects available") obj = self.free.pop(0) self.in_use.append(obj) return obj def release(self, obj): self.in_use.remove(obj) self.free.append(obj) ``` Safety with Context Managers Manually calling `acquire` and `release` is error-prone. If your code crashes before releasing, you leak resources. We solve this by wrapping the pool in a Context Manager: ```python class PoolManager: def __init__(self, pool): self.pool = pool def __enter__(self): self.obj = self.pool.acquire() return self.obj def __exit__(self, type, value, traceback): self.pool.release(self.obj) ``` Now, the syntax `with PoolManager(my_pool) as obj:` ensures the object returns to the pool even if an exception occurs. Always remember: when an object returns to the pool, you must reset its state to prevent data leaks between different parts of your application.
Database Connections
Products
Apr 2021 • 1 videos
High activity month for Database Connections. ArjanCodes among the most active voices, with 1 videos across 1 sources.
Apr 2021
- Apr 30, 2021