← JS Mastery | Module 10: Advanced Patterns Design Patterns: Singleton, Factory & Observer
Module 10

Design Patterns: Singleton, Factory & Observer

⏱ 26 min read ● Advanced 🆓 Free

Design Patterns Overview

Design patterns are reusable solutions to common programming problems. They're not code — they're templates. Learning them gives you vocabulary and proven approaches to structure your code.

Singleton Pattern

Ensures one instance exists globally. Perfect for database connections, configuration, logging.

class Database {
  static #instance = null;
  #connection;
  
  constructor(url) {
    if (Database.#instance) return Database.#instance;
    this.#connection = url;
    Database.#instance = this;
  }
  
  static getInstance(url) { return new Database(url); }
  query(sql) { return "Querying: " + sql; }
}

const db1 = Database.getInstance("postgres://localhost/app");
const db2 = Database.getInstance("postgres://localhost/app");
console.log(db1 === db2);  // true — same instance!

// Module-based singleton (simpler):
// db.js
let connection = null;
export function getDb() {
  if (!connection) connection = createConnection();
  return connection;
}

Factory Pattern

// Create objects without specifying exact class:
function createNotification(type, message, options = {}) {
  const base = { message, createdAt: new Date(), ...options };
  switch (type) {
    case "email": return { ...base, type: "email", send: sendEmail };
    case "sms": return { ...base, type: "sms", send: sendSMS };
    case "push": return { ...base, type: "push", send: sendPush };
    default: throw new Error("Unknown type: " + type);
  }
}

const notif = createNotification("email", "Welcome!", { to: "alice@example.com" });
notif.send();

Observer Pattern

class EventEmitter {
  #events = {};
  
  on(event, fn) {
    (this.#events[event] ??= []).push(fn);
    return () => this.off(event, fn);  // returns unsubscribe
  }
  
  once(event, fn) {
    const wrapper = (...args) => { fn(...args); this.off(event, wrapper); };
    return this.on(event, wrapper);
  }
  
  off(event, fn) {
    this.#events[event] = (this.#events[event] || []).filter(f => f !== fn);
  }
  
  emit(event, ...args) {
    (this.#events[event] || []).forEach(fn => fn(...args));
  }
}

const emitter = new EventEmitter();
const unsub = emitter.on("data", (d) => console.log("Got:", d));
emitter.emit("data", { users: [] });
unsub();  // stop listening

⚡ Key Takeaways

🎯 Practice Exercises

EXERCISE 1

Build a simple reactive state store using Observer pattern: state.set(key, value) notifies all subscribers. Multiple components can subscribe to specific keys.

← Environment Variables