Article Categories
- All Categories
-
Data Structure
-
Networking
-
RDBMS
-
Operating System
-
Java
-
MS Excel
-
iOS
-
HTML
-
CSS
-
Android
-
Python
-
C Programming
-
C++
-
C#
-
MongoDB
-
MySQL
-
Javascript
-
PHP
-
Economics & Finance
Advanced JavaScript Patterns: Singleton, Decorator, and Proxy
JavaScript is a versatile programming language that allows developers to create dynamic and interactive web applications. With its vast array of features and flexibility, JavaScript enables the implementation of various design patterns to solve complex problems efficiently. In this article, we will explore three advanced JavaScript patterns: Singleton, Decorator, and Proxy. These patterns provide elegant solutions for managing object creation, adding functionality dynamically, and controlling access to objects.
Singleton Pattern
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This pattern is useful when we want to limit the number of instances of a class to prevent redundancy or ensure that a single instance is shared across the application.
Example
Let's consider an example where we want to create a logger object that maintains a single instance throughout the application:
// Singleton Logger
const Logger = (() => {
let instance;
function createInstance() {
const log = [];
return {
addLog: (message) => {
log.push(message);
},
printLogs: () => {
log.forEach((message) => {
console.log(message);
});
},
};
}
return {
getInstance: () => {
if (!instance) {
instance = createInstance();
}
return instance;
},
};
})();
// Usage
const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();
logger1.addLog("Message 1");
logger2.addLog("Message 2");
logger1.printLogs();
Message 1 Message 2
Explanation
In this example, we use an immediately-invoked function expression (IIFE) to encapsulate the Singleton implementation. The getInstance method checks if an instance of the logger exists. If it does not, it creates a new instance using the createInstance function. This ensures that only one instance of the logger is created and shared among all the calls to getInstance. The logger instance maintains an internal log array, allowing messages to be added and printed.
Decorator Pattern
The Decorator pattern allows us to add new behaviors or modify existing ones dynamically to an object without affecting other instances of the same class. It enables us to extend the functionality of an object by wrapping it with one or more decorators, providing a flexible alternative to subclassing.
Example
Let's consider an example where we have a simple car object and want to add additional features using decorators:
// Car class
class Car {
constructor() {
this.description = "Basic car";
}
getDescription() {
return this.description;
}
}
// Decorator class
class CarDecorator {
constructor(car) {
this.car = car;
}
getDescription() {
return this.car.getDescription();
}
}
// Decorators
class ElectricCarDecorator extends CarDecorator {
constructor(car) {
super(car);
}
getDescription() {
return `${super.getDescription()}, electric engine`;
}
}
class LuxuryCarDecorator extends CarDecorator {
constructor(car) {
super(car);
}
getDescription() {
return `${super.getDescription()}, leather seats`;
}
}
// Usage
const basicCar = new Car();
const electricLuxuryCar = new ElectricCarDecorator(
new LuxuryCarDecorator(basicCar)
);
console.log(electricLuxuryCar.getDescription());
Basic car, leather seats, electric engine
Explanation
In this example, we start with a Car class representing a basic car with a getDescription method. The CarDecorator class serves as the base decorator, providing a common interface for all decorators. Each specific decorator extends the CarDecorator and overrides the getDescription method to add or modify functionality.
We create two specific decorators: ElectricCarDecorator and LuxuryCarDecorator. The ElectricCarDecorator adds an electric engine feature, while the LuxuryCarDecorator adds leather seats. By combining these decorators, we can enhance the car object with multiple features dynamically.
Proxy Pattern
The Proxy pattern allows us to control access to an object by providing a surrogate or placeholder. It can be used to add additional logic, such as validation or caching, without modifying the original object's behaviour. Proxies act as intermediaries between clients and the actual objects, enabling fine-grained control over object interactions.
Example
Let's consider an example where we use a proxy to implement simple access control for a user object:
// User object
const user = {
name: "John",
role: "user",
};
// Proxy with access control
const userProxy = new Proxy(user, {
get: (target, property) => {
if (property === "role" && target.role === "admin") {
throw new Error("Access denied");
}
return target[property];
},
});
console.log(userProxy.name);
console.log(userProxy.role);
// Test with admin role
const adminUser = { name: "Admin", role: "admin" };
const adminProxy = new Proxy(adminUser, {
get: (target, property) => {
if (property === "role" && target.role === "admin") {
throw new Error("Access denied");
}
return target[property];
},
});
try {
console.log(adminProxy.role);
} catch (error) {
console.log("Error:", error.message);
}
John user Error: Access denied
Explanation
In this example, we have a user object with properties name and role. We create a proxy using the Proxy constructor, providing a handler object with a get trap. The get trap intercepts property access and allows us to implement custom logic. In this case, we check if the property being accessed is role and if the user has the "admin" role. If the condition is met, we throw an error to deny access; otherwise, we allow the property access.
Comparison of Patterns
| Pattern | Purpose | Use Case |
|---|---|---|
| Singleton | Single instance control | Database connections, loggers |
| Decorator | Add functionality dynamically | UI components, data transformation |
| Proxy | Control object access | Validation, caching, access control |
Conclusion
These advanced JavaScript patterns?Singleton, Decorator, and Proxy?provide powerful tools for solving complex design challenges. The Singleton ensures single instances, Decorator adds functionality dynamically, and Proxy controls object access with custom logic.
