Design patterns play an important part in software development and they do help us to design better software and write better code.
Back in 1994, a book was authored by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides that discusses 23 desgin patterns, titled Design Patterns: Elements of Resuable Object-Oriented Software. You may have heard of this book or the authors as Gang of Four (GoF).
The book and the outlined design patterns in them formtulate a solid base for today's software development and those patterns introduced and explained in 1994 are still applicable today and can be used in modern day applications.
Design patterns can be categorised into the following categories based on what and how they aim to achieve
The singleton pattern is a creational software design pattern. This particular pattern is ensuring that a class has only one instance (an instance is a unique copy of a class) and via the Singleton we are provided a global point to accessing the instance.
There are some scenarios when it's as useful as important to have only one instance of a class. Examples would include situations when we are working with a shared resource - having a single entry point to a shared resource means one instance of a class, which also means that the application code is less prone to bugs.
The singleton pattern could be implemented in any programming language, including vanilla JavaScript as well but I thought it'd be interesting to see how a TypeScript implementation would look like.
It goes without saying that should you want to get the JavaScript implementation, just transpile the TypeScript code to JavaScript, either to ES2015 or to ES5.
Let's take a look at a practical example of having a Singleton in TypeScript. Remember that the purpose of this pattern is that we can maintain a single entry point to a shared resource - in other words, we want to only allow one instantiation of a given class.
Let's imagine that we want to have a class that keeps track of temperature. In this system we want to have one entry point from which we can alter the temperature. This is how the Singleton class would look like:
class Singleton {
private static instance: Singleton;
private _temperature: number;
private constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
Singleton.instance._temperature = 0;
}
return Singleton.instance;
}
get temperature(): number {
return this._temperature;
}
set temperature(score) {
this._temperature = score;
}
increaseTemperature(): number {
return (this._temperature += 1);
}
decreaseTemperature(): number {
return (this._temperature -= 1);
}
}
A few things to note here:
new
keyword.Let's see what happens when we try to instansiate this class using the new
keyword:
const myInstance = new Singleton(); // Constructor of class 'Singleton' is private and only accessible within the class declaration.
We do in fact get an error. So the only way to instansiate it is by calling the getInstance()
method:
const myInstance = Singleton.getInstance();
console.log(myInstance.temperature); // 0
And as per the example above we can also access the temperature property. Let's now set the temperature value and increase/decrase it a few times:
console.log((myInstance.temperature = 25)); // 25
console.log(myInstance.increaseTemperature()); // 26
console.log(myInstance.increaseTemperature()); // 27
console.log(myInstance.decreaseTemperature()); // 26
Think about this for a moment. If at this point we'd be able to create a new instance of our class, we would be able to overwrite the temperature readings.
The only way we can "create" an instance is by calling the getInstance()
method but that will realise that we already have an instance so it will return that existing one for us. And this is why we can access the temperature
property and still get the last set value:
const myInstance2 = Singleton.getInstance();
console.log(myInstance2.temperature); // 26
And in fact if we compare the two instances we'll get true
as they are exactly the same:
console.log(myInstance === myInstance2); // true