Design Patterns: Singleton
• • 7 min readThere are 23 classic design patterns, which are described in the original book, Design Patterns: Elements of Reusable Object-Oriented Software
. These patterns provide solutions to particular problems, often repeated in the software development.
In this article, I am going to describe the how the Singleton Pattern; and how and when it should be applied.
Singleton Pattern: Basic Idea
In software engineering, the singleton pattern is a software design pattern that restricts the instantiation of a class to one "single" instance. This is useful when exactly one object is needed to coordinate actions across the system. The term comes from the mathematical concept of a singleton. — Wikipedia
Ensure a class only has one instance, and provide a global point of access to it. - Design Patterns: Elements of Reusable Object-Oriented Software
The main feature of this pattern is that only a single object is instantiated in each class. Also, a single entry point for the class is created, usually using an accessor method such as getInstance.
The UML's diagram of this pattern is the following one:
The Singleton
class is a single class that has an own attribute called uniqueInstance
that stores an instance of the Singleton class. The class constructor is private, and you can only access the instance through an accessor method, which could be getInstance
.
The accessor method is responsible for returning the single instance in case it exists or instantiating it in case it has not yet been instantiated.
Singleton Pattern: When To Use
The Singleton Pattern should be used when:
- There must be a single instance of a class, and this class must be accessible by clients from an access point known to them.
- The singleton class can be extended by inheritance, and clients must be able to use extended classes without making any changes to it.
Singleton Pattern: Advantages
The Singleton Pattern has several advantages, summarised in the following points:
- Have a strict control over how and when clients access to singleton instance. So, you have a controlled access because the singleton class encapsulates its instance.
- When you need to restrict the number of instances that we create from a class in order to save the system resources.
- The singleton pattern is an improvement over global variables because it avoids polluting the name space with global variables which only store the singleton instances.
- The code is more easier to use, understand and test since the singleton simplify the code.
Singleton pattern - Example 1: A client want to connect to database
I will now show you how you can implement this pattern using JavaScript/TypeScript. In our case, I have made up a problem in which there is a class named DatabaseConnection
which defines two attributes: configuration
and getUniqueIdentificator
. This class is the connection to our database. The DatabaseConnection is used by several clients (client1
and client2
). The following UML diagram shows the scenario that I have just described.
The client
code associate is the following ones:
import { DatabaseConnection } from './database-connection';
export class Client1 {
private databaseConnection: DatabaseConnection;
constructor() {
console.log('Client1...');
console.log('DatabaseConnection created...');
this.databaseConnection = new DatabaseConnection();
}
getUniqueIdentificatorConnection(): number {
return this.databaseConnection.getUniqueIdentificator;
}
}
import { DatabaseConnection } from './database-connection';
export class Client2 {
private databaseConnection: DatabaseConnection;
constructor() {
console.log('Client2...');
console.log('DatabaseConnection created...');
this.databaseConnection = new DatabaseConnection();
}
getUniqueIdentificatorConnection(): number {
return this.databaseConnection.getUniqueIdentificator;
}
}
Each client creates a new connection to the database and requests the unique identifier of each of the connections. One of the main consequences of this architecture is that more resources are being used than necessary.
The DatabaseConnection
class is the following one:
interface ConnectionConfiguration {
host: string;
user: string;
pass: string;
name: string;
}
export class DatabaseConnection {
private configuration: ConnectionConfiguration = {
host: 'localhost',
user: 'db user-name',
pass: 'db password',
name: 'db name'
};
getUniqueIdentificator = Math.round(Math.random() * 10000);
constructor() {}
}
In the previous class it can be seen that only a private attribute is available with the configuration to the database, and the unique identifier is accessed using the public attribute.
Finally, the sample code for this interaction is as follows:
import { Client1 } from './client1';
import { Client2 } from './client2';
const client1 = new Client1();
console.log(
'DatabaseConnection ID:',
client1.getUniqueIdentificatorConnection()
);
const client2 = new Client2();
console.log(
'DatabaseConnection ID:',
client2.getUniqueIdentificatorConnection()
);
The result obtained is shown in the following image:
As you can see, each instance of the database has a unique identifier since they are different instances, when the task they perform is exactly the same. In fact, the most smartest would have been to have a single instance to make the connections.
The solution is to use a singleton pattern which only creates one instance of the class. I.e, the new UML diagram using the singleton pattern is shown below:
The code associate to the DatabaseConnection
is the following one:
interface ConnectionConfiguration {
host: string;
user: string;
pass: string;
name: string;
}
export class DatabaseConnection {
private static instance: DatabaseConnection;
private configuration: ConnectionConfiguration = {
host: 'localhost',
user: 'db user-name',
pass: 'db password',
name: 'db name'
};
getUniqueIdentificator = Math.round(Math.random() * 10000);
private constructor() {}
public static getDatabaseConnection(): DatabaseConnection {
if (!DatabaseConnection.instance) {
this.instance = new DatabaseConnection();
}
return DatabaseConnection.instance;
}
}
The only access point to the instance is using the getDatabaseConnection
static method, which will create a new instance in case that the instance does not exist or will get it. In this way, clients are slightly modified to use this instance instead of creating their own instance:
import { DatabaseConnection } from './database-connection';
export class Client1 {
private databaseConnection: DatabaseConnection;
constructor() {
console.log('Client1...');
console.log('DatabaseConnection created...');
this.databaseConnection = DatabaseConnection.getDatabaseConnection();
}
getUniqueIdentificatorConnection(): number {
return this.databaseConnection.getUniqueIdentificator;
}
}
import { DatabaseConnection } from './database-connection';
export class Client2 {
private databaseConnection: DatabaseConnection;
constructor() {
console.log('Client2...');
console.log('DatabaseConnection created...');
this.databaseConnection = DatabaseConnection.getDatabaseConnection();
}
getUniqueIdentificatorConnection(): number {
return this.databaseConnection.getUniqueIdentificator;
}
}
The result after these modifications in the execution of the program is the shown in the following image:
I have created two npm scripts that run the two examples shown here after applying the Singleton pattern.
npm run example1-problem
npm run example1-singleton-solution1
Singleton pattern - Example 2: Batman and Spiderman are singleton!
Another interesting example which is resolved using singleton pattern is when there are several class which must to be singleton. For example, a set of heroes as Spiderman and Batman are singleton. In the following UML's diagram you can see this situation:
The code associate to the clients is the following ones:
import { Batman } from './batman';
import { Spiderman } from './spiderman';
export class Client1 {
private batman: Batman;
private spiderman: Spiderman;
constructor() {
console.log('Client1...');
console.log('Calling to Heroes...');
this.batman = Batman.getHero();
this.spiderman = Spiderman.getHero();
}
showHero(hero: string): string {
return this[hero].toString();
}
}
export class Client2 {
private batman: Batman;
private spiderman: Spiderman;
constructor() {
console.log('Client2...');
console.log('Calling to Heroes...');
this.batman = Batman.getHero();
this.spiderman = Spiderman.getHero();
}
showHero(hero: string): string {
return this[hero].toString();
}
}
Next, we will create our heroes, which will be unique. First of all we will define a common interface of the information that each of them will contain:
export interface Hero {
name: string;
city: string;
}
Our heroes are unique but share certain attributes and methods, for this we have defined a parent class called HeroBase
that contains the common features of both Spiderman
and Batman
. This class is the following one:
import { Hero } from './hero.interface';
export abstract class HeroBase {
protected hero: Hero;
protected _getUniqueIdentificator = Math.round(Math.random() * 10000);
constructor(hero: Hero) {
this.hero = hero;
}
public toString() {
return `${this.hero.name} - ${this.hero.city} - ${
this._getUniqueIdentificator
}`;
}
public getUniqueIdentificator() {
return this._getUniqueIdentificator;
}
}
Both Batman and Spiderman have implemented the Singleton pattern in their construction and store a reference to the only object of each class (our hero!). These classes are following ones:
import { HeroBase } from './hero-base.class';
export class Batman extends HeroBase {
private static instance: Batman;
private constructor() {
super({
name: 'Bruce Wayne',
city: 'Gotham City'
});
}
public static getHero(): Batman {
if (!Batman.instance) {
this.instance = new Batman();
}
return Batman.instance;
}
}
import { HeroBase } from './hero-base.class';
export class Spiderman extends HeroBase {
private static instance: Spiderman;
private constructor() {
super({
name: 'Peter Parker',
city: 'New City'
});
}
public static getHero(): Spiderman {
if (!Spiderman.instance) {
this.instance = new Spiderman();
}
return Spiderman.instance;
}
}
Finally, the sample code for this interaction is as follows:
import { Client1 } from './client1';
import { Client2 } from './client2';
const client1 = new Client1();
console.log(Batman: + client1.showHero('batman') + "\n Spiderman:" + client1.showHero('spiderman'));
const client2 = new Client2();
console.log(Batman: + client1.showHero('batman') + "\n Spiderman:" + client1.showHero('spiderman'));
The result obtained is shown in the following image:
I have created a npm scripts that run the example shown here after applying the Singleton pattern.
npm run example2-singleton-solution1
Conclusion
The singleton pattern can avoid complexity in your projects because you will have strict control of the instantiation of a class in a single point well known by the clients. Furthermore, it is a pattern that saves system resources because instead of instantiating a set of classes that perform the same task, a single instance of that class will be used. However, this pattern has a very bad reputation, even coming to be considered an anti-pattern because this pattern is really creating global variables that can be accessed and changed from anywhere in the code.
The most important thing has not implement the pattern as I have shown you, but to be able to recognise the problem which this specific pattern can resolve, and when you may or may not implement said pattern. This is crucial, since implementation will vary depending on the programming language you use.
More more more...
- Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, & Vlissides, Addison Wesley, 1995.
- The Singleton Pattern — Wikipedia.
- https://www.dofactory.com/javascript/singleton-design-pattern
- https://github.com/sohamkamani/javascript-design-patterns-for-humans#-singleton
- The GitHub branch of this post is https://github.com/Caballerog/blog/tree/master/singleton-pattern