What is Singleton Pattern
The Singleton is a creational design pattern that allows to create only a single instance of an object and to share it across the system. Singletons are created once during the runtime of the application in the global scope.
When to use it ?
- You can only have a single instance
- You need to manage the state of this instance
- You do not care about initialization of this instance at runtime
- You need to access it across your app
- Inherited classes doesn’t affect it
Advantages
- Save memory (system resources)
- Have central control over singleton instance access
- Avoids polluting the name space with global variables
- Is thread-safe (lazy loaded): the instance is created only when is needed
Examples
- jQuery $ that encapsulate all its functions in one namespace which can be used anywhere in the application.
- Configuration settings of an app to manage global application state.
- Logger components which reads and writes to a file. Multiple classes can operate in the same file at the same time from different threads, so having one centralized place for that purpose is a good singleton pattern choice.
- Client Memory Data storage like Flux Stores does (architectural pattern used by Facebook for building SPA).
- Shopping carts
Basic singleton
const ShoppingCart = {
addItem: function() {},
removeItem: function() {}
}
// prevents new properties from being added to the object
Object.freeze(ShoppingCart);
The object literals are the simplest singleton pattern.
Lazy singleton
The class has to be instantiated as a constructor if:- The object requires some private variables and private methods
- The instance is created only when needed, not on loading
ES5 syntax
Implement lazy singleton using closures, IIFE (Immediately Invoked Function Expression) and function constructor:var ShoppingCart = (function() {
// Singleton reference
var instance;
function init() {
// Private methods and variables
this.cartId = Math.random();
this.items = [];
this.addItem = function(item) {
this.items.push(item)
}
this.getItems = function() {
return this.items;
}
};
return function() {
if ( !instance ) {
instance = new init();
}
return instance;
};
})();
let cart1 = ShoppingCart();
let cart2 = ShoppingCart();
Object.freeze(cart1);
Object.freeze(cart2);
cart1.addItem('cup');
cart2.addItem('ipad')
console.log(cart1.getItems(), cart2.getItems(), cart1 === cart);
// ["cup", "ipad"] ["cup", "ipad"] true
ES6 Classes
class ShoppingCart {
constructor(name) {
this.name = name;
}
getItems() {}
//static method
static getInstance(name) {
if (!this.instance) {
this.instance = new ShoppingCart(name);
}
return this.instance;
}
}
let cart1 = ShoppingCart.getInstance('de');
let cart2 = ShoppingCart.getInstance('ro');
Object.freeze(cart1);
Object.freeze(cart2);
console.log(cart1 === cart2); // true
Better to check the instance in the constructor:
class ShoppingCart {
constructor(name = "") {
if (!!ShoppingCart.instance) {
return ShoppingCart.instance;
}
ShoppingCart.instance = this;
this.name = name;
return this;
}
getName() {
return this.name;
}
}
const cart1 = new ShoppingCart('de');
const cart2 = new ShoppingCart('ro');
Object.freeze(cart1);
Object.freeze(cart2);
console.log(cart1 === cart2); // true
ES6 Module
The best implementation is to use an instance of a class scoped to a module. This example shows how to inherit a singleton class also. Add the class in a separate file, shoppingCart.js:class ShoppingCart {
constructor(name = "") {
if (!!ShoppingCart.instance) {
return ShoppingCart.instance;
}
ShoppingCart.instance = this;
this.name = name;
return this;
}
getName() {
return this.name;
}
}
export default ShoppingCart;
Create a class that extends ShoppingCart in amazonCart.js:
import ShoppingCart from './shoppingCart.js';
class Amazon extends ShoppingCart {
vendorType() {
return 'FBA'
}
}
export default Amazon;
// app.js
import ShoppingCart from './shoppingCart.js';
import Amazon from './amazonCart.js';
//const cart1 = new ShoppingCart('de');
const cart2 = new Amazon('us');
//Object.freeze(cart1);
Object.freeze(cart2);
console.log("getName" in cart2); // true
console.log("vendorType" in cart2);
// true if we don't create the cart1
console.log(cart2.getName()); // us
Load the module in html:
Happy javascript “design” 🙂
Opinions