Introduction:
Design patterns are essential tools for software developers in JavaScript. They offer proven solutions to common programming challenges, improve code quality, and provide structured approaches for organizing code. Understanding and applying these patterns is crucial for building robust and scalable applications.
Singleton Pattern:
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. It is useful in scenarios where you want to restrict the instantiation of a class to a single object.
class Singleton {
constructor() {
if (!Singleton.instance) {
// Initialize the instance
// Add your initialization code here
Singleton.instance = this;
}
return Singleton.instance;
}
// Add your methods and properties here
// ...
}
// Usage
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: true
Observer Pattern:
The Observer pattern establishes a one-to-many dependency between objects, where when one object (the subject) changes its state, all its dependents (observers) are automatically notified and updated. This pattern enables loose coupling between the subject and observers, allowing for easy extensibility and flexibility in handling event-driven systems.
// Observable (Publisher)
class Observable {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
// Observer (Subscriber)
class Subscriber {
update(data) {
console.log('Received data:', data);
// Perform necessary actions based on the received data
}
}
// Usage
const observable = new Observable();
const subs1 = new Subscriber();
const subs2 = new Subscriber();
observable.subscribe(subs1);
observable.subscribe(subs2);
observable.notify('Hello, observers!');
// Output: Received data: Hello, observers!
Proxy Pattern:
The Proxy pattern provides a surrogate or placeholder for another object and controls access to it. It allows you to add additional functionality or provide a level of indirection when interacting with the original object. This pattern is useful in scenarios where you want to control access, perform validation, or implement caching without modifying the original object’s code.
// RealSubject
class RealSubject {
request() {
console.log('RealSubject: Handling request.');
}
}
// Proxy
class ProxySubject {
constructor(realSubject) {
this.realSubject = realSubject;
}
request() {
if (this.checkAccess()) {
this.realSubject.request();
} else {
console.log('ProxySubject: Access denied.');
}
}
checkAccess() {
// Perform access control logic
return true; // Allow access for demonstration purposes
}
}
// Usage
const realSubject = new RealSubject();
const proxy = new ProxySubject(realSubject);
proxy.request(); // Output: RealSubject: Handling request.
Provider Pattern:
The Provider pattern, also known as the Dependency Injection pattern, allows for the centralized management and provisioning of dependencies across an application. It separates the creation and resolution of dependencies from the objects that require them, promoting modularity, testability, and flexibility.
// Dependency Provider
class DependencyProvider {
constructor() {
this.dependencies = new Map();
}
register(name, dependency) {
this.dependencies.set(name, dependency);
}
resolve(name) {
if (this.dependencies.has(name)) {
return this.dependencies.get(name);
}
throw new Error(`Dependency not found: ${name}`);
}
}
// Usage
const dependencyProvider = new DependencyProvider();
// Register dependencies
dependencyProvider.register('apiService', new ApiService());
dependencyProvider.register('dataService', new DataService());
// Resolve dependencies
const apiService = dependencyProvider.resolve('apiService');
const dataService = dependencyProvider.resolve('dataService');
// Use the resolved dependencies
apiService.fetchData();
dataService.saveData();
Module Pattern:
The Module pattern is a popular design pattern in JavaScript that allows you to create encapsulated and reusable modules with private and public members. It provides a way to organize code, prevents naming collisions, and controls the visibility of variables and functions.
const MyModule = (() => {
let privateVariable = 'I am private';
function privateFunction() {
console.log('This is a private function');
}
return {
publicVariable: 'I am public',
publicFunction: () => {
console.log('This is a public function');
console.log(privateVariable);
privateFunction();
}
};
})();
console.log(MyModule.publicVariable); // Output: I am public
MyModule.publicFunction();
// This is a public function
// I am private
// This is a private function
Mixin Pattern:
The Mixin pattern is a design pattern in JavaScript that allows objects to acquire the properties and methods of multiple mixins (reusable code modules) to enhance their functionality. It enables code reusability, promotes composition over inheritance, and allows for flexible object behavior customization.
// Mixin objects
const canEat = {
eat() {
console.log('Eating...');
}
};
const canSleep = {
sleep() {
console.log('Sleeping...');
}
};
const canPlay = {
play() {
console.log('Playing...');
}
};
// Target object
const pet = {};
// Mixin function
function applyMixin(target, mixin) {
Object.assign(target, mixin);
}
// Applying mixins to the target object
applyMixin(pet, canEat);
applyMixin(pet, canSleep);
applyMixin(pet, canPlay);
// Usage
pet.eat(); // Output: Eating...
pet.sleep(); // Output: Sleeping...
pet.play(); // Output: Playing...
Mediator/Middleware Pattern:
The Mediator pattern, also known as the Middleware pattern, provides a way to decouple and centralize communication between different components of an application. It promotes loose coupling by introducing a mediator object that handles communication and coordination between other objects, reducing direct dependencies between them.
// Mediator
class Mediator {
constructor() {
this.colleagues = [];
}
register(colleague) {
this.colleagues.push(colleague);
colleague.setMediator(this);
}
send(message, sender) {
for (const colleague of this.colleagues) {
if (colleague !== sender) {
colleague.receive(message);
}
}
}
}
// Colleague
class Colleague {
constructor(name) {
this.name = name;
this.mediator = null;
}
setMediator(mediator) {
this.mediator = mediator;
}
send(message) {
this.mediator.send(message, this);
}
receive(message) {
console.log(`${this.name} received message: ${message}`);
}
}
// Usage
const mediator = new Mediator();
const colleague1 = new Colleague('Colleague 1');
const colleague2 = new Colleague('Colleague 2');
const colleague3 = new Colleague('Colleague 3');
mediator.register(colleague1);
mediator.register(colleague2);
mediator.register(colleague3);
colleague1.send('Hello everyone!');
// Colleague 2 received message: Hello everyone!
// Colleague 3 received message: Hello everyone!
colleague3.send('Hi there!');
// Colleague 1 received message: Hi there!
// Colleague 2 received message: Hi there!
Command Pattern:
The Command pattern is a behavioral design pattern in JavaScript that encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. It separates the sender of a request from the object that performs the action, enabling decoupling and flexibility in command execution.
// Receiver
class Light {
turnOn() {
console.log('Light is on');
}
turnOff() {
console.log('Light is off');
}
}
// Command interface
class Command {
execute() {}
}
// Concrete commands
class TurnOnCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOn();
}
}
class TurnOffCommand extends Command {
constructor(light) {
super();
this.light = light;
}
execute() {
this.light.turnOff();
}
}
// Invoker
class RemoteControl {
setCommand(command) {
this.command = command;
}
pressButton() {
this.command.execute();
}
}
// Usage
const light = new Light();
const turnOnCommand = new TurnOnCommand(light);
const turnOffCommand = new TurnOffCommand(light);
const remoteControl = new RemoteControl();
remoteControl.setCommand(turnOnCommand);
remoteControl.pressButton(); // Output: Light is on
remoteControl.setCommand(turnOffCommand);
remoteControl.pressButton(); // Output: Light is off
Factory Pattern:
The Factory pattern is a creational design pattern in JavaScript that provides a way to create objects without exposing the object creation logic directly to the client. It encapsulates the object instantiation process within a factory method, allowing for flexible and consistent object creation.
In the Factory pattern, a factory function or a factory class is responsible for creating objects of a specific type based on the input or configuration provided. This abstracts the client from the details of object creation and allows for easy extensibility and customization.
// Product interface
class Product {
operation() {}
}
// Concrete products
class ConcreteProductA extends Product {
operation() {
console.log('ConcreteProductA operation');
}
}
class ConcreteProductB extends Product {
operation() {
console.log('ConcreteProductB operation');
}
}
// Factory
class Factory {
createProduct(type) {
switch (type) {
case 'A':
return new ConcreteProductA();
case 'B':
return new ConcreteProductB();
default:
throw new Error('Invalid product type.');
}
}
}
// Usage
const factory = new Factory();
const productA = factory.createProduct('A');
productA.operation(); // Output: ConcreteProductA operation
const productB = factory.createProduct('B');
productB.operation(); // Output: ConcreteProductB operation
Outro
Thank you for reading this explanation of JavaScript patterns. I hope you found it helpful and informative in understanding various JavaScript patterns and their application in solving common software design challenges.
If you enjoyed this explanation and found it useful, please consider subscribing to receive more content like this. Your support and feedback are greatly appreciated. If you have any further questions or topics you’d like to explore, feel free to ask.
Remember to like and share this post if you found it valuable. Your engagement helps others discover and benefit from the information as well.
Thank you once again, and happy coding!