JavaScript Singleton Design Pattern Implementation

This article introduces 2 categories 5 implementation in total of singleton in JavaScript.

This article is standing on the shoulder of the respectful by largely referencing to JavaScript Design Patterns: The Singleton and attached with my own understand and explanation.

Singleton Design Pattern

According to Wikipedia, the Singleton Pattern is as following:

In software engineering, the singleton pattern is a software 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. The concept is sometimes generalized to systems that operate more efficiently when only one object exists, or that restrict the instantiation to a certain number of objects. The term comes from the mathematical concept of a singleton.

JavaScript and Singleton

According to the above description, singleton pattern is related to class. However, until ES2015, JavaScript has no concept as class. And ES2015 class is not real class in the scope of OOP.

But if singleton pattern is considered to be only one working instance of same type, then JavaScript also has something to do with singleton pattern.

And in the following content of this article, singleton is by largely means only one instance of same type which maybe reference by many callers.

And by the way some other description of singleton includes:

Usually, the goal of single is to manage global application state. A singleton should be immutable by the callers (consuming code) in the aspect of API (but not in the aspect of state).

JavaScript Singleton Implementations

There are 2 main categories to implement singleton in JavaScript, of which one (Category A) is export a single already initialized instance and used as single instead of real singleton class template and the other (Category B) is to export a real singleton factory which always returns the same instance each time when call to initial an instance.

Although only the Category B is strictly the singleton pattern implementation when the singleton pattern is considered under the concept of OOP, the Category A single instance with public methods to alter its state is still commonly consider to be singleton implementation by developers around the world.

And the Category A implementation requires all the initial singleton state pre-defined while the Category B implementation leaves the space to supply some initial state at postponed initialization.

Category A - The ES5 Method - Closure + IIFE

A UserStore singleton implementation in JavaScript ES5 standardize specification may as the following.

var UserStore = (function(){
  var _data = [];

  function add(item){
    _data.push(item);
  }

  function get(id){
    return _data.find((d) => {
      return d.id === id;
    });
  }

  return {
    add: add,
    get: get
  };
}());

The IIFE (function(){}()) is corresponding to singleton class constructor call with new keyword, the JavaScript closures feature (which hides the variable _data (by the way, the naming pattern starts with _ is also a shining trick although it is not necessary here) under the hood) is corresponding to class encapsulation feature and the returned literal object { add: add, get: get } is corresponding to class public API exposure.

The usage of above singleton implementation is possible just as following by directly using it.

var one = { id: 1, value: 'one' };

UserStore.add(one);

var oneBack = UserStore.get(1);

Category B - The ES5 Method - Closure + IIFE

var UserStore = (function () {
    var instance;
    var _data = [];

    function add(item){
      _data.push(item);
    }

    function get(id){
      return _data.find((d) => {
        return d.id === id;
      });
    }

    function createInstance() {
        var object = {
            add: add,
            get: get
        }; // new Object("the only instance");

        return object;
    }

    return {
        getInstance: function () {
            if (!instance) {
                instance = createInstance();
            }

            return instance;
        }
    };
})();

The difference between this implementation and the above is this one returns a factory instance to crate singleton instance and with a mechanism to caching the singleton instant in one closure accessible IIFE scope variable (which is called instance). In this way, the initialization of singleton instant is postponed which gives way to support initial data later than the declaration.

The usage could be as following.

var userStore1 = UserStore.getInstance();
var userStore2 = UserStore.getInstance();

alert("Same instance? " + (userStore1 === userStore2));

// and then the same as above

Category A - The ES2015 Method - Module + export

const _data = [];

const UserStore = {
  add: item => _data.push(item),
  get: id => _data.find(d => d.id === id)
}

Object.freeze(UserStore);
export default UserStore;

When storing the above code snippet inside a single script file as module, the class encapsulation feature is right just implemented since ES2015 module hides non exported variables by definition. And the export statement is corresponding to class public API exposure.

And with keyword const and enhanced object method Object.freeze(), the singleton UserStore cannot be modified by the callers.

The arrow function here is not necessary for singleton implementation but indeed makes the implementation more concise. (And so does the enhanced object literal in ES2015.)

The usage of above singleton implementation is possible as following.

import UserStore from './UserStore';

var one = { id: 1, value: 'one' };

UserStore.add(one);

var oneBack = UserStore.get(1);

Category A - The ES2015 Method - Module + Class + export

class UserStore {
  constructor(){
    this._data = [];
  }

  add(item){
    this._data.push(item);
  }

  get(id){
    return this._data.find(d => d.id === id);
  }
}

const instance = new UserStore();
Object.freeze(instance);

export default instance;

The above implementation takes advantage of ES2015 class statement, which provide a more similar look to OOP singleton implementation. However, it still provide an already initialized instance with all current state pre-defined.

Since the class of ES2015 is just syntax sugar, this method doesn't really introduce new essential ideas comparing with enhanced object literal implementation above. The usage is also the same as above.

Category B - The ES2015 Method - Module + Class + export

class UserStore {
  constructor(){
    if(!UserStore.instance){
      this._data = [];
      UserStore.instance = this;
    }

    return UserStore.instance;
  }

  add(item){
    this._data.push(item);
  }

  get(id){
    return this._data.find(d => d.id === id);
  }
}

export default UserStore;

Although class in ES2015 is just syntax sugar for JavaScript Object, the above implementation using ES2015 class resembled a real OOP class implementation by always return the pretended static attribute saved instance (if any) or initialize and save to pretended static attribute before return the newly create instance each time the constructor is called.

References


* cached version, generated at 2018-12-06 05:06:26 UTC.

Subscribe by RSS