Design Patterns in JavaScript
Common design patterns and their applications
Challenge
Section titled “Challenge”Apply proven design patterns to write more maintainable, scalable, and reusable code.
Explanation
Section titled “Explanation”Design patterns are reusable solutions to common programming problems. They provide templates for solving issues.
Singleton Pattern
Section titled “Singleton Pattern”// Only one instance existsclass Database { static instance = null;
constructor() { if (Database.instance) { return Database.instance; }
this.connection = null; Database.instance = this; }
connect() { this.connection = 'connected'; }}
const db1 = new Database();const db2 = new Database();console.log(db1 === db2); // true (same instance)Factory Pattern
Section titled “Factory Pattern”// Create objects without specifying exact classesclass CarFactory { static createCar(type) { switch(type) { case 'sedan': return new Sedan(); case 'suv': return new SUV(); default: throw new Error('Unknown type'); } }}
class Sedan { drive() { return 'Driving sedan'; }}
class SUV { drive() { return 'Driving SUV'; }}
const car = CarFactory.createCar('sedan');console.log(car.drive()); // 'Driving sedan'Observer Pattern
Section titled “Observer Pattern”// Notify multiple subscribers of eventsclass EventEmitter { constructor() { this.events = {}; }
on(event, handler) { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(handler); }
emit(event, ...args) { if (this.events[event]) { this.events[event].forEach(handler => handler(...args)); } }}
const emitter = new EventEmitter();emitter.on('user-login', (user) => { console.log(`${user} logged in`);});
emitter.emit('user-login', 'John');Strategy Pattern
Section titled “Strategy Pattern”// Different algorithms for same taskclass PaymentProcessor { constructor(strategy) { this.strategy = strategy; }
process(amount) { return this.strategy.pay(amount); }}
class CreditCardStrategy { pay(amount) { return `Paid ${amount} via credit card`; }}
class PayPalStrategy { pay(amount) { return `Paid ${amount} via PayPal`; }}
const processor = new PaymentProcessor(new CreditCardStrategy());console.log(processor.process(100)); // 'Paid 100 via credit card'
processor.strategy = new PayPalStrategy();console.log(processor.process(100)); // 'Paid 100 via PayPal'Module Pattern
Section titled “Module Pattern”// Encapsulate private and public interfacesconst CounterModule = (() => { let count = 0; // Private
return { increment() { // Public count++; return count; }, decrement() { // Public count--; return count; } };})();
console.log(CounterModule.increment()); // 1console.log(CounterModule.increment()); // 2// count is not accessible directlyAdapter Pattern
Section titled “Adapter Pattern”// Make incompatible interfaces work togetherclass OldAPI { getOldData() { return { name: 'John', age: 30 }; }}
class APIAdapter { constructor(oldAPI) { this.oldAPI = oldAPI; }
getNewData() { const data = this.oldAPI.getOldData(); return { fullName: data.name, userAge: data.age }; }}
const oldAPI = new OldAPI();const adapter = new APIAdapter(oldAPI);console.log(adapter.getNewData());// { fullName: 'John', userAge: 30 }Decorator Pattern
Section titled “Decorator Pattern”// Add behavior to objects dynamicallyclass Component { render() { return 'Component'; }}
function withBorder(component) { const originalRender = component.render.bind(component); return { render() { return `[Border: ${originalRender()}]`; } };}
function withShadow(component) { const originalRender = component.render.bind(component); return { render() { return `[Shadow: ${originalRender()}]`; } };}
let comp = new Component();comp = withBorder(comp);comp = withShadow(comp);console.log(comp.render()); // '[Shadow: [Border: Component]]'Middleware Pattern
Section titled “Middleware Pattern”// Chain operations/transformationsclass Pipeline { constructor() { this.middlewares = []; }
use(middleware) { this.middlewares.push(middleware); return this; }
async execute(data) { let result = data; for (const middleware of this.middlewares) { result = await middleware(result); } return result; }}
const pipeline = new Pipeline();pipeline .use(async (data) => { data.step1 = 'done'; return data; }) .use(async (data) => { data.step2 = 'done'; return data; });
pipeline.execute({}).then(console.log);// { step1: 'done', step2: 'done' }