Singleton Design Pattern
Creational: Singleton Pattern
What is the Singleton Design Pattern?
View Answer:
The objects participating in this pattern are:
Singleton -- In example code: MySingleton
- It returns an instance via a constructor.
- In charge of creating and managing the instance object.
Here's an example of how you can implement the Singleton Pattern using JavaScript ES6:
class Singleton {
constructor(data) {
if (Singleton.instance) {
return Singleton.instance;
}
Singleton.instance = this;
this.data = data;
}
getData() {
return this.data;
}
}
// Usage
const singleton1 = new Singleton("Data for Singleton 1");
console.log(singleton1.getData()); // "Data for Singleton 1"
const singleton2 = new Singleton("Data for Singleton 2");
console.log(singleton2.getData()); // "Data for Singleton 1"
console.log(singleton1 === singleton2); // true
In this example, we try to instantiate Singleton
twice with different data. But when we try to get the data from both instances, we see that they are the same. This is because the second instantiation does not actually create a new object, but returns the first one created.
The key point here is Singleton.instance = this;
where we store the first instance created. On subsequent instantiations, Singleton.instance
would be truthy, so the constructor will return this first instance rather than creating a new one.
What pattern family does the Singleton belong to?
View Answer:
What distinguishes Singletons from static classes or objects?
View Answer:
Here's a code example illustrating the Singleton:
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
someMethod() {
console.log('I am a singleton method.');
}
}
// Usage
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // true
instance1.someMethod(); // I am a singleton method.
instance2.someMethod(); // I am a singleton method.
And here's an example illustrating a static class in JavaScript:
class StaticClass {
static someMethod() {
console.log('I am a static method.');
}
}
// Usage
// const instance = new StaticClass(); // This would throw an error because static class cannot be instantiated.
StaticClass.someMethod(); // I am a static method.
In the Singleton example, we create only one instance of the class, and every subsequent new
call returns the same instance. You can call instance methods on this instance, and it's capable of maintaining state.
In the static example, the class cannot be instantiated at all. Instead, methods are directly called on the class itself, and the class is incapable of maintaining state across the application.
What are some of the advantages of using the Singleton Pattern?
View Answer:
- You can be certain that a class only has one instance.
- You are granted global access to that instance.
- The singleton object only gets initialized the first time it is requested.
What are some of the Singleton Pattern's drawbacks?
View Answer:
- Infringes on the Single Responsibility Principle: At the same time, the pattern solves two problems.
- The Singleton pattern can hide lousy design, such as when application components know too much about each other.
- In a multithreaded environment, the pattern gets treated differently so that multiple threads do not create a singleton object multiple times.
- Unit testing the Singleton's client code may be complicated because many test frameworks rely on inheritance when producing mock objects. This reliance is relative to the constructor of the singleton class being private, and overriding static methods is impossible in most languages. You'll need to develop a unique way to mock the Singleton. Or don't write the tests at all. Alternatively, avoid using the Singleton pattern.
In JavaScript, Are there any alternatives to using the singleton pattern?
View Answer:
How do you ensure that the Singleton instance is created only once in JavaScript?
View Answer:
When is a Singleton useful?
View Answer:
How does Singleton enforce single instantiation?
View Answer:
Why can Singletons make unit testing difficult?
View Answer:
Can you modify a Singleton object once it is created?
View Answer:
Yes, you can modify the properties of a Singleton object after it has been created. Let's demonstrate with an example:
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
this.data = null; // initialize data
}
return Singleton.instance;
}
setData(data) {
this.data = data;
}
getData() {
return this.data;
}
}
// Usage
const singleton1 = new Singleton();
singleton1.setData("Initial data");
console.log(singleton1.getData()); // "Initial data"
const singleton2 = new Singleton();
console.log(singleton2.getData()); // "Initial data"
singleton2.setData("Modified data");
console.log(singleton1.getData()); // "Modified data"
console.log(singleton2.getData()); // "Modified data"
In this example, singleton1
and singleton2
are the same instance. Modifying the data through one instance reflects in the other because they are both the same object. The setData
method allows us to modify the data stored in the singleton instance.
Keep in mind that once the Singleton object has been created, you cannot create a new, different Singleton object. You can only modify the properties or call methods on the existing Singleton instance.
How do you handle multiple threads in a Singleton design pattern?
View Answer:
Let's take a look at an async function modifying singleton data to simulate asynchronous operations.
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
this.data = null;
}
return Singleton.instance;
}
async setData(data) {
// Simulating an async task
return new Promise((resolve) => {
setTimeout(() => {
this.data = data;
resolve();
}, 100);
});
}
getData() {
return this.data;
}
}
// Usage
const singleton1 = new Singleton();
const singleton2 = new Singleton();
(async function() {
await singleton1.setData("Data from singleton1");
console.log(singleton1.getData()); // "Data from singleton1"
console.log(singleton2.getData()); // "Data from singleton1"
await singleton2.setData("Data from singleton2");
console.log(singleton1.getData()); // "Data from singleton2"
console.log(singleton2.getData()); // "Data from singleton2"
})();
In this code, even though we are using async functions and tasks are not completed instantly, there will never be a problem with thread safety because JavaScript is single-threaded.
How can we create a Singleton in JavaScript?
View Answer:
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
}
// Usage
const singleton1 = new Singleton();
const singleton2 = new Singleton();
console.log(singleton1 === singleton2); // true
Can there be multiple instances of a Singleton class?
View Answer:
Is the Singleton pattern common in JavaScript?
View Answer:
Can a Singleton class be inherited?
View Answer:
Here's an example of how a Singleton class can be inherited in JavaScript.
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
}
class Child extends Singleton {
constructor() {
super();
}
childMethod() {
console.log('This is a method from the Child class');
}
}
// Usage
const singleton1 = new Singleton();
const singleton2 = new Singleton();
console.log(singleton1 === singleton2); // true
const child1 = new Child();
const child2 = new Child();
console.log(child1 === child2); // false
child1.childMethod(); // "This is a method from the Child class"
child2.childMethod(); // "This is a method from the Child class"
In this code, Singleton
is a singleton class and Child
extends Singleton
. When we try to create multiple instances of Child
, we see that they are not the same instance (child1
is not equal to child2
), meaning the singleton nature of Singleton
does not apply to Child
. Each instance of Child
has its own separate state and behavior.
What is a practical example of Singleton in JavaScript?
View Answer:
class Logger {
constructor() {
if (!Logger.instance) {
Logger.instance = this;
this.logs = [];
}
return Logger.instance;
}
log(message) {
this.logs.push(message);
console.log(`LOG: ${message}`);
}
printLogCount() {
console.log(`${this.logs.length} Logs`);
}
}
// Usage
const logger1 = new Logger();
Object.freeze(logger1); // to make sure it's not modified elsewhere
logger1.log('First piece of log');
logger1.printLogCount(); // "1 Logs"
const logger2 = new Logger();
Object.freeze(logger2); // to make sure it's not modified elsewhere
logger2.log('Second piece of log');
logger2.printLogCount(); // "2 Logs"
console.log(logger1 === logger2); // true
In this example, Logger
is a Singleton class with methods to log a message and print the total number of logs. logger1
and logger2
are both instances of Logger
, but they are actually the same object because Logger
is a Singleton class. As such, the logs added via logger1
and logger2
are both stored in the same array.
What are other design patterns related to Singleton?
View Answer:
Can Singleton be considered an anti-pattern?
View Answer:
Is Singleton's global access always advantageous?
View Answer:
Can we use Singleton in a distributed system?
View Answer:
How does JavaScript's module pattern relate to Singleton?
View Answer:
// logger.js
let logs = [];
const logger = {
log(message) {
logs.push(message);
console.log(`LOG: ${message}`);
},
printLogCount() {
console.log(`${logs.length} Logs`);
}
}
export default logger;
// main.js
import logger from './logger.js';
logger.log('This is from main.js');
logger.printLogCount(); // "1 Logs"
// other.js
import logger from './logger.js';
logger.log('This is from other.js');
logger.printLogCount(); // "2 Logs"
In this example, logger.js
exports a logger
object. This object is created once when logger.js
is first imported, and the same object is used in any subsequent imports (like in main.js
and other.js
).
As a result, even though logger
is imported in two different files, it maintains state across those files because it's the same object. This makes the JavaScript module pattern similar to the Singleton pattern in a way.