Introduction
Design patterns are reusable solutions that address common software development problems. These patterns help developers to write clean, efficient and maintainable code. With such patterns, developers don’t have to start project solutions from scratch.
Singleton pattern is one of the most commonly used patterns in software development. In this article, we will discuss the Singleton pattern in the context of JavaScript.
JavaScript Design Patterns
JavaScript is a highly dynamic language that supports a wide range of programming paradigms, including object-oriented programming (OOP). OOP is a software development methodology that revolves around the concept of objects. A design pattern is a reusable solution that solves a common problem. Design patterns are useful in software development because they help us write code that is modular, efficient and maintainable.
Classes in JavaScript
JavaScript is a prototype-based language, which means that there are no classes in the traditional sense. However, with the release of ECMAScript 6 (ES6), JavaScript introduced a new syntax for defining classes. This syntax is similar to that of other object-oriented languages such as Java and C++. The class syntax in JavaScript is syntactic sugar that simplifies the process of creating objects with constructors and methods.
Implementing Singleton Patterns in JavaScript
A Singleton is a design pattern that restricts the instantiation of a class to a single instance. This means that only one instance of the class can be created, and that instance can be accessed globally. The Singleton pattern is useful in scenarios where we want to limit the number of instances of a class, for example, in a configuration object or a database connection.
To implement a Singleton pattern in JavaScript, we can use the module pattern. The module pattern is a design pattern that encapsulates private data and provides a public interface for accessing that data. In the context of the Singleton pattern, we can use the module pattern to create a single instance of a class.
Here's an example of implementing a Singleton pattern in JavaScript:
const Singleton = (function () {
let instance;
function createInstance() {
const object = new Object({ name: "Singleton Object" });
return object;
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
const singleton1 = Singleton.getInstance();
const singleton2 = Singleton.getInstance();
console.log(singleton1 === singleton2);
Output
true
In the example above, we define a Singleton object using the module pattern. The Singleton object has a private variable called instance
that holds the single instance of the object. We also define a private function called createInstance
that creates a new object.
The Singleton object has a public method called getInstance
that returns the single instance of the object. The getInstance
method checks if the instance
variable is null, and if it is, it calls the createInstance
function to create a new object.
Finally, we create two instances of the Singleton object using the getInstance
method. We then compare the two instances using the ===
operator, which returns true because both variables point to the same instance of the Singleton object.
Here's another example of the Singleton pattern in JavaScript:
const Singleton = (() => {
let instance = null;
class Singleton {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
logMessage() {
console.log("This is a message from the Singleton instance");
}
}
return Singleton;
})();
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2);
instance1.logMessage();
instance2.logMessage();
Output
true
This is a message from the Singleton instance
This is a message from the Singleton instance
In this example, we use an IIFE (Immediately Invoked Function Expression) to create a Singleton class. The instance
variable is used to store the single instance of the class. The constructor
method of the Singleton class checks whether an instance of the class already exists. If an instance does not exist, it assigns the current instance to the instance
variable.
We then add a logMessage
method to the Singleton class to demonstrate that we can add properties and methods to the class. Finally, we create two instances of the Singleton class, instance1
and instance2
. We check that they are equal to each other, and we call the logMessage
method on each instance.
When we run the code, we can see that instance1
and instance2
are equal to each other, indicating that there is only one instance of the Singleton class. The logMessage
method is called on each instance, and we can see that the message is logged to the console.
How to create a Singleton instance that can be accessed globally
To create a Singleton instance that can be accessed globally in JavaScript, you can use the module pattern. Here is an example:
var Singleton = (function() {
var instance;
function createInstance() {
// private methods and properties go here
var privateVariable = "I am private";
function privateMethod() {
console.log(privateVariable);
}
// public methods and properties go here
return {
publicMethod: function() {
console.log("I am public");
},
publicVariable: "I am also public",
getPrivate: function() {
privateMethod();
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Usage:
var singletonInstance = Singleton.getInstance();
singletonInstance.publicMethod(); // Output: "I am public"
console.log(singletonInstance.publicVariable); // Output: "I am also public"
singletonInstance.getPrivate(); // Output: "I am private"
In this example, we define the Singleton as an immediately invoked function expression (IIFE) that returns an object containing a getInstance
method. When the getInstance
method is called, it checks if the instance
variable is already set and returns it if it is, or creates a new instance using the createInstance
function.
The createInstance
function defines private methods and properties and returns an object containing public methods and properties. The public methods and properties can be accessed globally by calling Singleton.getInstance().methodName()
.
Note that the Singleton
object is not created until the getInstance
method is called for the first time, so it is lazily instantiated. This helps to save resources and improve performance.
Can a Singleton pattern be extended in JavaScript
Yes, a Singleton pattern can be extended in JavaScript. One way to achieve this is by using inheritance, which allows you to create a new object that inherits properties and methods from an existing object. Here is an example of how to extend a Singleton pattern in JavaScript using inheritance:
var Singleton = (function() {
var instance;
function createInstance() {
// private methods and properties go here
var privateVariable = "I am private";
function privateMethod() {
console.log(privateVariable);
}
// public methods and properties go here
return {
publicMethod: function() {
console.log("I am public");
},
publicVariable: "I am also public",
getPrivate: function() {
privateMethod();
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Extending the Singleton
var ExtendedSingleton = (function() {
var instance;
function createInstance() {
// private methods and properties go here
var privateVariable = "I am private in the extended singleton";
function privateMethod() {
console.log(privateVariable);
}
// public methods and properties go here
return {
publicMethod: function() {
console.log("I am public in the extended singleton");
},
publicVariable: "I am also public in the extended singleton",
getPrivate: function() {
privateMethod();
}
};
}
// call the parent Singleton's getInstance() method
var parentInstance = Singleton.getInstance();
// create the new object that inherits from the parent Singleton
var childInstance = Object.create(parentInstance);
// override the parent Singleton's getInstance() method
childInstance.getInstance = function() {
if (!instance) {
instance = createInstance();
}
return instance;
};
return childInstance;
})();
// Usage
var extendedSingletonInstance = ExtendedSingleton.getInstance();
extendedSingletonInstance.publicMethod(); // Output: "I am public in the extended singleton"
console.log(extendedSingletonInstance.publicVariable); // Output: "I am also public in the extended singleton"
extendedSingletonInstance.getPrivate(); // Output: "I am private in the extended singleton"
In this example, we first define the Singleton
pattern as before. Then we define a new object called ExtendedSingleton
that inherits from the Singleton
object using Object.create()
. We then override the getInstance()
method of the Singleton
object with a new implementation that is specific to the ExtendedSingleton
.
When we call ExtendedSingleton.getInstance()
, it first calls the parent Singleton
's getInstance()
method to check if an instance of the Singleton
object already exists. If it does, it returns it. If not, it creates a new instance of the ExtendedSingleton
object using the createInstance()
function defined in the ExtendedSingleton
object.
How to unit test a Singleton pattern?
When it comes to unit testing a Singleton pattern in JavaScript, the key is to ensure that there is only one instance of the Singleton object throughout the entire test suite. This can be achieved using a technique called dependency injection.
Here's an example of how to unit test a Singleton pattern in JavaScript using dependency injection:
// Singleton module
var Singleton = (function() {
var instance;
function createInstance() {
// private methods and properties go here
var privateVariable = "I am private";
function privateMethod() {
console.log(privateVariable);
}
// public methods and properties go here
return {
publicMethod: function() {
console.log("I am public");
},
publicVariable: "I am also public",
getPrivate: function() {
privateMethod();
}
};
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Unit test module
var SingletonTest = (function() {
var testInstance;
return {
runTests: function(singletonInstance) {
testInstance = singletonInstance;
test("Singleton instance exists", function() {
ok(testInstance);
});
test("Singleton instance is the same throughout the test suite", function() {
var newInstance = Singleton.getInstance();
equal(testInstance, newInstance);
});
test("Public method works", function() {
sinon.spy(console, "log");
testInstance.publicMethod();
sinon.assert.calledWith(console.log, "I am public");
console.log.restore();
});
test("Public variable is correct", function() {
equal(testInstance.publicVariable, "I am also public");
});
test("Private method works", function() {
sinon.spy(console, "log");
testInstance.getPrivate();
sinon.assert.calledWith(console.log, "I am private");
console.log.restore();
});
}
};
})();
// Usage
var singletonInstance = Singleton.getInstance();
SingletonTest.runTests(singletonInstance);
In this example, we define a unit test module called SingletonTest
that runs a series of tests on the Singleton
object. The runTests
method takes a singletonInstance
argument, which is the instance of the Singleton
object that is being tested.
To ensure that there is only one instance of the Singleton
object throughout the entire test suite, we pass the instance of the Singleton
object to each test using dependency injection.
In each test, we perform a specific action on the singletonInstance
object and use assertion methods like ok()
and equal()
to verify that the expected result is obtained. We also use the sinon
library to spy on the console.log
method and verify that the correct message is being logged.
By using dependency injection to pass the instance of the Singleton
object to each test, we can ensure that there is only one instance of the Singleton
object throughout the entire test suite, which is essential for testing a Singleton pattern in JavaScript.
Advantages and Disadvantages of the JavaScript Singleton Pattern
One advantage of the Singleton Pattern is that it can help with memory management. By ensuring that only one instance of an object is created, the pattern reduces the amount of memory used in the application. The pattern can also help with code organization by centralizing the management of shared resources and global state in the application.
However, there are also some potential drawbacks to using the Singleton Pattern. For example, it can make testing more difficult because the singleton instance is shared across the application. This can also result in a violation of the Single Responsibility Principle, which states that a class should have only one reason to change. Additionally, using the Singleton Pattern can result in code that is difficult to maintain and modify.
Summary
The Singleton pattern is a useful design pattern that limits the number of instances of a class to a single instance. In JavaScript, we can implement the Singleton pattern using the module pattern. The module pattern encapsulates private data and provides a public interface for accessing that data. The Singleton object has a private variable that holds the single instance of the object and a public method that returns the single instance.
References
Singleton pattern - Wikipedia
The Singleton Pattern - Learning JavaScript Design Patterns [Book] (oreilly.com)