π JavaScript Singleton Design Pattern
The Singleton Pattern is a design pattern that ensures a module has only one instance and provides a global point of access to that instance. Itβs particularly useful in situations where you need to manage shared resources, settings, or global state across an application.
Why Use the Singleton Pattern?
- Single Instance Management
- The Singleton Pattern ensures that a module is instantiated only once, preventing the creation of multiple instances that might lead to inconsistent state or behavior.
- Global Access
- The Singleton provides a centralized point of access to the instance, making it easier to manage and interact with shared resources.
- Efficient Resource Management
- By controlling the instantiation, the Singleton Pattern can help optimize resource usage by reusing the same instance wherever itβs needed.
Basic Structure of the Singleton Pattern
In JavaScript, the Singleton Pattern can be implemented using an Immediately Invoked Function Expression (IIFE) to create a private scope. Hereβs a basic example:
const Singleton = (function() {
let instance;
function createInstance() {
const object = new Object('I am the instance');
return object;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
// Usage
const instance1 = Singleton.getInstance();
const instance2 = Singleton.getInstance();
console.log(instance1 === instance2); // true
In this example:
- The
createInstance
function creates a new instance, but it is called only ifinstance
is not already created. - The
getInstance
method checks if an instance already exists; if it does, it returns that instance. Otherwise, it creates a new one. - This ensures that no matter how many times
getInstance
is called, the same instance is returned.
Applying the Singleton Pattern in Applications
The Singleton Pattern is often used in situations where exactly one object is needed to coordinate actions across the system. For example, you might use a Singleton to manage application settings, logging, or shared state in a JavaScript application.
const ConfigManager = (function() {
let config;
function loadConfig() {
// Simulating loading configuration data
return {
apiUrl: 'https://jsonplaceholder.typicode.com',
theme: 'dark'
};
}
return {
getConfig: function() {
if (!config) {
config = loadConfig();
}
return config;
}
};
})();
// Usage
const config1 = ConfigManager.getConfig();
const config2 = ConfigManager.getConfig();
console.log(config1 === config2); // true
In this example, ConfigManager
manages the application configuration. It loads the configuration data only once, and subsequent calls to getConfig
return the same configuration object, ensuring consistency across the application.
Benefits of the Singleton Pattern
- Consistency
- By ensuring only one instance is used, the Singleton Pattern helps maintain consistent state across your application.
- Centralized Management
- The Singleton provides a single point of access to shared resources, simplifying management and interaction.
- Resource Optimization
- By reusing the same instance, the Singleton Pattern can optimize resource usage, particularly in resource-intensive operations.
Real-World Example
Using the Singleton Pattern in Game Development
In game development, the Singleton Pattern is a popular choice for managing game states, configurations, and shared resources. For example, consider a game where you need to manage a global game state or a settings manager that controls audio levels, screen resolution, and other configurations.
const GameSettings = (function() {
let instance;
function createInstance() {
const settings = {
soundVolume: 100,
screenResolution: '1920x1080'
};
return settings;
}
return {
getInstance: function() {
if (!instance) {
instance = createInstance();
}
return instance;
},
setVolume: function(volume) {
instance.soundVolume = volume;
},
getVolume: function() {
return instance.soundVolume;
},
setResolution: function(resolution) {
instance.screenResolution = resolution;
},
getResolution: function() {
return instance.screenResolution;
}
};
})();
// Usage
const settings1 = GameSettings.getInstance();
const settings2 = GameSettings.getInstance();
console.log(settings1 === settings2); // true
GameSettings.setVolume(80);
console.log(GameSettings.getVolume()); // 80
Why Use the Singleton Pattern in Games?
- Centralized Control
- In a game, you often need centralized control over settings or game states. The Singleton Pattern provides a global access point to these settings, ensuring that they remain consistent no matter where in the game they are accessed or modified.
- Resource Management
- In more complex games, the Singleton Pattern can be used to manage resources like audio or graphics systems, ensuring that there's only one instance managing these critical resources.
- Consistency Across the Game
- Whether it's managing game settings, player states, or even handling input devices, the Singleton Pattern ensures that these elements are consistent throughout the entire game, avoiding conflicts or errors that could arise from multiple instances.
Putting It Into Action
Create an HTML file with the following structure and include the provided script. This script demonstrates how to use the Singleton Pattern to manage application configuration settings.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Singleton Pattern Example</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<div class="container mt-5">
<h1>Singleton Pattern Example</h1>
</div>
<script>
const ConfigManager = (function() {
let config;
function loadConfig() {
// Simulating loading configuration data
return {
apiUrl: 'https://jsonplaceholder.typicode.com',
theme: 'dark'
};
}
return {
getConfig: function() {
if (!config) {
config = loadConfig();
}
return config;
}
};
})();
const config = ConfigManager.getConfig();
console.log(config);
</script>
</body>
</html>
Challenge
Extend the example to allow for setting and getting individual configuration properties. Update the ConfigManager
to include methods for retrieving and updating specific configuration settings.
In order to check your learning, you should attempt to create a solution before revealing the provided solution below.
// JavaScript Code
const ConfigManager = (function() {
let config;
function loadConfig() {
// Simulating loading configuration data
return {
apiUrl: 'https://jsonplaceholder.typicode.com',
theme: 'dark'
};
}
return {
getConfig: function() {
if (!config) {
config = loadConfig();
}
return config;
},
getProperty: function(prop) {
return config[prop];
},
setProperty: function(prop, value) {
config[prop] = value;
}
};
})();
const config = ConfigManager.getConfig();
console.log(config);
ConfigManager.setProperty('theme', 'light');
console.log(ConfigManager.getProperty('theme')); // light