Contents
A key idea in software development is the singleton design pattern, which offers a straightforward method of guaranteeing a class has a single instance and a global access point. This pattern, which focuses on object generation methods, is classified as a creational design pattern. Although singletons provide benefits in some situations, it’s important to know the disadvantages before using them carelessly.
Core Principles of the Singleton Design Pattern
Single Instance: The Singleton class ensures that only one object is created. This is accomplished by turning the constructor private, which stops instances from being created directly by outside code.
Global Access Point: Other sections of the code can access the single instance through a static method provided by the Singleton class, which is commonly called getInstance().
Lazy Initialization: In certain implementations, the instance is not generated until the getInstance() method is called for the first time. This is an optional feature. If the Singleton is not needed for the entire application, this might help with performance optimization.
The Singleton design pattern Issue: When There Are Too Many of One
While the goal of Singletons is a single instance, other issues may occur:
Tight Coupling: When multiple code segments rely on the same instance, singletons establish tight dependencies between them. This may complicate code modifications and unit testing.
Global State Management: When a Singleton takes on the role of a central point of state, it might result in hidden dependencies and make it difficult to comprehend how the program works as a whole.
Limited Flexibility: A Singleton’s ability to add new features is frequently restricted because doing so changes the code that can be added in the future.
Testability Problems: It can be challenging to test singletons design pattern, particularly when eager initialization is used. The Singleton’s behavior may need to be mocked with more work.
Singleton Design Pattern: Real-Time Scenario
Consider yourself creating a shopping cart application. For the purpose of keeping track of the things that users add to their cart while browsing, you should have a central repository. The following difficulties could arise in the absence of a singleton pattern
Real-Time Scenario Problem Statement
Many Cart Situations: You may find yourself having multiple carts for the same user if you create a new Cart object for every user activity (adding or removing items). Data problems and an unsatisfactory user experience can arise from this inconsistency.
Global Access: You need to make sure that the same shopping cart data can be accessed and used by other sections of your application, such as adding products, viewing the cart, and checking out. Tight coupling can result from passing the cart object via each function call, which can get tedious.
How the Singleton Pattern Solves These Problems
To guarantee that there is only one instance of the Cart class and to offer a global access point to it, the Singleton Design Pattern is used:
Single Instance: You can ensure that there is only one Cart object for every part of the application’s operation by making the Cart class a Singleton. By doing this, it is ensured that all user interactions and data changes are made to the same cart.
Global Access: Any portion of your code can retrieve the single instance of the Cart class and utilise its functionality to the Singleton’s static method, getInstance(). As a result, passing the cart object around directly is no longer necessary.
Solutions and Alternatives
Before resorting to Singletons, consider these alternative approaches:
Dependency Injection: By explicitly giving dependencies as inputs to functions or constructors, this idea enables loose coupling. It encourages testability and modularity.
Static Methods: Using static methods inside of a class might accomplish comparable functionality without the disadvantages of a singleton for straightforward utility tasks that don’t require state management.
Interface Implementation: Specify an interface that has the necessary features. The interface can be implemented by several concrete classes, providing flexibility and possibly enabling mocking during testing.
The Singleton Design Pattern: Code Example
Here’s a basic example of a Singleton Design Pattern implementation in PHP using lazy initialization
class DatabaseConnection {
private static ?DatabaseConnection $instance = null;
private PDO $connection;
private function __construct(string $host, string $dbname, string $user, string $password) {
$this->connection = new PDO("mysql:host=$host;dbname=$dbname", $user, $password);
}
public static function getInstance(string $host, string $dbname, string $user, string $password): DatabaseConnection {
if (self::$instance === null) {
self::$instance = new self($host, $dbname, $user, $password);
}
return self::$instance;
}
public function getConnection(): PDO {
return $this->connection;
}
}
// Usage
$db = DatabaseConnection::getInstance("localhost", "mydatabase", "username", "password");
$connection = $db->getConnection();
// This will still return the same instance
$db2 = DatabaseConnection::getInstance("localhost", "mydatabase", "username", "password");
$connection2 = $db2->getConnection();
// You can check if it's the same instance
if ($connection === $connection2) {
echo "It's the same database connection!";
}
Implementation Approaches
There are two primary ways to implement the Singleton Design Pattern
Eager Initialization
When a class loads, an instance of the Singleton class is created. This ensures instant availability, but if the Singleton is not used throughout the programme, it may result in pointless initialization.
Lazy Initialization
Only when the getInstance() function is called for the first time is the instance generated. Performance is increased with this method, but thread safety must be handled with extra logic when several threads may attempt to access the instance at once.