Slide 1: Introduction to the Singleton Pattern
The Singleton pattern ensures a class has only one instance and provides a global point of access to it. This pattern is useful when exactly one object is needed to coordinate actions across the system.
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # Output: TrueSlide 2: The Need for Thread Safety
In multi-threaded environments, the basic Singleton implementation can fail if multiple threads attempt to create the instance simultaneously. This can result in multiple instances being created, violating the Singleton principle.
import threading
def create_singleton():
s = Singleton()
print(f"Instance created by thread {threading.current_thread().name}")
# This might create multiple instances in a multi-threaded environment
threads = [threading.Thread(target=create_singleton) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()Slide 3: Double-Checked Locking Pattern
The double-checked locking pattern is an attempt to reduce the overhead of acquiring a lock by first testing the locking criterion without actually acquiring the lock. However, this pattern can be problematic in some programming languages due to memory model issues.
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instanceSlide 4: Thread-Safe Singleton Using Metaclass
A metaclass can be used to implement a thread-safe Singleton pattern. This approach ensures that the Singleton behavior is inherent to the class itself, rather than relying on the implementation of new.
import threading
class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Singleton(metaclass=SingletonMeta):
passSlide 5: Lazy Initialization in Thread-Safe Singleton
Lazy initialization defers the creation of the Singleton instance until it's first accessed. This can be beneficial for resource management, especially when the Singleton is resource-intensive to create.
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.initialize()
return cls._instance
def initialize(self):
# Perform any resource-intensive initialization here
passSlide 6: Borg Pattern: A Singleton Variant
The Borg pattern (also known as the Monostate pattern) is an alternative to the Singleton. It allows multiple instances of a class to share the same state, achieving a similar effect to Singleton but with a different approach.
class Borg:
_shared_state = {}
def __init__(self):
self.__dict__ = self._shared_state
class SingletonBorg(Borg):
def __init__(self):
super().__init__()
if not hasattr(self, 'instance'):
self.instance = 'Borg Singleton'
def __str__(self):
return self.instance
# Usage
b1 = SingletonBorg()
b2 = SingletonBorg()
print(b1 is b2) # False
print(b1.instance, b2.instance) # Same valueSlide 7: Thread-Safe Singleton Using Module-Level Variables
In Python, module-level variables are singleton by nature. This approach leverages Python's module import mechanism to create a thread-safe Singleton.
# In file singleton.py
import threading
class Singleton:
def __init__(self):
self.value = None
def set_value(self, value):
self.value = value
_instance = None
_lock = threading.Lock()
def get_instance():
global _instance
if _instance is None:
with _lock:
if _instance is None:
_instance = Singleton()
return _instance
# Usage in another file
from singleton import get_instance
instance1 = get_instance()
instance2 = get_instance()
print(instance1 is instance2) # TrueSlide 8: Singleton with Custom Exception
Adding custom exceptions to your Singleton implementation can help in debugging and maintaining the code. This example demonstrates how to raise an exception when attempting to create multiple instances.
class SingletonException(Exception):
pass
class Singleton:
_instance = None
def __new__(cls):
if cls._instance is not None:
raise SingletonException("Singleton instance already exists")
cls._instance = super().__new__(cls)
return cls._instance
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
# Usage
s1 = Singleton.get_instance()
s2 = Singleton.get_instance()
print(s1 is s2) # True
Singleton() # Raises SingletonExceptionSlide 9: Singleton with init Method
When implementing a Singleton, it's important to consider how the init method behaves. This example shows how to ensure that initialization occurs only once, even if the Singleton is accessed multiple times.
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
_initialized = False
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self):
if not self._initialized:
with self._lock:
if not self._initialized:
self.some_attribute = "Initialized only once"
self._initialized = True
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1.some_attribute) # "Initialized only once"
print(s2.some_attribute) # "Initialized only once"
print(s1 is s2) # TrueSlide 10: Singleton with Parameters
Sometimes, you might need to pass parameters when creating a Singleton instance. This example demonstrates how to implement a Singleton that accepts parameters during initialization.
import threading
class Singleton:
_instance = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, value=None):
if not self._initialized:
self.value = value
self._initialized = True
# Usage
s1 = Singleton("First")
s2 = Singleton("Second")
print(s1.value) # "First"
print(s2.value) # "First"
print(s1 is s2) # TrueSlide 11: Testing Thread-Safe Singleton
Testing is crucial to ensure that your Singleton implementation is truly thread-safe. This example demonstrates a simple test case using Python's threading module.
import threading
import unittest
class TestSingleton(unittest.TestCase):
def test_thread_safety(self):
instances = []
def create_instance():
instances.append(Singleton())
threads = [threading.Thread(target=create_instance) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(len(set(instances)), 1)
if __name__ == '__main__':
unittest.main()Slide 12: Singleton Pattern in Real-World Scenarios
Singletons are often used for managing shared resources or coordinating actions across a system. This example demonstrates a simple logger implementation using the Singleton pattern.
import threading
class Logger:
_instance = None
_lock = threading.Lock()
def __new__(cls):
if cls._instance is None:
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.log_file = open("application.log", "w")
return cls._instance
def log(self, message):
with self._lock:
self.log_file.write(f"{message}\n")
self.log_file.flush()
# Usage
logger1 = Logger()
logger2 = Logger()
logger1.log("Log message from logger1")
logger2.log("Log message from logger2")
# Both messages are written to the same fileSlide 13: Singleton vs Dependency Injection
While Singletons can be useful, they can also make code harder to test and maintain. Dependency Injection is an alternative that can provide similar benefits with more flexibility. This example compares the two approaches.
# Singleton approach
class DatabaseConnection:
_instance = None
@classmethod
def get_instance(cls):
if cls._instance is None:
cls._instance = cls()
return cls._instance
def query(self, sql):
# Execute query
pass
# Dependency Injection approach
class DatabaseConnection:
def query(self, sql):
# Execute query
pass
class Service:
def __init__(self, db_connection):
self.db = db_connection
def do_something(self):
self.db.query("SELECT * FROM table")
# Usage with DI
db = DatabaseConnection()
service = Service(db)
service.do_something()Slide 14: Additional Resources
For further exploration of the Singleton pattern and its implementations in Python, consider the following resources:
- "Design Patterns: Elements of Reusable Object-Oriented Software" by Gamma et al. - The original book introducing design patterns.
- "Python Design Patterns" by Chetan Giridhar - A comprehensive guide to design patterns in Python.
- ArXiv.org paper: "A Survey of Object Oriented Design Patterns" by Prashant Jamwal - An overview of various design patterns, including Singleton. Reference: arXiv:2004.09159 [cs.SE]
- Python official documentation on threading: https://docs.python.org/3/library/threading.html
These resources provide deeper insights into the Singleton pattern and its applications in software design.