The Singleton pattern is thus known because it restricts instantiation of a class to a single object. Classically, the Singleton pattern can be implemented by creating a class with a method that creates a new instance of the class if one doesnât exist. In the event of an instance already existing, it simply returns a reference to that object. Singletons differ from static classes (or objects) as we can delay their initialization, generally because they require some information that may not be available during initialization time. For code that is unaware of a previous reference to them, they do not provide a method for easy retrieval. This is because it is neither the object nor âclassâ thatâs returned by a Singleton; itâs a structure. Think of how closured variables arenât actually closuresâthe function scope that provides the closure is the closure. In JavaScript, Singletons serve as a shared resource namespace which isolate implementation code from the global namespace so as to provide a single point of access for functions. We can implement a Singleton as follows:
var mySingleton = (function () { // Instance stores a reference to the Singleton var instance; function init() { // Singleton // Private methods and variables function privateMethod(){ console.log( "I am private" ); } var privateVariable = "Im also private"; var privateRandomNumber = Math.random(); return { // Public methods and variables publicMethod: function () { console.log( "The public can see me!" ); }, publicProperty: "I am also public" getRandomNumber: function() { return privateRandomNumber; } }; }; return { // Get the Singleton instance if one exists // or create one if it doesn't getInstance: function () { if ( !instance ) { instance = init(); } return instance; } }; })(); var myBadSingleton = (function () { // Instance stores a reference to the Singleton var instance; function init() { // Singleton var privateRandomNumber = Math.random(); return { getRandomNumber: function() { return privateRandomNumber; } }; }; return { // Always create a new Singleton instance getInstance: function () { instance = init(); return instance; } }; })(); // Usage: var singleA = mySingleton.getInstance(); var singleB = mySingleton.getInstance(); console.log( singleA.getRandomNumber() === singleB.getRandomNumber() ); // true var badSingleA = myBadSingleton.getInstance(); var badSingleB = myBadSingleton.getInstance(); console.log( badSingleA.getRandomNumber() !== badSingleB.getRandomNumber() ); // true
What makes the Singleton is the global access to the instance
(generally through MySingleton.getInstance()
) as we
donât (at least in static languages) call a new
MySingleton()
directly. This is, however, possible in
JavaScript. In the GoF book, the applicability of the
Singleton pattern is described as follows:
There must be exactly one instance of a class, and it must be accessible to clients from a well-known access point.
The sole instance should be extensible by subclassing, and clients should be able to use an extended instance without modifying their code.
The second of these points refers to a case where we might need code, such as:
mySingleton
.
getInstance
=
function
(){
if
(
this
.
_instance
==
null
)
{
if
(
isFoo
()
)
{
this
.
_instance
=
new
FooSingleton
();
}
else
{
this
.
_instance
=
new
BasicSingleton
();
}
}
return
this
.
_instance
;
};
Here, getInstance
becomes a little like a Factory
method and we donât need to update each point in our code when accessing
it. FooSingleton
(above) would be a subclass of
BasicSingleton
and would implement the same
interface.
Why is deferring execution considered important for a Singleton?:
In C++, it serves as isolation from the unpredictability of the dynamic initialization order, returning control to the programmer.
It is important to note the difference between a static instance of a class (object) and a Singleton: while a Singleton can be implemented as a static instance, it can also be constructed lazily, without the use of resources or memory, until the static instance is needed.
If we have a static object that can be initialized directly, we need
to ensure the code is always executed in the same order (e.g., in case
objCar
needs objWheel
during its
initialization), and this doesnât scale when you have a large number of
source files.
Both Singletons and static objects are useful, but they shouldnât be overused in the same way that we shouldnât overuse other patterns.
In practice, the Singleton pattern is useful when exactly one object is needed to coordinate others across a system. Here, you can see the pattern being used in this context:
var
SingletonTester
=
(
function
()
{
// options: an object containing configuration options for the singleton
// e.g var options = { name: "test", pointX: 5};
function
Singleton
(
options
)
{
// set options to the options supplied
// or an empty object if none are provided
options
=
options
||
{};
// set some properties for our singleton
this
.
name
=
"SingletonTester"
;
this
.
pointX
=
options
.
pointX
||
6
;
this
.
pointY
=
options
.
pointY
||
10
;
}
// our instance holder
var
instance
;
// an emulation of static variables and methods
var
_static
=
{
name
:
"SingletonTester"
,
// Method for getting an instance. It returns
// a singleton instance of a singleton object
getInstance
:
function
(
options
)
{
if
(
instance
===
undefined
)
{
instance
=
new
Singleton
(
options
);
}
return
instance
;
}
};
return
_static
;
})();
var
singletonTest
=
SingletonTester
.
getInstance
({
pointX
:
5
});
// Log the output of pointX just to verify it is correct
// Outputs: 5
console
.
log
(
singletonTest
.
pointX
);
While the Singleton has valid uses, often when we find ourselves needing it in JavaScript, itâs a sign that we may need to reevaluate our design.
The presence of the Singleton is often an indication that modules in a system are either tightly coupled or that logic is overly spread across multiple parts of a code base. Singletons can be more difficult to test due to issues ranging from hidden dependencies to difficulty in creating multiple instances, difficulty in stubbing dependencies, etc.
Miller Medeiros has previously recommended this excellent article for further reading on the Singleton and its various issues. Also recommended reading are the comments to this article, discussing how Singletons can increase tight coupling. Iâm happy to second these recommendations, as both pieces raise many important points about this pattern.
Get Learning JavaScript Design Patterns now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.