Singleton Pattern
Ensures a class has only one instance and provides a global access point to it.
class Singleton {
constructor() {
if (!Singleton.instance) {
Singleton.instance = this;
}
return Singleton.instance;
}
// Other methods...
}
const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: trueFactory Pattern
Provides an interface for creating objects while hiding the creation logic, making the code more flexible.
function createShape(type) {
switch (type) {
case 'circle':
return new Circle();
case 'rectangle':
return new Rectangle();
default:
throw new Error('Invalid shape type');
}
}
class Shape {}
class Circle extends Shape {}
class Rectangle extends Shape {}
const circle = createShape('circle');
const rectangle = createShape('rectangle');Builder Pattern
Separates the construction of a complex object from its representation, allowing the same construction process to create different representations.
class UserBuilder {
constructor() {
this.user = {};
}
setName(name) {
this.user.name = name;
return this;
}
setEmail(email) {
this.user.email = email;
return this;
}
build() {
return this.user;
}
}
const user = new UserBuilder()
.setName('John Doe')
.setEmail('john.doe@example.com')
.build();Observer Pattern
Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.
class Subject {
constructor() {
this.observers = [];
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
notifyObservers(data) {
this.observers.forEach((observer) => observer.update(data));
}
}
class Observer {
update(data) {
console.log('Received update:', data);
}
}
const subject = new Subject();
const observer = new Observer();
subject.addObserver(observer);
subject.notifyObservers({ message: 'Hello, observers!' }); // Output: Received update: { message: 'Hello, observers!' }Strategy Pattern
Defines a family of algorithms, encapsulates each one in a separate class, and makes them interchangeable, allowing the algorithm to vary independently from the clients that use it.
class PaymentStrategy {
calculate(price) {
throw new Error('Not implemented');
}
}
class CashPayment extends PaymentStrategy {
calculate(price) {
return price;
}
}
class CreditCardPayment extends PaymentStrategy {
calculate(price) {
return price * 1.05; // Add 5% surcharge for credit card payments
}
}
class ShoppingCart {
constructor(paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
setPaymentStrategy(strategy) {
this.paymentStrategy = strategy;
}
checkout(totalPrice) {
const amountToPay = this.paymentStrategy.calculate(totalPrice);
console.log(`Total amount to pay: ${amountToPay}`);
}
}
const cart = new ShoppingCart(new CashPayment());
cart.checkout(100); // Output: Total amount to pay: 100
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100); // Output: Total amount to pay: 105Decorator Pattern
Dynamically adds new functionality to an object while keeping its interface unchanged, without modifying the original code.
class Coffee {
constructor(description) {
this.description = description;
}
getCost() {
return 1;
}
getDescription() {
return this.description;
}
}
class MilkDecorator extends Coffee {
constructor(coffee) {
super(coffee.getDescription());
this.coffee = coffee;
}
getCost() {
return this.coffee.getCost() + 0.7;
}
getDescription() {
return `${this.coffee.getDescription()}, with milk`;
}
}
const coffee = new Coffee('Regular coffee');
console.log(coffee.getCost()); // Output: 1
console.log(coffee.getDescription()); // Output: Regular coffee
const milkCoffee = new MilkDecorator(coffee);
console.log(milkCoffee.getCost()); // Output: 1.7
console.log(milkCoffee.getDescription()); // Output: Regular coffee, with milkAdapter Pattern
Converts the interface of a class into another interface that a client expects, allowing classes with incompatible interfaces to work together.
class TargetInterface {
request() {
throw new Error('Not implemented');
}
}
class Adaptee {
specificRequest() {
return 'Adaptee response';
}
}
class Adapter extends TargetInterface {
constructor(adaptee) {
super();
this.adaptee = adaptee;
}
request() {
return `Adapter: ${this.adaptee.specificRequest()}`;
}
}
const adaptee = new Adaptee();
const adapter = new Adapter(adaptee);
console.log(adapter.request()); // Output: Adapter: Adaptee responseProxy Pattern
Provides a surrogate or placeholder for another object to control access to it, allowing additional functionality like access control, lazy loading, caching, or logging.
class RealSubject {
expensiveOperation() {
console.log('Performing expensive operation...');
return 'RealSubject result';
}
}
class ProxySubject {
constructor(realSubject) {
this.realSubject = realSubject;
}
expensiveOperation() {
if (this.checkAccess()) {
return this.realSubject.expensiveOperation();
} else {
console.log('Access denied');
return null;
}
}
checkAccess() {
// Simulate access check
return Math.random() > 0.5;
}
}
const realSubject = new RealSubject();
const proxySubject = new ProxySubject(realSubject);
proxySubject.expensiveOperation(); // May output: Performing expensive operation..., or Access deniedFacade Pattern
Provides a simplified, unified interface to a complex subsystem, making it easier for external code to interact with it.
class SubsystemA {
methodA() {
console.log('Subsystem A method called');
}
}
class SubsystemB {
methodB() {
console.log('Subsystem B method called');
}
}
class Facade {
constructor(subsystemA, subsystemB) {
this.subsystemA = subsystemA;
this.subsystemB = subsystemB;
}
executeComplexWorkflow() {
this.subsystemA.methodA();
this.subsystemB.methodB();
console.log('Additional logic in Facade');
}
}
const subsystemA = new SubsystemA();
const subsystemB = new SubsystemB();
const facade = new Facade(subsystemA, subsystemB);
facade.executeComplexWorkflow();
// Output:
// Subsystem A method called
// Subsystem B method called
// Additional logic in FacadeMediator Pattern
Defines a mediator object that encapsulates interactions between a set of objects, reducing coupling and promoting loose coupling between them.
class Colleague {
constructor(mediator) {
this.mediator = mediator;
}
send(message) {
this.mediator.send(message, this);
}
receive(message, sender) {
console.log(`${this.constructor.name} received: ${message} from ${sender.constructor.name}`);
}
}
class ConcreteColleagueA extends Colleague {
doSomething() {
this.send('Action A');
}
}
class ConcreteColleagueB extends Colleague {
doSomething() {
this.send('Action B');
}
}
class Mediator {
constructor() {
this.colleagues = [];
}
register(colleague) {
this.colleagues.push(colleague);
}
send(message, sender) {
this.colleagues.forEach((colleague) => {
if (colleague !== sender) {
colleague.receive(message, sender);
}
});
}
}
const mediator = new Mediator();
const colleagueA = new ConcreteColleagueA(mediator);
const colleagueB = new ConcreteColleagueB(mediator);
mediator.register(colleagueA);
mediator.register(colleagueB);
colleagueA.doSomething();
// Output: ConcreteColleagueB received: Action A from ConcreteColleagueA
colleagueB.doSomething();
// Output: ConcreteColleagueA received: Action B from ConcreteColleagueBIterator Pattern
Provides a way to sequentially access the elements of an aggregate object without exposing its internal representation.
class Collection {
constructor(items = []) {
this.items = items;
}
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.items.length) {
return { value: this.items[index++], done: false };
} else {
return { done: true };
}
},
};
}
}
const collection = new Collection([1, 2, 3, 4, 5]);
for (const item of collection) {
console.log(item);
}
// Output:
// 1
// 2
// 3
// 4
// 5



