Blog

JavaScript Design Pattern – Singleton

 2,026 total views,  1 views today

Definition

Ensure a class has only one instance and provide a global point of access to it.
 

Frequency of use (in JavaScript):

●●●●● high

Motivation

It’s important for some classes (or equivalent types in non class based languages) to have exactly one instance.

Some examples for your kind perusal.

  • There should a single instance of URL router for your application (ideally)
  • Caching strategy design (ideally only one instance should be there)
  • Services Objects

How do we ensure that a class has only one instance and that the instance is easily accessible?

A global variable makes an object accessible, but it doesn’t keep you from instantiating multiple objects.

A better solution is to make the class itself responsible for keeping track of its sole instance.

The class can ensure that no other instance can be created (by intercepting requests to create new objects), and it can provide a way to access the instance. This is the Singleton pattern.

Points to Ponder

Singleton is not in a fundamental way inherently bad in the sense that anything in design or computing or technology can be good or bad (As you will hear and read a lot about bad things on singleton). The idea behind the design pattern series is you understand the core concepts, read other people’s opinion and then make a value judgement.

It’s not that if it didn’t worked for them, it may not work for you. Let’s get started. And also even if you don’t want to use the pattern, when you encounter them you know what you are dealing with rather than taking guess work.


Applicability

Use the Singleton pattern when

– There must be exactly one instance of a class, and it must be accessible to
clients from a well known access point.
– When the sole instance should be extensible by sub-classing, and clients
should be able to use an extended instance without modifying their code.

Structure

As you can observe by the above diagram a singleton class should have a public static method to create an instance (in this case the method name is instance(). Additionally it can have other methods.

Yet another conceptual diagram for more indepth understanding.

Conceptual Code (Approach 1) – Object Literal

The quick way to create Singleton or Singleton like object in JavaScript is using Object Literal.

var Singleton = {
  attribute1: true,
  attribute2: 10,
  method1: function() {
    console.log("This is method1");
  },

  method2: function(arg) {
  }

};

// The usage is quite simple as shown below.
Singleton.attribute = 20;
Singleton.method1();  // prints => This is method1

Conceptual Code (Approach 2) – Revealing Module Pattern

Let’s use the Revealing Module Pattern to build up a structure for an image gallery. More about RMP will be covered separately.

var ImageGallery = (function () {
  // Let's make sure no one can directly access our images
  let index = 0;
  let images = ["sun.jpg", "star.jpg", "moon.jpg"];  
  let paused = false;

  // We'll expose all these functions to the user
  function show () {
    console.log('Showing image...', images[index]);
  }

  function add (imageData) {
    images.push(imageData);
    console.log(`Image ${imageData} added.`);
  }

  function next () {
    index++;
    if (index >= images.length) {
      index = 0; // Reset index to beginning of array
    }
    console.log('Next Image is', images[index]);
  }

  
  function loadImages(imageArray) {
    images = imageArray;
    index = 0;
  }

  return {
    show: show,
    next: next,
    add: add,
    load: loadImages
  }
})(); // our IIFE function (surrounded with parens) is invoked here

Lets take a moment to look at how the client will use our ImageGallery code.

ImageGallery.show(); // prints => Showing image sun
ImageGallery.next(); // prints => Next image is star.jpg
ImageGallery.show();

// Reload the data (you can fetch from remote api as well)
ImageGallery.load(["apple","orange","pears"]);
ImageGallery.show();  // prints => Showing image apple

Conceptual Code (Approach 3) – Class + Symbol

There are various ways to create a singleton class/function in javascript. Do note that we will use the ES6+ class notation for all examples. This will help you carry your understanding to other languages like c#, java etc (though not exactly but enough to understand other language nuisances and adapt as needed).

Here we will make use of the Symbol feature in JavaScript.

NOTE: (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol)

The data type symbol is a primitive data type. The Symbol() function returns a value of type symbol, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class, but is incomplete as a constructor because it does not support the syntax “new Symbol()“.  

Every symbol value returned from Symbol() is unique.  A symbol value may be used as an identifier for object properties; this is the data type’s primary purpose, although other use-cases exist, such as enabling opaque data types, or serving as an implementation-supported unique identifier in general.

Conceptual Code -Singleton – Approach 1

// Used for hash key to store instance
const singleton = Symbol(); 

// The unique identifier for the only instance
const singletonIdentifier = Symbol(); 

class Singleton {
  constructor(enforcer) {
    if (enforcer !== singletonIdentifier) {
      throw new Error('Cannot construct singleton');
    }
  }

  // Getter method to return a new or stored instance
  static get instance() {
    if (!this[singleton]) {
      this[singleton] = new Singleton(singletonIdentifier);
    }

    return this[singleton];
  }

  singletonMethod() {
    return 'singletonMethod';
  }

  static staticMethod() {
    return 'staticMethod';
  }
}

Dry running/testing the code

function runSingleton() {
 
  // Both of the below instance point to same instance in memory
  const instance1 = Singleton.instance;
  const instance2 = Singleton.instance;

  // This will give you error
  //const instance3 = new Singleton();

  // Call instance method or /prototype method
  console.log(instance1.singletonMethod());

  // Static method
  console.log(Singleton.staticMethod());

  // Should return true as both instance are same
  console.log("instance1 == instance2 ? " + (instance1 === instance2));  
}

runSingleton();  // Let the action begin

Practical Example – Singleton – CacheManager

Let’s now take a look at a practical example of Singleton pattern in action by building a cache manager (key/value pair)

const instanceKey = Symbol();
const instanceKeyIdentifier = Symbol();

class CacheManager {
  constructor(enforcer) {
    this.items = {};
    this.count = 0;
    if (enforcer !== instanceKeyIdentifier) {
      throw new Error('Cannot construct cacheManagerKey');
    }
  }

  // instance getter
  static get instance() {
    if (!this[instanceKey]) {
      this[instanceKey] = new CacheManager(instanceKeyIdentifier);
    }
    return this[instanceKey];
  }

  // Add key/value pair
  add(key, value) {
    this.items[key] = value;
    this.count++;
  }

  // Get the value by key
  read(key) {
    return this.items[key];
  }

  eachObject(fn) {
    for (let [key, value] of Object.entries(this.items)) {
      fn({key, value});
    }
  }

  values(fn) {
    for (let [key, value] of Object.entries(this.items)) {
      fn(value);
    }
  }

  keys(fn) {
    for (let [key, value] of Object.entries(this.items)) {
      fn(key);
    }
  }

  delete(key) {
    delete this.items[key];
    this.count--;
  }
}

CacheManager Test Code

function runSingletonPractical() {
 
  const instance = CacheManager.instance;

  instance.add("key1", "value1");
  instance.add("key2", "value2");
  instance.add("key3", "value3");

  const instance2 = CacheManager.instance;
  console.log(instance.read("key2"), instance2.read("key2"));
  console.log("Count: ", instance2.count);

  instance.delete("key2");
  console.log("Count: ", instance2.count);

  // Test each method
  instance.eachObject(item => console.log(item));
  instance.values(value => console.log("value: ", value));
  instance.keys(key => console.log("key: ", key));

}

runSingletonPractical();

Limitations of the pattern (adapted from web, from my peer groups)

  • People think it’s hard to test (but depends on your goal and testing strategy and tools).
  • Singletons’ create hidden dependencies (well not really if the purpose of singletons are made clear withing the working group members)
  • Singletons breaks OCP and SRP principles of SOLID (well again not really, as patterns always works best in groups rather than isolation. So based on needs you can bring in other patterns to fix the issue).

Applications

The following applications/areas uses Singleton or similar pattern extensively.

  • Game Programming (The Game Environment is usually a Singleton class which includes scores, list of enemies etc.
  • Service or Factory Objects in typical web applications (They are usually mixed with Dependency Injection.
  • Caching (Singleton or an adaptation of the pattern)

Related Patterns and principles

This section lists down patterns that makes singleton effective.

  • Factory Method
  • Dependency Inversion Principle (from SOLID principles)

Source Code + Unit Testing

The source code + unit testing (mocha+chai) is available at https://github.com/rajeshpillai/designpatterns-code-js

A note

Please note, this is an early release of the post and will be updated more benefits, alternate approach ,cons and some solutions for fixing the cons.

Understand singleton pattern with practical examples

How useful was this post?

Click on a heart to rate it!

Average rating 4.8 / 5. Vote count: 5

No votes so far! Be the first to rate this post.

Leave a Reply