singleton example

This commit is contained in:
Ruidy Nemausat 2020-09-14 21:16:45 +02:00
parent d5e94c90a1
commit d525eb96b1
5 changed files with 148 additions and 0 deletions

View file

@ -9,3 +9,4 @@
- [Abstract Factory](creational/abstract-factory/README.md)
- [Builder](creational/builder/README.md)
- [Prototype](creational/prototype/README.md)
- [Singleton](creational/singleton/README.md)

View file

@ -4,3 +4,4 @@
- [Abstract Factory](abstract-factory/README.md)
- [Builder](builder/README.md)
- [Prototype](prototype/README.md)
- [Singleton](singleton/README.md)

View file

@ -0,0 +1,39 @@
# Singleton
Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
Singleton has almost the same pros and cons as global variables. Although theyre super-handy, they break the modularity of your code.
You cant just use a class that depends on Singleton in some other context. Youll have to carry the Singleton class as well. Most of the time, this limitation comes up during the creation of unit tests.
## Summary
Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
## Problem
The Singleton pattern solves two problems at the same time, violating the Single Responsibility Principle:
1. Ensure that a class has just a single instance.
2. Provide a global access point to that instance.
## Solution
All implementations of the Singleton have these two steps in common:
- Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
- Create a static creation method that acts as a constructor. Under the hood, this method calls the private constructor to create an object and saves it in a static field. All following calls to this method return the cached object.
If your code has access to the Singleton class, then its able to call the Singletons static method. So whenever that method is called, the same object is always returned.
## How to Implement
1. Add a private static field to the class for storing the singleton instance.
1. Declare a public static creation method for getting the singleton instance.
1. Implement “lazy initialization” inside the static method. It should create a new object on its first call and put it into the static field. The method should always return that instance on all subsequent calls.
1. Make the constructor of the class private. The static method of the class will still be able to call the constructor, but not the other objects.
1. Go over the client code and replace all direct calls to the singletons constructor with calls to its static creation method.

View file

@ -0,0 +1,38 @@
class SingletonMeta(type):
"""
The Singleton class can be implemented in different ways in Python. Some
possible methods include: base class, decorator, metaclass. We will use the
metaclass because it is best suited for this purpose.
"""
_instances = {}
def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""
# ...
if __name__ == "__main__":
s1 = Singleton()
s2 = Singleton()
if id(s1) == id(s2):
print("Singleton works, both variables contain the same instance.")
else:
print("Singleton failed, variables contain different instances.")

View file

@ -0,0 +1,69 @@
from threading import Lock, Thread
class SingletonMeta(type):
"""
This is a thread-safe implementation of Singleton.
"""
_instances = {}
# We now have a lock object that will be used to synchronize threads during
# first access to the Singleton.
_lock = Lock()
def __call__(cls, *args, **kwargs):
"""
Possible changes to the value of the `__init__` argument do not affect
the returned instance.
"""
# Now, imagine that the program has just been launched. Since there's
# no Singleton instance yet, multiple threads can simultaneously pass
# the previous conditional and reach this point almost at the same time
# The first of them will acquire lock and will proceed further, while
# the rest will wait here.
with cls._lock:
# The first thread to acquire the lock, reaches this conditional,
# goes inside and creates the Singleton instance. Once it leaves
# the lock block, a thread that might have been waiting for the
# lock release may then enter this section. But since the Singleton
# field is already initialized the thread won't create a new object
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
value: str = None
"""
We'll use this property to prove that our Singleton really works.
"""
def __init__(self, value: str) -> None:
self.value = value
def some_business_logic(self):
"""
Finally, any singleton should define some business logic, which can be
executed on its instance.
"""
def test_singleton(value: str) -> None:
singleton = Singleton(value)
print(singleton.value)
if __name__ == "__main__":
print(
"If you see the same value, then singleton was reused (yay!)\n"
"If you see different values, "
"then 2 singletons were created (booo!!)\n\n"
"RESULT:\n"
)
process1 = Thread(target=test_singleton, args=("FOO",))
process2 = Thread(target=test_singleton, args=("BAR",))
process1.start()
process2.start()