javascript-design-patterns-singleton-and-modules

JavaScript Design Patterns: Singleton and Modules

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:
<script type="module" src="app.js"></script>
Happy javascript “design” 🙂
email-newsletter

Dev'Letter

Professional opinion about Web Development Techniques, Tools & Productivity.
Only once a month!

Any suggestion on what to write next time ? Submit now

Opinions

avatar
550
  Subscribe  
Notify of

Related Posts