Singleton is a simple, controversial yet common design pattern that all developers should be aware of
If you're interested in loading this design pattern straight into your brain, understand why some developers consider it an anti-pattern well, that's great because we are going to cover that!
What is Singleton? 💔
Singleton is when you force having only one (or limited) object(s) of its kind.
When to use it? 🕶
You use it when you want to force having only one (or limited) object(s) instantiated from a class.
It's useful when it comes to situations where you have limited global resources. like when your server has a database that should at most have one connection. If anyone mistakenly created a new instance of your database object, that might result in a new connection.
In python, this could be implemented by altering class behavior, using decorators, metaclasses, or a base class.
Pythonic - Base class
a pythonic way to implement Singleton is using a base class, but the constructor will be called on each instantiation
class Singleton(object): _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance
__new__ which is a Magic Method similar to
__new__ is called before the creation of an object and is responsible for returning a new instance of the class, whereas
__init__ is called after the creation of an object and it's responsible for initializing the object aka Constructor.
Real Singleton - metaclass
Although the above approach works well, a real singleton shouldn't recall the constructor
we'll use a metaclass approach, if you are not familiar with metaclasses check this StackOverflow answer.
class Singleton(type): def __init__(cls, name, bases, dict): super(Singleton, cls).__init__(name, bases, dict) cls.instance = None def __call__(cls,*args,**kw): if cls.instance is None: cls.instance = super(Singleton, cls).__call__(*args, **kw) return cls.instance
And it could be used like
class DB(metaclass=Singleton): def __init__(self): self.id = random() * 100 def conn_id(self): print("Connection ID: ", self.id) first = DB() sec = DB() print(first == sec) # True; reference check print(DB().conn_id() == first.conn_id() == sec.conn_id()) # True
The Religious war behind singleton ⚔
Many argue over how Singleton is an anti-pattern or if it's just being misused.
Some say that it introduces global variables which lead to race conditions (in a multithreaded environment), the code becomes harder to test (as your code becomes tightly coupled with the singletons), it doesn't get freed from memory, it violates SRP (Single Responsibility Principle) and hides dependencies.
Whereas others say that Singletons aren't supposed to be used as global variables but should be used only to control the number of the object instantiations, still Singleton could be replaced by instantiating one object in the
main() (the entry function of your app) and passing it to the needed modules aka Dependency Injection (DI).
It could be only a few edge cases to use Singleton like when you need a logger functionality, where it doesn't affect the code whether the logger is enabled or not, or when it's managed in an injectable container to handle its life cycle aka Dependency Injection (DI) just like how it's used in Angular or NestJS.
It is "acceptable" to use Singleton as long you are aware of the consequences, and it's not being overused.
I'm planning to cover the most common design patterns. if you are interested, you can follow me to get notified of any new articles that I publish in the future or you can check the below list of my published articles about design patterns: