Architectural Design

JavaScript Design Patterns

Design patterns are not just templatesthey are professional solutions to recurring architectural challenges. Master the patterns that separate spaghetti code from scalable, enterprise-grade JavaScript.

Creational Patterns: Object Lifecycle

Creational patterns provide mechanisms for object creation that increase flexibility and reuse of existing code. In modern JavaScript, the **Singleton** is often implemented via ES Modules, while the **Factory** pattern remains essential for decoupling object usage from its construction logic.

javascript
// 1. Singleton (Modern ESM Approach)
// The module system itself caches exports, making this the cleanest Singleton.
class DatabaseService {
    constructor() {
        this.connection = null;
    }
    
    connect() {
        if (!this.connection) {
            this.connection = "Connected to DB";
            console.log("New connection established");
        }
        return this.connection;
    }
}

export const db = new DatabaseService(); // Single instance shared across app

// 2. Factory Pattern (Decoupling Creation)
class NotificationFactory {
    static create(type) {
        switch (type) {
            case 'email': return new EmailProvider();
            case 'sms':   return new SMSProvider();
            default:      throw new Error("Unknown provider");
        }
    }
}

Behavioral Patterns: Communication

Behavioral patterns focus on how objects communicate and interact. The **Observer** pattern (or EventEmitter) forms the backbone of event-driven systems like Node.js and React, while the **Strategy** pattern allows you to swap algorithms at runtime without modifying the calling code.

javascript
// 1. Observer Pattern (The Event Bus)
class EventEmitter {
    constructor() { this.events = {}; }
    
    on(event, listener) {
        (this.events[event] || (this.events[event] = [])).push(listener);
    }
    
    emit(event, ...data) {
        (this.events[event] || []).forEach(fn => fn(...data));
    }
}

// 2. Strategy Pattern (Interchangeable Logic)
const paymentStrategies = {
    stripe: (amt) => console.log(`Processing ${amt} via Stripe`),
    paypal: (amt) => console.log(`Processing ${amt} via PayPal`)
};

function checkout(amount, strategy) {
    paymentStrategies[strategy](amount);
}

Structural Patterns: Relationship Management

Structural patterns explain how to assemble objects and classes into larger structures while keeping them flexible and efficient. The **Proxy** pattern is a modern favorite (and a core part of Vue's reactivity system), allowing you to intercept and validate operations on an object before they reach the target.

javascript
// Proxy Pattern (Validation/Logging Layer)
const user = { name: "Neo", role: "user" };

const adminProxy = new Proxy(user, {
    set(target, prop, value) {
        if (prop === "role" && value === "admin") {
            console.error("Unauthorized: Role escalation blocked.");
            return false;
        }
        target[prop] = value;
        return true;
    }
});

adminProxy.role = "admin"; // Fails: Unauthorized escalation.

Pro-Tip

Senior engineers understand that patterns come with a "Complexity Tax." While the Factory pattern adds flexibility, it also adds an extra layer of abstraction. Always apply the **YAGNI** (You Ain't Gonna Need It) principle: start with simple objects and refactor into patterns only when the need for scalability or decoupling becomes evident.

Key Facts & Engineering Context

  • **Singleton:** Use ES Modules for clean, cached single instances.
  • **Factory:** Use when the exact type of object can only be determined at runtime.
  • **Observer:** Ideal for decoupling UI components from business logic.
  • **Proxy:** Use for logging, validation, and creating reactive data structures.
  • **Anti-Pattern:** Avoid nesting patterns too deeply, as it obscures the data flow.