In software engineering, design patterns are tried-and-tested solutions to common problems in software design. Using design patterns in JavaScript can help you write more structured, reusable, and maintainable code. In this blog, we’ll walk through 10 essential JavaScript design patterns that every developer should know!
1. Singleton Pattern
The Singleton Pattern restricts the instantiation of a class to one single instance, which is especially useful when only one instance is needed to coordinate actions across a system (like a database connection).
class Database {
constructor() {
if (Database.instance) return Database.instance;
Database.instance = this;
this.connection = "Connected to DB";
}
}
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true
2. Factory Pattern
The Factory Pattern provides a way to create objects without specifying the exact class of the object that will be created. It’s ideal for scenarios where you need to create different instances based on certain conditions.
class Car {
constructor() {
this.type = "Car";
}
}
class Bike {
constructor() {
this.type = "Bike";
}
}
class VehicleFactory {
static createVehicle(type) {
switch(type) {
case "car": return new Car();
case "bike": return new Bike();
}
}
}
const car = VehicleFactory.createVehicle("car");
console.log(car.type); // "Car"
3. Observer Pattern
The Observer Pattern allows an object (known as the subject) to notify other objects (known as observers) of changes. This pattern is useful for event-driven systems.
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
update(data) {
console.log("Observer received:", data);
}
}
const subject = new Subject();
const observer1 = new Observer();
subject.subscribe(observer1);
subject.notify("Hello Observers!"); // "Observer received: Hello Observers!"
4. Module Pattern
The Module Pattern helps encapsulate code within a single object to keep certain variables and functions private, exposing only what’s necessary.
const CounterModule = (() => {
let counter = 0;
return {
increment: () => ++counter,
decrement: () => --counter,
value: () => counter
};
})();
CounterModule.increment();
console.log(CounterModule.value()); // 1
5. Revealing Module Pattern
This is a variation of the Module Pattern where we define all functions and variables in a private scope and return an object exposing only the chosen ones.
const Calculator = (() => {
let result = 0;
function add(x) { result += x; }
function subtract(x) { result -= x; }
return {
add,
subtract,
getResult: () => result
};
})();
Calculator.add(5);
console.log(Calculator.getResult()); // 5
6. Prototype Pattern
The Prototype Pattern allows sharing properties or methods across all instances of a class. JavaScript’s prototypal inheritance makes this pattern easy to implement.
const Vehicle = {
wheels: 4,
drive() { console.log("Driving on", this.wheels, "wheels"); }
};
const car = Object.create(Vehicle);
car.drive(); // "Driving on 4 wheels"
7. Command Pattern
The Command Pattern encapsulates requests as objects, allowing you to parameterize actions. This pattern is especially useful for creating a sequence of commands or logging them.
class Light {
on() { console.log("Light is ON"); }
off() { console.log("Light is OFF"); }
}
class Command {
constructor(light) {
this.light = light;
}
execute(action) {
this.light[action]();
}
}
const light = new Light();
const lightCommand = new Command(light);
lightCommand.execute("on"); // "Light is ON"
8. Strategy Pattern
The Strategy Pattern enables selecting an algorithm at runtime. This pattern is helpful when you have multiple ways to perform an action and want to switch algorithms dynamically.
class Add {
execute(a, b) { return a + b; }
}
class Subtract {
execute(a, b) { return a - b; }
}
class Calculator {
constructor(strategy) {
this.strategy = strategy;
}
calculate(a, b) {
return this.strategy.execute(a, b);
}
}
const calculator = new Calculator(new Add());
console.log(calculator.calculate(5, 3)); // 8
9. Decorator Pattern
The Decorator Pattern lets you add new functionalities to an existing object without altering its structure. In JavaScript, this is often achieved using higher-order functions.
function addGreeting(fn) {
return function(...args) {
console.log("Hello!");
return fn(...args);
};
}
function sayName(name) {
console.log(name);
}
const sayNameWithGreeting = addGreeting(sayName);
sayNameWithGreeting("Alice"); // "Hello!" "Alice"
10. Chain of Responsibility Pattern
The Chain of Responsibility Pattern allows processing a request through a series of handlers, stopping when a handler has handled the request. It’s useful for scenarios like form validation or middleware in web applications.
class RequestHandler {
constructor(successor) {
this.successor = successor;
}
handleRequest(request) {
if (this.successor) this.successor.handleRequest(request);
}
}
class AuthHandler extends RequestHandler {
handleRequest(request) {
if (request.isAuthenticated) {
console.log("Request Authenticated");
super.handleRequest(request);
} else {
console.log("Authentication Failed");
}
}
}
const authHandler = new AuthHandler();
authHandler.handleRequest({ isAuthenticated: true }); // "Request Authenticated"
Wrapping Up
JavaScript design patterns improve readability, structure, and scalability, making code easier to maintain and extend. By understanding and applying these patterns, you can solve common coding challenges elegantly and consistently. Whether you’re managing state with a Singleton or building complex UI interactions with Observer, design patterns bring a deeper level of control and flexibility to your JavaScript projects.
Which pattern is your favorite? Leave a comment below!