Jump to content

Featured Replies

Posted

You are reading Part 30 of the 39-part series: JavaScript Skill Progression: The Path from Beginner to Extreme. [Level 5]

Introduction

Design patterns are common solutions to recurring problems in software development. In JavaScript, these patterns help improve code organization, maintainability, and scalability. This article explores five important design patterns: Singleton, Factory, Observer, Module, and Proxy.

1. Singleton Pattern

The Singleton Pattern ensures that only one instance of an object exists and provides a global access point to it. This pattern is useful for managing shared resources, such as database connections or application configurations.

Example: Singleton Implementation

class Singleton {
    constructor() {
        if (!Singleton.instance) {
            Singleton.instance = this;
        }
        return Singleton.instance;
    }
    logMessage() {
        console.log("Singleton Instance Active");
    }
}

const instance1 = new Singleton();
const instance2 = new Singleton();
console.log(instance1 === instance2); // Output: true
  • Ensures only one instance of Singleton exists.

  • The second instance returns the same reference as the first.

2. Factory Pattern

The Factory Pattern provides a way to create objects without specifying the exact class of object that will be created. It helps manage complex object creation logic.

Example: Factory Function

class Car {
    constructor(make, model) {
        this.make = make;
        this.model = model;
    }
}

class CarFactory {
    createCar(make, model) {
        return new Car(make, model);
    }
}

const factory = new CarFactory();
const car1 = factory.createCar("Tesla", "Model S");
const car2 = factory.createCar("Ford", "Mustang");
console.log(car1, car2);
  • Abstracts object creation, making it flexible and reusable.

3. Observer Pattern

The Observer Pattern allows an object (the subject) to maintain a list of dependent objects (observers) that need to be notified when the subject changes.

Example: Implementing an Event Emitter (Observer Pattern)

class EventEmitter {
    constructor() {
        this.events = {};
    }
    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
    }
    emit(event, data) {
        if (this.events[event]) {
            this.events[event].forEach(listener => listener(data));
        }
    }
}

const emitter = new EventEmitter();
emitter.on("message", data => console.log("Received:", data));
emitter.emit("message", "Hello Observer Pattern");
  • Useful for implementing event-driven architectures like Pub-Sub systems.

4. Module Pattern

The Module Pattern encapsulates functionality in an independent, reusable unit, avoiding polluting the global scope.

Example: Module Pattern Using an IIFE (Immediately Invoked Function Expression)

const CounterModule = (function() {
    let count = 0;
    return {
        increment: function() {
            count++;
            console.log("Count:", count);
        },
        getCount: function() {
            return count;
        }
    };
})();

CounterModule.increment(); // Output: Count: 1
console.log(CounterModule.getCount()); // Output: 1
  • Prevents direct access to internal variables (encapsulation).

  • Encourages modular development.

5. Proxy Pattern

The Proxy Pattern provides an intermediary to control access to an object. This is useful for validation, logging, caching, and lazy loading.

Example: Proxy for Validation

const user = {
    name: "John Doe",
    age: 25
};

const userProxy = new Proxy(user, {
    get(target, property) {
        console.log(`Accessing ${property}:`, target[property]);
        return target[property];
    },
    set(target, property, value) {
        if (property === "age" && value < 0) {
            throw new Error("Age cannot be negative");
        }
        target[property] = value;
        console.log(`${property} updated to ${value}`);
        return true;
    }
});

console.log(userProxy.name);
userProxy.age = 30;
// userProxy.age = -5; // Throws error
  • Allows control over property access and mutation.

  • Useful for security checks, API rate limiting, and caching.

You are reading Part 30 of the 39-part series: JavaScript Skill Progression: The Path from Beginner to Extreme. [Level 5]

  • Views 72
  • Created
  • Last Reply

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

Important Information

Terms of Use Privacy Policy Guidelines We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.