In this article you are going to learn how to implement the Singleton design pattern, and why and when to use this pattern in your application. As the name "Singleton" suggests, this method allows us to create one and only one object of a class.
Let's see what we have on Wikipedia about this design pattern:
The singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
As mentioned in the above definition, when we want to make sure that one and only one object needs to be created for any class, then we should implement the Singleton pattern for that class.
You might ask why we should implement such a class which allows us to create only one object of it. I would say there are many use cases where we can apply this design pattern. These include: configuration class, session class, database class, and many more.
I will take the example of a database class for this article. First we will see what the problem can be if a Singleton pattern is not implemented for such a class.
The Problem
Imagine a very simple database connection class which creates a connection with the database once we create an object of that class.
class database { private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null; public function __construct($dbDetails = array()) { $this->dbName = $dbDetails['db_name']; $this->dbHost = $dbDetails['db_host']; $this->dbUser = $dbDetails['db_user']; $this->dbPass = $dbDetails['db_pass']; $this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass); } }
In the above code example, you can see that it will make a connection to the database every time you create an object of this class. So if a developer has created an object of this class in multiple places, imagine the number of (identical) database connections it will create with the database server.
So unknowingly the developer is making mistakes which lead to a huge impact on the speed of the database and the application server. Let's see the same thing via creating a different object of that class.
$dbDetails = array( 'db_name' => 'designpatterns', 'db_host' => 'localhost', 'db_user' => 'root', 'db_pass' => 'mysqldba' ); $db1 = new database($dbDetails); var_dump($db1); $db2 = new database($dbDetails); var_dump($db2); $db3 = new database($dbDetails); var_dump($db3); $db4 = new database($dbDetails); var_dump($db4); // Output object(database)[1] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[2] object(database)[3] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[4] object(database)[5] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[6] object(database)[7] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[8]
If you see the output of the above code and output, you can see that each object has a new resource ID assigned, so all objects are completely new reference, hence it allocates separate memory as well. So unknowingly our application will occupy resources which are really not required.
The Solution
It's not in our control how developers use our base framework. It is in our control after the code review process takes place, but during development we cannot stand behind them all the time.
To overcome such a situation, we should make our base class in such a way that it is not able to create multiple objects of a class; instead it will give an already created object if any. This is the case where we should consider developing a Singleton pattern for our base classes.
While implementing this pattern, our aim will be to allow the creation of an object of a class one and only one time. Allow me to add the class code below and then we will go through each portion of this class.
class database { private $dbName = null, $dbHost = null, $dbPass = null, $dbUser = null; private static $instance = null; private function __construct($dbDetails = array()) { // Please note that this is Private Constructor $this->dbName = $dbDetails['db_name']; $this->dbHost = $dbDetails['db_host']; $this->dbUser = $dbDetails['db_user']; $this->dbPass = $dbDetails['db_pass']; // Your Code here to connect to database // $this->dbh = new PDO('mysql:host='.$this->dbHost.';dbname='.$this->dbName, $this->dbUser, $this->dbPass); } public static function connect($dbDetails = array()) { // Check if instance is already exists if(self::$instance == null) { self::$instance = new database($dbDetails); } return self::$instance; } private function __clone() { // Stopping Clonning of Object } private function __wakeup() { // Stopping unserialize of object } }
There is little indication which says the above class is a Singleton class. The very first thing is a private constructor, which prevents object creation using the new keyword. Another indication is one static member variable which holds the reference to an already created object.
$dbDetails = array( 'db_name' => 'designpatterns', 'db_host' => 'localhost', 'db_user' => 'root', 'db_pass' => 'mysqldba' ); $db1 = database::connect($dbDetails); var_dump($db1); $db2 = database::connect($dbDetails); var_dump($db2); $db3 = database::connect($dbDetails); var_dump($db3); $db4 = database::connect($dbDetails); var_dump($db4); // Output object(database)[1] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[2] object(database)[1] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[2] object(database)[1] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[2] object(database)[1] private 'dbName' => string 'designpatterns' (length=14) private 'dbHost' => string 'localhost' (length=9) private 'dbPass' => string 'mysqldba' (length=8) private 'dbUser' => string 'root' (length=4) public 'dbh' => object(PDO)[2]
If you compare the output of both sections then you will see, in the output of the Singleton pattern, the resource ID for the object is the same for all different objects. But that is not the case when the design pattern is not used.
Singleton as an Anti-Pattern
This design pattern is also called an Anti-Pattern for various reasons, which I will mention below:
- It violates the single responsibility principle because of its quality of controlling its own creation and lifecycle.
- It introduces global state to your application. I would say global state is very bad because any code can change its value. So at the time of debugging it's really hard to find which portion of the code has made the current stage of global variable.
- Singleton is generally a bad idea if you are doing unit testing, and it's generally a bad idea not to perform unit testing.
Conclusion
I have tried my best to explain the Singleton design pattern, which is widely discussed on the internet. I hope you find this article helpful. We have covered both aspects of this pattern which are as a Design Pattern and as an Anti-Pattern.
Please post your comments, suggestions and/or questions below, and I will post my response as soon as possible. You can also reach me on Twitter @XpertDevelopers or email me straight away.
Comments