JavaScript Design Patterns

1. Module Patterns and Namespace Management

Module Pattern Types

Pattern Purpose Usage
IIFE Module Encapsulation with closure Pre-ES6 modules
Revealing Module Explicit public API Clear interface definition
ES6 Modules Native import/export Modern JavaScript standard
CommonJS Node.js modules require/module.exports
Namespace Object Group related functionality Avoid global pollution

ES6 Module Syntax

Syntax Description Example
export Named export export const fn = () => {}
export default Default export export default class {}
import Import named exports import {fn} from './module'
import default Import default export import MyClass from './module'
import * as Import namespace import * as utils from './utils'
export {} Re-export export {fn} from './module'

Module Benefits

Benefit Description Impact
Encapsulation Hide internal implementation Prevent unwanted access
Reusability Share code across projects DRY principle
Maintainability Organized code structure Easier to debug and update
Dependency Management Explicit dependencies Clear module relationships
Namespace Avoid global scope pollution Prevent naming conflicts

Example: Module patterns

// IIFE Module Pattern (pre-ES6)
const myModule = (function() {
    // Private variables and functions
    let privateVar = 'I am private';
    
    function privateMethod() {
        console.log(privateVar);
    }
    
    // Public API
    return {
        publicMethod: function() {
            privateMethod();
        },
        publicVar: 'I am public'
    };
})();

myModule.publicMethod();  // Works
// myModule.privateVar  // undefined (not accessible)

// Revealing Module Pattern
const calculator = (function() {
    // Private
    function add(a, b) {
        return a + b;
    }
    
    function subtract(a, b) {
        return a - b;
    }
    
    function multiply(a, b) {
        return a * b;
    }
    
    // Reveal public API
    return {
        add: add,
        subtract: subtract
        // multiply is private
    };
})();

calculator.add(2, 3);       // 5
calculator.subtract(5, 2);  // 3
// calculator.multiply(2, 3)  // Error: not exposed

// Namespace Pattern
const MyApp = MyApp || {};

MyApp.Utils = {
    formatDate: function(date) {
        return date.toLocaleDateString();
    },
    
    formatCurrency: function(amount) {
        return `${amount.toFixed(2)}`;
    }
};

MyApp.Models = {
    User: class {
        constructor(name) {
            this.name = name;
        }
    }
};

MyApp.Controllers = {
    UserController: class {
        createUser(name) {
            return new MyApp.Models.User(name);
        }
    }
};

// Usage
const formatted = MyApp.Utils.formatCurrency(99.99);
const user = new MyApp.Models.User('John');

// ES6 Modules
// math.js
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

export const PI = 3.14159;

// Default export
export default function multiply(a, b) {
    return a * b;
}

// Import in another file
import multiply, {add, subtract, PI} from './math.js';

console.log(add(2, 3));        // 5
console.log(multiply(2, 3));   // 6
console.log(PI);               // 3.14159

// Import all as namespace
import * as math from './math.js';

console.log(math.add(2, 3));
console.log(math.default(2, 3));  // multiply

// Re-export pattern (index.js)
export {add, subtract} from './math.js';
export {User} from './user.js';
export {default as multiply} from './math.js';

// Lazy loading modules
async function loadModule() {
    const module = await import('./heavy-module.js');
    module.doSomething();
}

// Module with initialization
// logger.js
class Logger {
    constructor() {
        this.logs = [];
    }
    
    log(message) {
        this.logs.push({
            message,
            timestamp: Date.now()
        });
        console.log(message);
    }
    
    getLogs() {
        return this.logs;
    }
}

// Export singleton instance
export default new Logger();

// Usage
import logger from './logger.js';

logger.log('Application started');
logger.log('User logged in');

// Dependency injection with modules
// services.js
export class ApiService {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async get(endpoint) {
        const response = await fetch(`${this.baseUrl}${endpoint}`);
        return response.json();
    }
}

export class AuthService {
    constructor(apiService) {
        this.api = apiService;
    }
    
    async login(credentials) {
        return this.api.get('/auth/login', credentials);
    }
}

// app.js
import {ApiService, AuthService} from './services.js';

const api = new ApiService('https://api.example.com');
const auth = new AuthService(api);

await auth.login({username: 'john', password: 'pass123'});
Key Points: Module pattern uses IIFE for encapsulation. Revealing module pattern explicitly defines public API. ES6 modules with import/export are standard. Use export default for main export. Namespace objects prevent global pollution. Dynamic imports for lazy loading. Modules provide encapsulation, reusability, maintainability.

2. Observer Pattern and Event Emitters

Observer Pattern Components

Component Role Responsibility
Subject Observable Maintains observers list, notifies on change
Observer Subscriber Receives notifications, reacts to changes
ConcreteSubject Specific observable Stores state, triggers notifications
ConcreteObserver Specific subscriber Implements update logic

Event Emitter Methods

Method Purpose Usage
on(event, handler) Register listener Subscribe to event
off(event, handler) Remove listener Unsubscribe from event
emit(event, data) Trigger event Notify all listeners
once(event, handler) One-time listener Auto-remove after first call
removeAllListeners() Clear all listeners Cleanup

Observer Pattern Benefits

Benefit Description Use Case
Loose Coupling Subject and observers independent Flexible system architecture
Dynamic Relationships Add/remove observers at runtime Plugins, extensions
Broadcast Communication One-to-many notification State changes, events
Separation of Concerns Observers handle own logic Clean code organization

Example: Observer pattern and Event Emitter

// Classic Observer Pattern
class Subject {
    constructor() {
        this.observers = [];
    }
    
    attach(observer) {
        if (!this.observers.includes(observer)) {
            this.observers.push(observer);
        }
    }
    
    detach(observer) {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
            this.observers.splice(index, 1);
        }
    }
    
    notify(data) {
        this.observers.forEach(observer => {
            observer.update(data);
        });
    }
}

class Observer {
    update(data) {
        console.log('Observer received:', data);
    }
}

// Usage
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.attach(observer1);
subject.attach(observer2);

subject.notify({message: 'Hello observers!'});

subject.detach(observer1);
subject.notify({message: 'Only observer2 gets this'});

// Event Emitter Pattern
class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, handler) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(handler);
        
        // Return unsubscribe function
        return () => this.off(event, handler);
    }
    
    off(event, handler) {
        if (!this.events[event]) return;
        
        this.events[event] = this.events[event].filter(h => h !== handler);
    }
    
    emit(event, data) {
        if (!this.events[event]) return;
        
        this.events[event].forEach(handler => {
            handler(data);
        });
    }
    
    once(event, handler) {
        const onceHandler = (data) => {
            handler(data);
            this.off(event, onceHandler);
        };
        
        this.on(event, onceHandler);
    }
    
    removeAllListeners(event) {
        if (event) {
            delete this.events[event];
        } else {
            this.events = {};
        }
    }
    
    listenerCount(event) {
        return this.events[event] ? this.events[event].length : 0;
    }
}

// Usage
const emitter = new EventEmitter();

const unsubscribe = emitter.on('data', (data) => {
    console.log('Data received:', data);
});

emitter.emit('data', {value: 42});

// One-time listener
emitter.once('complete', () => {
    console.log('Completed!');
});

emitter.emit('complete');  // Logs "Completed!"
emitter.emit('complete');  // Nothing happens

// Unsubscribe
unsubscribe();

// Real-world example: Store with observers
class Store extends EventEmitter {
    constructor(initialState = {}) {
        super();
        this.state = initialState;
    }
    
    getState() {
        return this.state;
    }
    
    setState(updates) {
        const prevState = {...this.state};
        this.state = {...this.state, ...updates};
        
        this.emit('change', {
            state: this.state,
            prevState: prevState
        });
    }
    
    subscribe(handler) {
        return this.on('change', handler);
    }
}

// Usage
const store = new Store({count: 0, user: null});

const unsubscribeStore = store.subscribe(({state, prevState}) => {
    console.log('State changed from', prevState, 'to', state);
});

store.setState({count: 1});
store.setState({user: {name: 'John'}});

// Real-world: UI Component with events
class Button extends EventEmitter {
    constructor(label) {
        super();
        this.label = label;
        this.element = null;
    }
    
    render() {
        this.element = document.createElement('button');
        this.element.textContent = this.label;
        
        this.element.addEventListener('click', () => {
            this.emit('click', {label: this.label});
        });
        
        return this.element;
    }
    
    setText(label) {
        this.label = label;
        if (this.element) {
            this.element.textContent = label;
        }
        this.emit('textChange', {label});
    }
}

// Usage
const button = new Button('Click Me');

button.on('click', ({label}) => {
    console.log(`Button "${label}" clicked`);
});

button.on('textChange', ({label}) => {
    console.log(`Button text changed to "${label}"`);
});

document.body.appendChild(button.render());

// Async event emitter
class AsyncEventEmitter extends EventEmitter {
    async emit(event, data) {
        if (!this.events[event]) return;
        
        await Promise.all(
            this.events[event].map(handler => handler(data))
        );
    }
}

const asyncEmitter = new AsyncEventEmitter();

asyncEmitter.on('fetch', async (url) => {
    const response = await fetch(url);
    console.log('Fetched:', response.status);
});

await asyncEmitter.emit('fetch', 'https://api.example.com/data');

// Event namespacing
class NamespacedEventEmitter extends EventEmitter {
    emit(event, data) {
        // Emit specific event
        super.emit(event, data);
        
        // Emit namespace events
        const parts = event.split(':');
        for (let i = 1; i < parts.length; i++) {
            const namespace = parts.slice(0, i).join(':');
            super.emit(namespace, data);
        }
    }
}

const nsEmitter = new NamespacedEventEmitter();

nsEmitter.on('user', (data) => {
    console.log('Any user event:', data);
});

nsEmitter.on('user:login', (data) => {
    console.log('User login:', data);
});

nsEmitter.emit('user:login', {id: 123});
// Triggers both 'user' and 'user:login' handlers
Key Points: Observer pattern enables one-to-many communication. Subject maintains observers list, notifies on changes. Event emitter with on/off/emit methods. once() for one-time listeners. Return unsubscribe function from on(). Useful for state management, UI components, pub/sub systems. Loose coupling between components.

3. Factory Pattern and Builder Pattern

Factory Pattern Types

Type Description Use Case
Simple Factory Single factory function Basic object creation
Factory Method Subclasses decide type Polymorphic creation
Abstract Factory Family of related objects Platform-specific objects
Static Factory Static method creates instances Named constructors

Builder Pattern Benefits

Benefit Description When to Use
Fluent Interface Chainable method calls Readable construction code
Immutability Build once, immutable result Thread-safe objects
Complex Construction Step-by-step building Objects with many parameters
Validation Validate before creation Ensure valid state

Factory vs Constructor

Aspect Factory Constructor
Return type Can return any type Always returns instance
Naming Descriptive names Class name only
Caching Can return cached instances Always creates new
Polymorphism Return different subtypes Fixed type

Example: Factory patterns

// Simple Factory
class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }
    
    getPermissions() {
        return [];
    }
}

class Admin extends User {
    getPermissions() {
        return ['read', 'write', 'delete'];
    }
}

class Guest extends User {
    getPermissions() {
        return ['read'];
    }
}

// Factory function
function createUser(name, role) {
    switch (role) {
        case 'admin':
            return new Admin(name, role);
        case 'guest':
            return new Guest(name, role);
        default:
            return new User(name, role);
    }
}

// Usage
const admin = createUser('John', 'admin');
console.log(admin.getPermissions());  // ['read', 'write', 'delete']

const guest = createUser('Jane', 'guest');
console.log(guest.getPermissions());  // ['read']

// Factory Method Pattern
class Document {
    constructor(title) {
        this.title = title;
    }
    
    render() {
        throw new Error('Must implement render()');
    }
}

class PDFDocument extends Document {
    render() {
        return `PDF: ${this.title}`;
    }
}

class WordDocument extends Document {
    render() {
        return `Word: ${this.title}`;
    }
}

class DocumentFactory {
    createDocument(type, title) {
        switch (type) {
            case 'pdf':
                return new PDFDocument(title);
            case 'word':
                return new WordDocument(title);
            default:
                throw new Error('Unknown document type');
        }
    }
}

// Usage
const factory = new DocumentFactory();
const pdf = factory.createDocument('pdf', 'Report');
console.log(pdf.render());

// Static Factory Methods
class Color {
    constructor(r, g, b) {
        this.r = r;
        this.g = g;
        this.b = b;
    }
    
    static fromRGB(r, g, b) {
        return new Color(r, g, b);
    }
    
    static fromHex(hex) {
        const r = parseInt(hex.slice(1, 3), 16);
        const g = parseInt(hex.slice(3, 5), 16);
        const b = parseInt(hex.slice(5, 7), 16);
        return new Color(r, g, b);
    }
    
    static red() {
        return new Color(255, 0, 0);
    }
    
    static green() {
        return new Color(0, 255, 0);
    }
    
    static blue() {
        return new Color(0, 0, 255);
    }
}

// Usage
const color1 = Color.fromRGB(255, 128, 0);
const color2 = Color.fromHex('#FF8000');
const red = Color.red();

// Object Pool Factory
class ObjectPool {
    constructor(factory, maxSize = 10) {
        this.factory = factory;
        this.maxSize = maxSize;
        this.available = [];
        this.inUse = [];
    }
    
    acquire() {
        let obj;
        
        if (this.available.length > 0) {
            obj = this.available.pop();
        } else if (this.inUse.length < this.maxSize) {
            obj = this.factory();
        } else {
            throw new Error('Pool exhausted');
        }
        
        this.inUse.push(obj);
        return obj;
    }
    
    release(obj) {
        const index = this.inUse.indexOf(obj);
        if (index > -1) {
            this.inUse.splice(index, 1);
            this.available.push(obj);
        }
    }
}

// Usage
const connectionPool = new ObjectPool(
    () => ({id: Math.random(), connected: true}),
    5
);

const conn1 = connectionPool.acquire();
const conn2 = connectionPool.acquire();

connectionPool.release(conn1);

// Abstract Factory Pattern
class Button {
    render() {
        throw new Error('Must implement');
    }
}

class WindowsButton extends Button {
    render() {
        return '<button class="windows">Click</button>';
    }
}

class MacButton extends Button {
    render() {
        return '<button class="mac">Click</button>';
    }
}

class Checkbox {
    render() {
        throw new Error('Must implement');
    }
}

class WindowsCheckbox extends Checkbox {
    render() {
        return '<input type="checkbox" class="windows">';
    }
}

class MacCheckbox extends Checkbox {
    render() {
        return '<input type="checkbox" class="mac">';
    }
}

// Abstract Factory
class UIFactory {
    createButton() {
        throw new Error('Must implement');
    }
    
    createCheckbox() {
        throw new Error('Must implement');
    }
}

class WindowsFactory extends UIFactory {
    createButton() {
        return new WindowsButton();
    }
    
    createCheckbox() {
        return new WindowsCheckbox();
    }
}

class MacFactory extends UIFactory {
    createButton() {
        return new MacButton();
    }
    
    createCheckbox() {
        return new MacCheckbox();
    }
}

// Usage
function createUI(factory) {
    const button = factory.createButton();
    const checkbox = factory.createCheckbox();
    
    return {
        button: button.render(),
        checkbox: checkbox.render()
    };
}

const windowsFactory = new WindowsFactory();
const windowsUI = createUI(windowsFactory);

const macFactory = new MacFactory();
const macUI = createUI(macFactory);

Example: Builder pattern

// Builder Pattern
class User2 {
    constructor(builder) {
        this.name = builder.name;
        this.email = builder.email;
        this.age = builder.age;
        this.address = builder.address;
        this.phone = builder.phone;
    }
}

class UserBuilder {
    constructor(name) {
        this.name = name;
    }
    
    withEmail(email) {
        this.email = email;
        return this;  // Return this for chaining
    }
    
    withAge(age) {
        this.age = age;
        return this;
    }
    
    withAddress(address) {
        this.address = address;
        return this;
    }
    
    withPhone(phone) {
        this.phone = phone;
        return this;
    }
    
    build() {
        // Validation
        if (!this.email) {
            throw new Error('Email is required');
        }
        
        return new User2(this);
    }
}

// Usage (fluent interface)
const user = new UserBuilder('John Doe')
    .withEmail('john@example.com')
    .withAge(30)
    .withAddress('123 Main St')
    .withPhone('555-1234')
    .build();

console.log(user);

// Query Builder
class QueryBuilder {
    constructor() {
        this.query = {
            table: null,
            fields: ['*'],
            conditions: [],
            orderBy: null,
            limit: null
        };
    }
    
    select(...fields) {
        this.query.fields = fields;
        return this;
    }
    
    from(table) {
        this.query.table = table;
        return this;
    }
    
    where(condition) {
        this.query.conditions.push(condition);
        return this;
    }
    
    orderBy(field, direction = 'ASC') {
        this.query.orderBy = {field, direction};
        return this;
    }
    
    limit(count) {
        this.query.limit = count;
        return this;
    }
    
    build() {
        let sql = `SELECT ${this.query.fields.join(', ')}`;
        sql += ` FROM ${this.query.table}`;
        
        if (this.query.conditions.length > 0) {
            sql += ` WHERE ${this.query.conditions.join(' AND ')}`;
        }
        
        if (this.query.orderBy) {
            sql += ` ORDER BY ${this.query.orderBy.field} ${this.query.orderBy.direction}`;
        }
        
        if (this.query.limit) {
            sql += ` LIMIT ${this.query.limit}`;
        }
        
        return sql;
    }
}

// Usage
const query = new QueryBuilder()
    .select('id', 'name', 'email')
    .from('users')
    .where('age > 18')
    .where('status = "active"')
    .orderBy('name', 'ASC')
    .limit(10)
    .build();

console.log(query);
// SELECT id, name, email FROM users WHERE age > 18 AND status = "active" ORDER BY name ASC LIMIT 10

// HTML Builder
class HTMLBuilder {
    constructor(tag) {
        this.element = {
            tag: tag,
            attributes: {},
            children: [],
            text: null
        };
    }
    
    attr(name, value) {
        this.element.attributes[name] = value;
        return this;
    }
    
    text(content) {
        this.element.text = content;
        return this;
    }
    
    child(builder) {
        if (builder instanceof HTMLBuilder) {
            this.element.children.push(builder.element);
        } else {
            this.element.children.push(builder);
        }
        return this;
    }
    
    build() {
        let html = `<${this.element.tag}`;
        
        // Add attributes
        for (const [key, value] of Object.entries(this.element.attributes)) {
            html += ` ${key}="${value}"`;
        }
        
        html += '>';
        
        // Add text or children
        if (this.element.text) {
            html += this.element.text;
        } else {
            this.element.children.forEach(child => {
                if (typeof child === 'string') {
                    html += child;
                } else {
                    html += new HTMLBuilder(child.tag)
                        .attr(Object.keys(child.attributes)[0], Object.values(child.attributes)[0])
                        .text(child.text)
                        .build();
                }
            });
        }
        
        html += `</${this.element.tag}>`;
        
        return html;
    }
}

// Usage
const html = new HTMLBuilder('div')
    .attr('class', 'container')
    .child(
        new HTMLBuilder('h1')
            .attr('class', 'title')
            .text('Hello World')
    )
    .child(
        new HTMLBuilder('p')
            .text('This is a paragraph')
    )
    .build();

console.log(html);
// <div class="container"><h1 class="title">Hello World</h1><p>This is a paragraph</p></div>
Key Points: Factory pattern encapsulates object creation logic. Use static factory methods for named constructors. Abstract factory creates families of related objects. Builder pattern uses fluent interface for complex construction. Builder validates before creating object. Good for objects with many optional parameters. Both patterns improve code maintainability.

4. Singleton Pattern and Registry Pattern

Singleton Characteristics

Characteristic Description Implementation
Single Instance Only one instance exists Private constructor or closure
Global Access Accessible from anywhere Static getInstance() method
Lazy Initialization Created only when needed Check if instance exists
Thread Safety Safe in multi-threaded env Synchronization (JS single-threaded)

Registry Pattern Uses

Use Case Purpose Example
Service Locator Central service registry DI container
Plugin System Register/retrieve plugins Extension manager
Factory Registry Register factory functions Component registry
Event Registry Central event handlers Event bus

Singleton vs Global Variable

Aspect Singleton Global Variable
Instantiation Lazy, controlled Immediate
Encapsulation Private members possible All public
Inheritance Can extend classes Cannot extend
Testability Can be mocked Hard to test

Example: Singleton pattern

// Classic Singleton (ES6 class)
class Database {
    constructor() {
        if (Database.instance) {
            return Database.instance;
        }
        
        this.connection = null;
        this.connected = false;
        
        Database.instance = this;
    }
    
    connect(connectionString) {
        if (!this.connected) {
            this.connection = connectionString;
            this.connected = true;
            console.log('Database connected');
        }
    }
    
    query(sql) {
        if (!this.connected) {
            throw new Error('Not connected');
        }
        console.log(`Executing: ${sql}`);
        return [];
    }
    
    disconnect() {
        if (this.connected) {
            this.connection = null;
            this.connected = false;
            console.log('Database disconnected');
        }
    }
}

// Usage
const db1 = new Database();
db1.connect('mongodb://localhost');

const db2 = new Database();
console.log(db1 === db2);  // true (same instance)

// Module Singleton (Using ES6 modules)
// logger.js
class Logger {
    constructor() {
        this.logs = [];
    }
    
    log(message) {
        const entry = {
            message,
            timestamp: new Date(),
            level: 'info'
        };
        this.logs.push(entry);
        console.log(`[${entry.timestamp.toISOString()}] ${message}`);
    }
    
    error(message) {
        const entry = {
            message,
            timestamp: new Date(),
            level: 'error'
        };
        this.logs.push(entry);
        console.error(`[${entry.timestamp.toISOString()}] ERROR: ${message}`);
    }
    
    getLogs() {
        return this.logs;
    }
    
    clear() {
        this.logs = [];
    }
}

// Export singleton instance
export default new Logger();

// Usage in other files
// import logger from './logger.js';
// logger.log('Application started');

// Singleton with getInstance()
class Config {
    static instance = null;
    
    constructor() {
        if (Config.instance) {
            throw new Error('Use Config.getInstance()');
        }
        
        this.settings = {};
    }
    
    static getInstance() {
        if (!Config.instance) {
            Config.instance = new Config();
        }
        return Config.instance;
    }
    
    set(key, value) {
        this.settings[key] = value;
    }
    
    get(key) {
        return this.settings[key];
    }
    
    getAll() {
        return {...this.settings};
    }
}

// Usage
const config1 = Config.getInstance();
config1.set('apiUrl', 'https://api.example.com');

const config2 = Config.getInstance();
console.log(config2.get('apiUrl'));  // https://api.example.com
console.log(config1 === config2);    // true

// Closure-based Singleton
const AppState = (function() {
    let instance;
    
    function createInstance() {
        const state = {
            user: null,
            settings: {},
            cache: new Map()
        };
        
        return {
            getState() {
                return state;
            },
            
            setUser(user) {
                state.user = user;
            },
            
            getUser() {
                return state.user;
            },
            
            setSetting(key, value) {
                state.settings[key] = value;
            },
            
            getSetting(key) {
                return state.settings[key];
            }
        };
    }
    
    return {
        getInstance() {
            if (!instance) {
                instance = createInstance();
            }
            return instance;
        }
    };
})();

// Usage
const state1 = AppState.getInstance();
state1.setUser({name: 'John', id: 123});

const state2 = AppState.getInstance();
console.log(state2.getUser());  // {name: 'John', id: 123}

// Singleton with initialization options
class Cache {
    static instances = new Map();
    
    constructor(name, options = {}) {
        const key = name;
        
        if (Cache.instances.has(key)) {
            return Cache.instances.get(key);
        }
        
        this.name = name;
        this.maxSize = options.maxSize || 100;
        this.ttl = options.ttl || 60000;  // 1 minute
        this.store = new Map();
        
        Cache.instances.set(key, this);
    }
    
    set(key, value) {
        if (this.store.size >= this.maxSize) {
            const firstKey = this.store.keys().next().value;
            this.store.delete(firstKey);
        }
        
        this.store.set(key, {
            value,
            expires: Date.now() + this.ttl
        });
    }
    
    get(key) {
        const item = this.store.get(key);
        
        if (!item) return null;
        
        if (Date.now() > item.expires) {
            this.store.delete(key);
            return null;
        }
        
        return item.value;
    }
    
    clear() {
        this.store.clear();
    }
}

// Usage - named singletons
const userCache = new Cache('users', {maxSize: 50});
const productCache = new Cache('products', {maxSize: 100});

userCache.set('user:1', {name: 'John'});

const userCache2 = new Cache('users');
console.log(userCache2.get('user:1'));  // {name: 'John'}
console.log(userCache === userCache2);  // true

Example: Registry pattern

// Service Registry / Dependency Injection Container
class ServiceRegistry {
    constructor() {
        this.services = new Map();
        this.singletons = new Map();
    }
    
    register(name, factory, options = {}) {
        this.services.set(name, {
            factory,
            singleton: options.singleton || false,
            dependencies: options.dependencies || []
        });
    }
    
    resolve(name) {
        const service = this.services.get(name);
        
        if (!service) {
            throw new Error(`Service '${name}' not registered`);
        }
        
        // Return singleton if already created
        if (service.singleton && this.singletons.has(name)) {
            return this.singletons.get(name);
        }
        
        // Resolve dependencies
        const dependencies = service.dependencies.map(dep => this.resolve(dep));
        
        // Create instance
        const instance = service.factory(...dependencies);
        
        // Store singleton
        if (service.singleton) {
            this.singletons.set(name, instance);
        }
        
        return instance;
    }
    
    has(name) {
        return this.services.has(name);
    }
    
    clear() {
        this.services.clear();
        this.singletons.clear();
    }
}

// Usage
const registry = new ServiceRegistry();

// Register services
registry.register('database', () => {
    return {
        query: (sql) => console.log('Query:', sql)
    };
}, {singleton: true});

registry.register('userService', (db) => {
    return {
        getUser: (id) => {
            db.query(`SELECT * FROM users WHERE id = ${id}`);
            return {id, name: 'John'};
        }
    };
}, {dependencies: ['database']});

// Resolve services
const userService = registry.resolve('userService');
userService.getUser(123);

// Plugin Registry
class PluginRegistry {
    constructor() {
        this.plugins = new Map();
        this.hooks = new Map();
    }
    
    register(name, plugin) {
        if (this.plugins.has(name)) {
            throw new Error(`Plugin '${name}' already registered`);
        }
        
        this.plugins.set(name, plugin);
        
        // Initialize plugin
        if (typeof plugin.init === 'function') {
            plugin.init(this);
        }
        
        return this;
    }
    
    get(name) {
        return this.plugins.get(name);
    }
    
    has(name) {
        return this.plugins.has(name);
    }
    
    unregister(name) {
        const plugin = this.plugins.get(name);
        
        if (plugin && typeof plugin.destroy === 'function') {
            plugin.destroy();
        }
        
        return this.plugins.delete(name);
    }
    
    // Hook system
    registerHook(hookName, callback) {
        if (!this.hooks.has(hookName)) {
            this.hooks.set(hookName, []);
        }
        this.hooks.get(hookName).push(callback);
    }
    
    executeHook(hookName, data) {
        const callbacks = this.hooks.get(hookName) || [];
        
        let result = data;
        for (const callback of callbacks) {
            result = callback(result);
        }
        
        return result;
    }
}

// Usage
const plugins = new PluginRegistry();

// Register plugins
plugins.register('markdown', {
    init(registry) {
        console.log('Markdown plugin initialized');
        registry.registerHook('beforeRender', (text) => {
            return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
        });
    },
    
    render(text) {
        return text;
    },
    
    destroy() {
        console.log('Markdown plugin destroyed');
    }
});

plugins.register('emoji', {
    init(registry) {
        registry.registerHook('beforeRender', (text) => {
            return text.replace(/:smile:/g, '😊');
        });
    }
});

// Execute hooks
let content = 'Hello **world** :smile:';
content = plugins.executeHook('beforeRender', content);
console.log(content);  // Hello <strong>world</strong> 😊

// Component Registry
class ComponentRegistry {
    constructor() {
        this.components = new Map();
    }
    
    register(name, component) {
        this.components.set(name, component);
        return this;
    }
    
    create(name, props = {}) {
        const Component = this.components.get(name);
        
        if (!Component) {
            throw new Error(`Component '${name}' not registered`);
        }
        
        if (typeof Component === 'function') {
            return new Component(props);
        }
        
        return Component;
    }
    
    getAll() {
        return Array.from(this.components.keys());
    }
}

// Usage
const components = new ComponentRegistry();

components.register('Button', class {
    constructor(props) {
        this.label = props.label || 'Click';
    }
    
    render() {
        return `<button>${this.label}</button>`;
    }
});

components.register('Input', class {
    constructor(props) {
        this.type = props.type || 'text';
        this.placeholder = props.placeholder || '';
    }
    
    render() {
        return `<input type="${this.type}" placeholder="${this.placeholder}">`;
    }
});

const button = components.create('Button', {label: 'Submit'});
console.log(button.render());

const input = components.create('Input', {placeholder: 'Enter name'});
console.log(input.render());

console.log('Registered components:', components.getAll());
Key Points: Singleton pattern ensures single instance with global access. Use constructor check or getInstance() method. ES6 module exports create natural singletons. Registry pattern manages collections of objects. Service registry for dependency injection. Plugin registry for extensible systems. Component registry for dynamic creation.

5. Strategy Pattern and Command Pattern

Strategy Pattern Components

Component Role Responsibility
Context Uses strategy Maintains reference to strategy
Strategy Interface Common interface Defines algorithm method
Concrete Strategy Implements algorithm Specific implementation

Command Pattern Elements

Element Purpose Implementation
Command Encapsulates action execute() method
Receiver Performs action Business logic
Invoker Triggers command Calls execute()
Client Creates command Binds receiver to command

Pattern Benefits

Pattern Key Benefit Use Case
Strategy Runtime algorithm selection Sorting, validation, payment methods
Command Undo/redo, queuing, logging Text editors, transactions, macros

Example: Strategy pattern

// Strategy Pattern - Sorting algorithms
class SortStrategy {
    sort(array) {
        throw new Error('Must implement sort()');
    }
}

class BubbleSortStrategy extends SortStrategy {
    sort(array) {
        const arr = [...array];
        for (let i = 0; i < arr.length; i++) {
            for (let j = 0; j < arr.length - 1 - i; j++) {
                if (arr[j] > arr[j + 1]) {
                    [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
                }
            }
        }
        return arr;
    }
}

class QuickSortStrategy extends SortStrategy {
    sort(array) {
        if (array.length <= 1) return array;
        
        const pivot = array[Math.floor(array.length / 2)];
        const left = array.filter(x => x < pivot);
        const middle = array.filter(x => x === pivot);
        const right = array.filter(x => x > pivot);
        
        return [...this.sort(left), ...middle, ...this.sort(right)];
    }
}

class Sorter {
    constructor(strategy) {
        this.strategy = strategy;
    }
    
    setStrategy(strategy) {
        this.strategy = strategy;
    }
    
    sort(array) {
        console.log(`Sorting with ${this.strategy.constructor.name}`);
        return this.strategy.sort(array);
    }
}

// Usage
const data = [5, 2, 8, 1, 9];

const sorter = new Sorter(new BubbleSortStrategy());
console.log(sorter.sort(data));

sorter.setStrategy(new QuickSortStrategy());
console.log(sorter.sort(data));

// Strategy Pattern - Validation
class ValidationStrategy {
    validate(value) {
        throw new Error('Must implement validate()');
    }
}

class EmailValidation extends ValidationStrategy {
    validate(value) {
        const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        return regex.test(value);
    }
}

class PhoneValidation extends ValidationStrategy {
    validate(value) {
        const regex = /^\d{3}-\d{3}-\d{4}$/;
        return regex.test(value);
    }
}

class PasswordValidation extends ValidationStrategy {
    validate(value) {
        return value.length >= 8 && 
               /[A-Z]/.test(value) &&
               /[a-z]/.test(value) &&
               /[0-9]/.test(value);
    }
}

class Validator {
    constructor() {
        this.strategies = new Map();
    }
    
    addStrategy(field, strategy) {
        this.strategies.set(field, strategy);
    }
    
    validate(data) {
        const errors = {};
        
        for (const [field, strategy] of this.strategies) {
            const value = data[field];
            
            if (!strategy.validate(value)) {
                errors[field] = `Invalid ${field}`;
            }
        }
        
        return {
            valid: Object.keys(errors).length === 0,
            errors
        };
    }
}

// Usage
const validator = new Validator();
validator.addStrategy('email', new EmailValidation());
validator.addStrategy('phone', new PhoneValidation());
validator.addStrategy('password', new PasswordValidation());

const result = validator.validate({
    email: 'user@example.com',
    phone: '555-123-4567',
    password: 'Pass123'
});

console.log(result);  // {valid: true, errors: {}}

// Strategy Pattern - Payment methods
class PaymentStrategy {
    pay(amount) {
        throw new Error('Must implement pay()');
    }
}

class CreditCardPayment extends PaymentStrategy {
    constructor(cardNumber) {
        super();
        this.cardNumber = cardNumber;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} with credit card ${this.cardNumber}`);
        return {success: true, transactionId: Math.random()};
    }
}

class PayPalPayment extends PaymentStrategy {
    constructor(email) {
        super();
        this.email = email;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} via PayPal (${this.email})`);
        return {success: true, transactionId: Math.random()};
    }
}

class CryptoPayment extends PaymentStrategy {
    constructor(wallet) {
        super();
        this.wallet = wallet;
    }
    
    pay(amount) {
        console.log(`Paid ${amount} with crypto (${this.wallet})`);
        return {success: true, transactionId: Math.random()};
    }
}

class ShoppingCart {
    constructor() {
        this.items = [];
        this.paymentStrategy = null;
    }
    
    addItem(item) {
        this.items.push(item);
    }
    
    setPaymentStrategy(strategy) {
        this.paymentStrategy = strategy;
    }
    
    checkout() {
        const total = this.items.reduce((sum, item) => sum + item.price, 0);
        
        if (!this.paymentStrategy) {
            throw new Error('Payment method not set');
        }
        
        return this.paymentStrategy.pay(total);
    }
}

// Usage
const cart = new ShoppingCart();
cart.addItem({name: 'Book', price: 20});
cart.addItem({name: 'Pen', price: 5});

cart.setPaymentStrategy(new CreditCardPayment('1234-5678-9012-3456'));
cart.checkout();

cart.setPaymentStrategy(new PayPalPayment('user@example.com'));
cart.checkout();

Example: Command pattern

// Command Pattern - Text editor
class Command {
    execute() {
        throw new Error('Must implement execute()');
    }
    
    undo() {
        throw new Error('Must implement undo()');
    }
}

class InsertTextCommand extends Command {
    constructor(receiver, text, position) {
        super();
        this.receiver = receiver;
        this.text = text;
        this.position = position;
    }
    
    execute() {
        this.receiver.insertText(this.text, this.position);
    }
    
    undo() {
        this.receiver.deleteText(this.position, this.text.length);
    }
}

class DeleteTextCommand extends Command {
    constructor(receiver, position, length) {
        super();
        this.receiver = receiver;
        this.position = position;
        this.length = length;
        this.deletedText = null;
    }
    
    execute() {
        this.deletedText = this.receiver.deleteText(this.position, this.length);
    }
    
    undo() {
        this.receiver.insertText(this.deletedText, this.position);
    }
}

// Receiver
class TextEditor {
    constructor() {
        this.content = '';
    }
    
    insertText(text, position) {
        this.content = 
            this.content.slice(0, position) + 
            text + 
            this.content.slice(position);
        console.log('Content:', this.content);
    }
    
    deleteText(position, length) {
        const deleted = this.content.slice(position, position + length);
        this.content = 
            this.content.slice(0, position) + 
            this.content.slice(position + length);
        console.log('Content:', this.content);
        return deleted;
    }
    
    getText() {
        return this.content;
    }
}

// Invoker
class CommandManager {
    constructor() {
        this.history = [];
        this.currentIndex = -1;
    }
    
    execute(command) {
        // Remove any commands after current index
        this.history = this.history.slice(0, this.currentIndex + 1);
        
        command.execute();
        
        this.history.push(command);
        this.currentIndex++;
    }
    
    undo() {
        if (this.currentIndex < 0) {
            console.log('Nothing to undo');
            return;
        }
        
        const command = this.history[this.currentIndex];
        command.undo();
        this.currentIndex--;
    }
    
    redo() {
        if (this.currentIndex >= this.history.length - 1) {
            console.log('Nothing to redo');
            return;
        }
        
        this.currentIndex++;
        const command = this.history[this.currentIndex];
        command.execute();
    }
}

// Usage
const editor = new TextEditor();
const manager = new CommandManager();

manager.execute(new InsertTextCommand(editor, 'Hello', 0));
manager.execute(new InsertTextCommand(editor, ' World', 5));
manager.execute(new DeleteTextCommand(editor, 5, 6));

manager.undo();  // Restore " World"
manager.undo();  // Remove " World"
manager.redo();  // Add " World" again

// Command Pattern - Remote control
class Light {
    constructor(location) {
        this.location = location;
        this.isOn = false;
    }
    
    on() {
        this.isOn = true;
        console.log(`${this.location} light is ON`);
    }
    
    off() {
        this.isOn = false;
        console.log(`${this.location} light is OFF`);
    }
}

class LightOnCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }
    
    execute() {
        this.light.on();
    }
    
    undo() {
        this.light.off();
    }
}

class LightOffCommand extends Command {
    constructor(light) {
        super();
        this.light = light;
    }
    
    execute() {
        this.light.off();
    }
    
    undo() {
        this.light.on();
    }
}

class RemoteControl {
    constructor() {
        this.commands = {};
        this.history = [];
    }
    
    setCommand(button, command) {
        this.commands[button] = command;
    }
    
    pressButton(button) {
        const command = this.commands[button];
        
        if (command) {
            command.execute();
            this.history.push(command);
        }
    }
    
    pressUndo() {
        const command = this.history.pop();
        
        if (command) {
            command.undo();
        }
    }
}

// Usage
const livingRoomLight = new Light('Living Room');
const bedroomLight = new Light('Bedroom');

const remote = new RemoteControl();
remote.setCommand('button1', new LightOnCommand(livingRoomLight));
remote.setCommand('button2', new LightOffCommand(livingRoomLight));
remote.setCommand('button3', new LightOnCommand(bedroomLight));

remote.pressButton('button1');  // Living Room light ON
remote.pressButton('button3');  // Bedroom light ON
remote.pressUndo();             // Bedroom light OFF

// Macro Command - multiple commands
class MacroCommand extends Command {
    constructor(commands) {
        super();
        this.commands = commands;
    }
    
    execute() {
        this.commands.forEach(cmd => cmd.execute());
    }
    
    undo() {
        // Undo in reverse order
        for (let i = this.commands.length - 1; i >= 0; i--) {
            this.commands[i].undo();
        }
    }
}

// Usage
const partyMode = new MacroCommand([
    new LightOnCommand(livingRoomLight),
    new LightOnCommand(bedroomLight)
]);

remote.setCommand('party', partyMode);
remote.pressButton('party');  // Turn on all lights
Key Points: Strategy pattern encapsulates algorithms as interchangeable objects. Set strategy at runtime. Use for sorting, validation, payment methods. Command pattern encapsulates actions as objects. Enables undo/redo with command history. Macro commands combine multiple commands. Both patterns promote loose coupling.

6. Decorator Pattern and Proxy Pattern

Decorator Pattern Features

Feature Description Benefit
Wrapping Wrap object to add behavior Extend without modifying
Composition Stack multiple decorators Flexible combinations
Transparent Same interface as original Drop-in replacement
Runtime Add behavior dynamically Configuration flexibility

Proxy Pattern Types

Type Purpose Use Case
Virtual Proxy Lazy initialization Expensive object creation
Protection Proxy Access control Authentication, authorization
Remote Proxy Remote object access API calls, RPC
Caching Proxy Cache results Performance optimization
Logging Proxy Log method calls Debugging, auditing

ES6 Proxy Traps

Trap Intercepts Usage
get Property access obj.prop
set Property assignment obj.prop = value
has in operator 'prop' in obj
deleteProperty delete operator delete obj.prop
apply Function call func(...args)
construct new operator new Constructor()

Example: Decorator pattern

// Decorator Pattern - Coffee shop
class Coffee {
    cost() {
        return 5;
    }
    
    description() {
        return 'Coffee';
    }
}

class CoffeeDecorator {
    constructor(coffee) {
        this.coffee = coffee;
    }
    
    cost() {
        return this.coffee.cost();
    }
    
    description() {
        return this.coffee.description();
    }
}

class MilkDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 1;
    }
    
    description() {
        return this.coffee.description() + ', Milk';
    }
}

class SugarDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 0.5;
    }
    
    description() {
        return this.coffee.description() + ', Sugar';
    }
}

class WhipDecorator extends CoffeeDecorator {
    cost() {
        return this.coffee.cost() + 1.5;
    }
    
    description() {
        return this.coffee.description() + ', Whip';
    }
}

// Usage
let myCoffee = new Coffee();
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);

myCoffee = new MilkDecorator(myCoffee);
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);

myCoffee = new SugarDecorator(myCoffee);
myCoffee = new WhipDecorator(myCoffee);
console.log(`${myCoffee.description()}: ${myCoffee.cost()}`);
// Coffee, Milk, Sugar, Whip: $8

// Function Decorator
function logger(func) {
    return function(...args) {
        console.log(`Calling ${func.name} with`, args);
        const result = func(...args);
        console.log(`Result:`, result);
        return result;
    };
}

function memoize(func) {
    const cache = new Map();
    
    return function(...args) {
        const key = JSON.stringify(args);
        
        if (cache.has(key)) {
            console.log('Cache hit');
            return cache.get(key);
        }
        
        const result = func(...args);
        cache.set(key, result);
        return result;
    };
}

function timer(func) {
    return function(...args) {
        const start = performance.now();
        const result = func(...args);
        const end = performance.now();
        console.log(`${func.name} took ${(end - start).toFixed(2)}ms`);
        return result;
    };
}

// Usage
function fibonacci(n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

const decoratedFib = timer(memoize(logger(fibonacci)));
decoratedFib(10);

// Class Method Decorator (using decorator proposal)
function readonly(target, key, descriptor) {
    descriptor.writable = false;
    return descriptor;
}

function validate(target, key, descriptor) {
    const original = descriptor.value;
    
    descriptor.value = function(...args) {
        if (args.some(arg => typeof arg !== 'number')) {
            throw new Error('All arguments must be numbers');
        }
        return original.apply(this, args);
    };
    
    return descriptor;
}

class Calculator {
    @readonly
    PI = 3.14159;
    
    @validate
    add(a, b) {
        return a + b;
    }
}

// Without decorator syntax (current JavaScript)
class Calculator2 {
    constructor() {
        Object.defineProperty(this, 'PI', {
            value: 3.14159,
            writable: false
        });
    }
}

// Wrap with validation
Calculator2.prototype.add = function(a, b) {
    if (typeof a !== 'number' || typeof b !== 'number') {
        throw new Error('All arguments must be numbers');
    }
    return a + b;
};

Example: Proxy pattern

// Virtual Proxy - Lazy loading
class Image {
    constructor(filename) {
        this.filename = filename;
        this.loadFromDisk();
    }
    
    loadFromDisk() {
        console.log(`Loading ${this.filename} from disk`);
    }
    
    display() {
        console.log(`Displaying ${this.filename}`);
    }
}

class ImageProxy {
    constructor(filename) {
        this.filename = filename;
        this.image = null;
    }
    
    display() {
        if (!this.image) {
            this.image = new Image(this.filename);
        }
        this.image.display();
    }
}

// Usage
const proxy = new ImageProxy('photo.jpg');
// Image not loaded yet

proxy.display();  // Loads and displays
proxy.display();  // Just displays (already loaded)

// Protection Proxy - Access control
class BankAccount {
    constructor(balance) {
        this.balance = balance;
    }
    
    deposit(amount) {
        this.balance += amount;
        console.log(`Deposited ${amount}, balance: ${this.balance}`);
    }
    
    withdraw(amount) {
        if (amount > this.balance) {
            throw new Error('Insufficient funds');
        }
        this.balance -= amount;
        console.log(`Withdrew ${amount}, balance: ${this.balance}`);
    }
    
    getBalance() {
        return this.balance;
    }
}

class BankAccountProxy {
    constructor(account, password) {
        this.account = account;
        this.password = password;
    }
    
    authenticate(password) {
        return password === this.password;
    }
    
    deposit(amount, password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        this.account.deposit(amount);
    }
    
    withdraw(amount, password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        this.account.withdraw(amount);
    }
    
    getBalance(password) {
        if (!this.authenticate(password)) {
            throw new Error('Authentication failed');
        }
        return this.account.getBalance();
    }
}

// Usage
const account = new BankAccount(1000);
const secureAccount = new BankAccountProxy(account, 'secret123');

secureAccount.deposit(500, 'secret123');
secureAccount.withdraw(200, 'secret123');
// secureAccount.withdraw(200, 'wrong');  // Error

// ES6 Proxy - Validation
const user = {
    name: 'John',
    age: 30,
    email: 'john@example.com'
};

const validatedUser = new Proxy(user, {
    set(target, property, value) {
        if (property === 'age') {
            if (typeof value !== 'number' || value < 0) {
                throw new Error('Age must be a positive number');
            }
        }
        
        if (property === 'email') {
            if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) {
                throw new Error('Invalid email format');
            }
        }
        
        target[property] = value;
        return true;
    }
});

validatedUser.age = 35;  // OK
// validatedUser.age = -5;  // Error
// validatedUser.email = 'invalid';  // Error

// ES6 Proxy - Logging
function createLoggingProxy(obj, name) {
    return new Proxy(obj, {
        get(target, property) {
            console.log(`[GET] ${name}.${property}`);
            return target[property];
        },
        
        set(target, property, value) {
            console.log(`[SET] ${name}.${property} = ${value}`);
            target[property] = value;
            return true;
        }
    });
}

const person = createLoggingProxy({
    name: 'John',
    age: 30
}, 'person');

person.name;       // [GET] person.name
person.age = 31;   // [SET] person.age = 31

// ES6 Proxy - Negative array indexes
function createArrayProxy(arr) {
    return new Proxy(arr, {
        get(target, property) {
            const index = parseInt(property);
            
            if (!isNaN(index) && index < 0) {
                return target[target.length + index];
            }
            
            return target[property];
        }
    });
}

const arr = createArrayProxy([1, 2, 3, 4, 5]);
console.log(arr[-1]);  // 5
console.log(arr[-2]);  // 4

// ES6 Proxy - Observable
function createObservable(obj, onChange) {
    return new Proxy(obj, {
        set(target, property, value) {
            const oldValue = target[property];
            target[property] = value;
            
            onChange({
                property,
                oldValue,
                newValue: value
            });
            
            return true;
        }
    });
}

const state = createObservable({count: 0}, (change) => {
    console.log(`${change.property} changed from ${change.oldValue} to ${change.newValue}`);
});

state.count = 1;  // count changed from 0 to 1
state.count = 2;  // count changed from 1 to 2

// Caching Proxy
function createCachingProxy(target) {
    const cache = new Map();
    
    return new Proxy(target, {
        apply(target, thisArg, args) {
            const key = JSON.stringify(args);
            
            if (cache.has(key)) {
                console.log('Cache hit');
                return cache.get(key);
            }
            
            const result = target.apply(thisArg, args);
            cache.set(key, result);
            return result;
        }
    });
}

function expensiveOperation(n) {
    console.log('Computing...');
    let result = 0;
    for (let i = 0; i < n * 1000000; i++) {
        result += i;
    }
    return result;
}

const cachedOp = createCachingProxy(expensiveOperation);

cachedOp(10);  // Computing...
cachedOp(10);  // Cache hit (instant)
cachedOp(20);  // Computing...
Key Points: Decorator pattern wraps objects to add behavior dynamically. Stack multiple decorators for flexible combinations. Proxy pattern controls access to objects. Virtual proxy for lazy loading. Protection proxy for access control. ES6 Proxy with traps (get, set, apply) enables powerful interceptors. Use for validation, logging, caching, observables.

Section 22 Summary: Modern JavaScript Patterns and Techniques

  • Module Patterns: IIFE modules, revealing module, ES6 modules (import/export), namespace objects for organization
  • Observer Pattern: Subject maintains observers, notify on changes, Event Emitter with on/off/emit methods
  • Factory Pattern: Encapsulate object creation, static factory methods, abstract factory for related objects
  • Builder Pattern: Fluent interface for complex construction, chainable methods, validation before creation
  • Singleton: Single instance with global access, getInstance() method, ES6 module exports as natural singletons
  • Registry Pattern: Manage collections of objects, service registry (DI), plugin registry, component registry
  • Strategy Pattern: Encapsulate algorithms, runtime selection, interchangeable strategies for sorting/validation/payments
  • Command Pattern: Encapsulate actions as objects, undo/redo with command history, macro commands for multiple actions
  • Decorator Pattern: Wrap objects to add behavior, stack decorators, same interface as original object
  • Proxy Pattern: Control object access, types (virtual, protection, caching), ES6 Proxy traps (get/set/apply)
  • Pattern Benefits: All patterns promote loose coupling, code reusability, maintainability, testability
  • Use Cases: Choose pattern based on problem: Observer for events, Factory for creation, Strategy for algorithms, Command for undo/redo, Proxy for access control