Docs

README

7.6 Object Patterns

Overview

This section covers common design patterns and best practices for working with objects in JavaScript. These patterns solve recurring problems and provide reusable templates for structuring your code.


Table of Contents

  1. β€’Factory Pattern
  2. β€’Constructor Pattern
  3. β€’Module Pattern
  4. β€’Revealing Module Pattern
  5. β€’Singleton Pattern
  6. β€’Mixin Pattern
  7. β€’Namespace Pattern
  8. β€’Builder Pattern
  9. β€’Prototype Pattern
  10. β€’Observer Pattern

Factory Pattern

Creates objects without specifying the exact class. Encapsulates object creation logic.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Factory Pattern                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   createUser("admin")          createUser("guest")          β”‚
β”‚         β”‚                            β”‚                      β”‚
β”‚         β–Ό                            β–Ό                      β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”               β”‚
β”‚   β”‚ AdminUser   β”‚             β”‚ GuestUser   β”‚               β”‚
β”‚   β”‚ {           β”‚             β”‚ {           β”‚               β”‚
β”‚   β”‚   role,     β”‚             β”‚   role,     β”‚               β”‚
β”‚   β”‚   perms,    β”‚             β”‚   perms,    β”‚               β”‚
β”‚   β”‚   ...       β”‚             β”‚   ...       β”‚               β”‚
β”‚   β”‚ }           β”‚             β”‚ }           β”‚               β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜               β”‚
β”‚                                                             β”‚
β”‚   β€’ Encapsulates creation logic                             β”‚
β”‚   β€’ Returns different types based on input                  β”‚
β”‚   β€’ Client doesn't need to know concrete class              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Basic Factory

function createUser(name, role) {
  return {
    name,
    role,
    createdAt: new Date(),
    greet() {
      return `Hi, I'm ${this.name}`;
    },
  };
}

const user1 = createUser('Alice', 'admin');
const user2 = createUser('Bob', 'user');

Factory with Type Selection

function createVehicle(type, options) {
  const vehicles = {
    car: () => ({
      type: 'car',
      wheels: 4,
      doors: options.doors || 4,
      drive() {
        console.log('Driving on road');
      },
    }),
    motorcycle: () => ({
      type: 'motorcycle',
      wheels: 2,
      drive() {
        console.log('Riding on road');
      },
    }),
    boat: () => ({
      type: 'boat',
      wheels: 0,
      drive() {
        console.log('Sailing on water');
      },
    }),
  };

  if (!vehicles[type]) {
    throw new Error(`Unknown vehicle type: ${type}`);
  }

  return vehicles[type]();
}

Factory vs Constructor

AspectFactoryConstructor
SyntaxcreateThing()new Thing()
this bindingExplicit returnImplicit
instanceofNoYes
InheritanceManualPrototype chain
FlexibilityHighMedium
MemoryNew object each timeShared prototype

Constructor Pattern

Uses new keyword with constructor functions to create instances.

function Person(name, age) {
  // Instance properties
  this.name = name;
  this.age = age;
}

// Shared methods on prototype
Person.prototype.greet = function () {
  return `Hello, I'm ${this.name}`;
};

Person.prototype.birthday = function () {
  this.age++;
  return this;
};

const person = new Person('Alice', 30);

Constructor with Private State (Closure)

function BankAccount(initialBalance) {
  // Private variable (closure)
  let balance = initialBalance;

  // Public methods (privileged)
  this.deposit = function (amount) {
    if (amount > 0) balance += amount;
    return this;
  };

  this.withdraw = function (amount) {
    if (amount <= balance) balance -= amount;
    return this;
  };

  this.getBalance = function () {
    return balance;
  };
}

const account = new BankAccount(100);
account.deposit(50).withdraw(25);
console.log(account.getBalance()); // 125
console.log(account.balance); // undefined (private!)

Module Pattern

Encapsulates private state and exposes a public API using closures.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Module Pattern                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚   const module = (function() {                              β”‚
β”‚                                                             β”‚
β”‚       β”Œβ”€β”€β”€ Private Scope ───┐                               β”‚
β”‚       β”‚ let privateVar      β”‚                               β”‚
β”‚       β”‚ function helper()   β”‚                               β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                               β”‚
β”‚                β”‚                                            β”‚
β”‚                β–Ό                                            β”‚
β”‚       β”Œβ”€β”€β”€ Public API ───┐                                  β”‚
β”‚       β”‚ return {         β”‚                                  β”‚
β”‚       β”‚   method1,       β”‚  ◄── Only these are accessible   β”‚
β”‚       β”‚   method2        β”‚                                  β”‚
β”‚       β”‚ };               β”‚                                  β”‚
β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                                  β”‚
β”‚   })();                                                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Classic IIFE Module

const Calculator = (function () {
  // Private state
  let result = 0;

  // Private function
  function validate(n) {
    if (typeof n !== 'number') {
      throw new Error('Expected a number');
    }
  }

  // Public API
  return {
    add(n) {
      validate(n);
      result += n;
      return this;
    },
    subtract(n) {
      validate(n);
      result -= n;
      return this;
    },
    multiply(n) {
      validate(n);
      result *= n;
      return this;
    },
    getResult() {
      return result;
    },
    reset() {
      result = 0;
      return this;
    },
  };
})();

Calculator.add(5).multiply(2).subtract(3);
console.log(Calculator.getResult()); // 7

Revealing Module Pattern

A variation where all functions are defined privately, then "revealed" in the return statement.

const UserManager = (function () {
  // Private state
  const users = [];

  // Private functions
  function findById(id) {
    return users.find((u) => u.id === id);
  }

  function generateId() {
    return Date.now().toString(36) + Math.random().toString(36).substr(2);
  }

  function addUser(name, email) {
    const user = {
      id: generateId(),
      name,
      email,
      createdAt: new Date(),
    };
    users.push(user);
    return user;
  }

  function removeUser(id) {
    const index = users.findIndex((u) => u.id === id);
    if (index > -1) {
      return users.splice(index, 1)[0];
    }
    return null;
  }

  function getUser(id) {
    return findById(id);
  }

  function getAllUsers() {
    return [...users]; // Return copy
  }

  // Reveal public API
  return {
    add: addUser,
    remove: removeUser,
    get: getUser,
    getAll: getAllUsers,
  };
})();

Module Pattern Comparison

PatternDescriptionUse Case
Classic ModuleMixed public/private in returnQuick encapsulation
Revealing ModuleAll private, reveal in returnCleaner, maintainable
ES6 Modulesimport/exportModern projects

Singleton Pattern

Ensures only one instance of a class/object exists.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                     Singleton Pattern                        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   getInstance() ─────┐                                      β”‚
β”‚                      β”‚                                      β”‚
β”‚   getInstance() ─────┼───► Same Instance                    β”‚
β”‚                      β”‚     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                   β”‚
β”‚   getInstance() β”€β”€β”€β”€β”€β”˜     β”‚  Singleton β”‚                   β”‚
β”‚                            β”‚  {         β”‚                   β”‚
β”‚                            β”‚    data,   β”‚                   β”‚
β”‚                            β”‚    methods β”‚                   β”‚
β”‚                            β”‚  }         β”‚                   β”‚
β”‚                            β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                   β”‚
β”‚                                                             β”‚
β”‚   Multiple calls β†’ Always returns the SAME object           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Object Literal Singleton

const AppConfig = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  debug: false,

  set(key, value) {
    if (this.hasOwnProperty(key)) {
      this[key] = value;
    }
  },

  get(key) {
    return this[key];
  },
};

Object.freeze(AppConfig); // Prevent modifications

Lazy Singleton

const Database = (function () {
  let instance = null;

  function createInstance() {
    // Expensive initialization
    const connection = {
      host: 'localhost',
      connected: false,

      connect() {
        this.connected = true;
        console.log('Connected to database');
      },

      query(sql) {
        if (!this.connected) throw new Error('Not connected');
        console.log(`Executing: ${sql}`);
      },
    };

    return connection;
  }

  return {
    getInstance() {
      if (!instance) {
        instance = createInstance();
      }
      return instance;
    },
  };
})();

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

ES6 Class Singleton

class Logger {
  static instance = null;

  constructor() {
    if (Logger.instance) {
      return Logger.instance;
    }

    this.logs = [];
    Logger.instance = this;
  }

  log(message) {
    const entry = { timestamp: new Date(), message };
    this.logs.push(entry);
    console.log(`[${entry.timestamp.toISOString()}] ${message}`);
  }

  getLogs() {
    return [...this.logs];
  }
}

const logger1 = new Logger();
const logger2 = new Logger();
console.log(logger1 === logger2); // true

Mixin Pattern

Adds functionality to objects/classes from multiple sources.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Mixin Pattern                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”‚
β”‚   β”‚ Walkable β”‚  β”‚ Swimmableβ”‚  β”‚ Flyable  β”‚                  β”‚
β”‚   β”‚ {walk()} β”‚  β”‚ {swim()} β”‚  β”‚ {fly()}  β”‚                  β”‚
β”‚   β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜                  β”‚
β”‚        β”‚             β”‚             β”‚                        β”‚
β”‚        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                        β”‚
β”‚                      β–Ό                                      β”‚
β”‚               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                              β”‚
β”‚               β”‚    Duck      β”‚                              β”‚
β”‚               β”‚ {            β”‚                              β”‚
β”‚               β”‚   walk(),    β”‚                              β”‚
β”‚               β”‚   swim(),    β”‚                              β”‚
β”‚               β”‚   fly()      β”‚                              β”‚
β”‚               β”‚ }            β”‚                              β”‚
β”‚               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                              β”‚
β”‚                                                             β”‚
β”‚   Compose behavior from multiple sources                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Object Mixins

const Walkable = {
  walk() {
    console.log(`${this.name} is walking`);
  },
};

const Swimmable = {
  swim() {
    console.log(`${this.name} is swimming`);
  },
};

const Flyable = {
  fly() {
    console.log(`${this.name} is flying`);
  },
};

// Duck can walk, swim, and fly
function Duck(name) {
  this.name = name;
}

Object.assign(Duck.prototype, Walkable, Swimmable, Flyable);

const duck = new Duck('Donald');
duck.walk(); // "Donald is walking"
duck.swim(); // "Donald is swimming"
duck.fly(); // "Donald is flying"

Functional Mixins

const withLogging = (Base) => {
  return {
    ...Base,
    log(message) {
      console.log(`[${this.name}]: ${message}`);
    },
  };
};

const withTimestamp = (Base) => {
  return {
    ...Base,
    timestamp() {
      return new Date().toISOString();
    },
  };
};

const withValidation = (Base) => {
  return {
    ...Base,
    validate(data) {
      return Object.keys(data).every((key) => data[key] !== undefined);
    },
  };
};

// Compose mixins
let Service = { name: 'UserService' };
Service = withLogging(Service);
Service = withTimestamp(Service);
Service = withValidation(Service);

// Or with compose function
const compose =
  (...mixins) =>
  (base) =>
    mixins.reduce((acc, mixin) => mixin(acc), base);

const enhancedService = compose(
  withLogging,
  withTimestamp,
  withValidation
)({ name: 'OrderService' });

Namespace Pattern

Organizes code under a single global object to avoid pollution.

// Create namespace
const MyApp = MyApp || {};

// Add sub-namespaces
MyApp.Models = {};
MyApp.Views = {};
MyApp.Utils = {};

// Define components
MyApp.Models.User = function (name) {
  this.name = name;
};

MyApp.Utils.format = {
  date(d) {
    return d.toLocaleDateString();
  },
  currency(n) {
    return `$${n.toFixed(2)}`;
  },
};

// Usage
const user = new MyApp.Models.User('Alice');
console.log(MyApp.Utils.format.currency(99.5)); // "$99.50"

Deep Namespace Creation

const namespace = (name, root = window) => {
  const parts = name.split('.');
  let current = root;

  for (const part of parts) {
    if (!current[part]) {
      current[part] = {};
    }
    current = current[part];
  }

  return current;
};

// Usage
const globalRoot = {};
namespace('MyApp.Services.API', globalRoot);
namespace('MyApp.Services.Auth', globalRoot);

globalRoot.MyApp.Services.API.fetch = function () {
  /* ... */
};

Builder Pattern

Separates construction of complex objects from their representation.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Builder Pattern                         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚   QueryBuilder                                              β”‚
β”‚     .select("name", "email")                                β”‚
β”‚     .from("users")                                          β”‚
β”‚     .where("active", true)                                  β”‚
β”‚     .orderBy("name")                                        β”‚
β”‚     .limit(10)                                              β”‚
β”‚     .build()                                                β”‚
β”‚          β”‚                                                  β”‚
β”‚          β–Ό                                                  β”‚
β”‚   "SELECT name, email FROM users WHERE active = true        β”‚
β”‚    ORDER BY name LIMIT 10"                                  β”‚
β”‚                                                             β”‚
β”‚   β€’ Chain method calls                                      β”‚
β”‚   β€’ Configure step by step                                  β”‚
β”‚   β€’ Build final result                                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Fluent Builder

class QueryBuilder {
  constructor() {
    this.query = {
      select: [],
      from: '',
      where: [],
      orderBy: null,
      limit: null,
    };
  }

  select(...fields) {
    this.query.select = fields;
    return this;
  }

  from(table) {
    this.query.from = table;
    return this;
  }

  where(field, value) {
    this.query.where.push({ field, value });
    return this;
  }

  orderBy(field, direction = 'ASC') {
    this.query.orderBy = { field, direction };
    return this;
  }

  limit(n) {
    this.query.limit = n;
    return this;
  }

  build() {
    let sql = `SELECT ${this.query.select.join(', ')}`;
    sql += ` FROM ${this.query.from}`;

    if (this.query.where.length) {
      const conditions = this.query.where
        .map((w) => `${w.field} = '${w.value}'`)
        .join(' AND ');
      sql += ` WHERE ${conditions}`;
    }

    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;
  }
}

const query = new QueryBuilder()
  .select('name', 'email')
  .from('users')
  .where('status', 'active')
  .orderBy('name')
  .limit(10)
  .build();

Object Configuration Builder

class RequestBuilder {
  constructor(url) {
    this.config = {
      url,
      method: 'GET',
      headers: {},
      body: null,
      timeout: 5000,
    };
  }

  method(m) {
    this.config.method = m;
    return this;
  }

  header(key, value) {
    this.config.headers[key] = value;
    return this;
  }

  body(data) {
    this.config.body = JSON.stringify(data);
    this.config.headers['Content-Type'] = 'application/json';
    return this;
  }

  timeout(ms) {
    this.config.timeout = ms;
    return this;
  }

  async execute() {
    const controller = new AbortController();
    const id = setTimeout(() => controller.abort(), this.config.timeout);

    try {
      const response = await fetch(this.config.url, {
        method: this.config.method,
        headers: this.config.headers,
        body: this.config.body,
        signal: controller.signal,
      });
      return response.json();
    } finally {
      clearTimeout(id);
    }
  }
}

// Usage
const response = await new RequestBuilder('/api/users')
  .method('POST')
  .header('Authorization', 'Bearer token')
  .body({ name: 'Alice' })
  .timeout(10000)
  .execute();

Observer Pattern

Defines a one-to-many dependency where observers are notified of state changes.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Observer Pattern                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                             β”‚
β”‚                    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                           β”‚
β”‚                    β”‚  Subject   β”‚                           β”‚
β”‚                    β”‚ (EventBus) β”‚                           β”‚
β”‚                    β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜                           β”‚
β”‚                          β”‚                                  β”‚
β”‚              emit("event", data)                            β”‚
β”‚                          β”‚                                  β”‚
β”‚        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                β”‚
β”‚        β–Ό                 β–Ό                 β–Ό                β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β”‚
β”‚   β”‚Observer 1β”‚    β”‚Observer 2β”‚    β”‚Observer 3β”‚              β”‚
β”‚   β”‚callback()β”‚    β”‚callback()β”‚    β”‚callback()β”‚              β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β”‚
β”‚                                                             β”‚
β”‚   Subject notifies all subscribed observers                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Event Emitter Implementation

class EventEmitter {
  constructor() {
    this.events = new Map();
  }

  on(event, callback) {
    if (!this.events.has(event)) {
      this.events.set(event, []);
    }
    this.events.get(event).push(callback);
    return this;
  }

  off(event, callback) {
    if (this.events.has(event)) {
      const callbacks = this.events.get(event);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
    return this;
  }

  once(event, callback) {
    const wrapper = (...args) => {
      callback(...args);
      this.off(event, wrapper);
    };
    return this.on(event, wrapper);
  }

  emit(event, ...args) {
    if (this.events.has(event)) {
      for (const callback of this.events.get(event)) {
        callback(...args);
      }
    }
    return this;
  }
}

// Usage
const emitter = new EventEmitter();

emitter.on('message', (data) => {
  console.log('Received:', data);
});

emitter.once('connect', () => {
  console.log('Connected!');
});

emitter.emit('connect');
emitter.emit('message', { text: 'Hello' });

Observable Object

function createObservable(target) {
  const listeners = new Map();

  return new Proxy(target, {
    get(obj, prop) {
      if (prop === 'subscribe') {
        return (property, callback) => {
          if (!listeners.has(property)) {
            listeners.set(property, []);
          }
          listeners.get(property).push(callback);
          return () => {
            const callbacks = listeners.get(property);
            const index = callbacks.indexOf(callback);
            if (index > -1) callbacks.splice(index, 1);
          };
        };
      }
      return obj[prop];
    },

    set(obj, prop, value) {
      const oldValue = obj[prop];
      obj[prop] = value;

      if (listeners.has(prop)) {
        for (const callback of listeners.get(prop)) {
          callback(value, oldValue, prop);
        }
      }
      return true;
    },
  });
}

// Usage
const state = createObservable({ count: 0, name: 'App' });

const unsubscribe = state.subscribe('count', (newVal, oldVal) => {
  console.log(`Count changed from ${oldVal} to ${newVal}`);
});

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

unsubscribe(); // Stop listening
state.count = 3; // No output

Pattern Selection Guide

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         When to Use Each Pattern                          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Pattern            β”‚ Use When                                            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Factory            β”‚ Creating objects based on conditions/config         β”‚
β”‚ Constructor        β”‚ Multiple instances with shared methods              β”‚
β”‚ Module             β”‚ Encapsulating private state, creating APIs          β”‚
β”‚ Singleton          β”‚ Need exactly one instance globally                  β”‚
β”‚ Mixin              β”‚ Sharing behavior across unrelated objects           β”‚
β”‚ Namespace          β”‚ Organizing code, avoiding global pollution          β”‚
β”‚ Builder            β”‚ Complex object construction with many options       β”‚
β”‚ Observer           β”‚ Decoupled communication between components          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Best Practices

1. Prefer Composition Over Inheritance

// Instead of deep inheritance chains
// Use mixins and composition

const canFly = (base) => ({
  ...base,
  fly() {
    console.log(`${this.name} flies`);
  },
});

const canSwim = (base) => ({
  ...base,
  swim() {
    console.log(`${this.name} swims`);
  },
});

const duck = canSwim(canFly({ name: 'Duck' }));

2. Keep Modules Focused

// Good: Single responsibility
const UserAuthentication = {
  login() {
    /* ... */
  },
  logout() {
    /* ... */
  },
  validateToken() {
    /* ... */
  },
};

// Bad: Mixed concerns
const UserEverything = {
  login() {
    /* ... */
  },
  formatDate() {
    /* ... */
  },
  sendEmail() {
    /* ... */
  },
};

3. Use Immutable Patterns When Possible

// Return new objects instead of mutating
const updateUser = (user, updates) => ({
  ...user,
  ...updates,
  updatedAt: new Date(),
});

const user1 = { name: 'Alice', age: 30 };
const user2 = updateUser(user1, { age: 31 });
// user1 unchanged, user2 is new object

Summary

PatternKey ConceptMemory Aid
FactoryCreates objects"Factory produces products"
Constructornew keyword + prototype"Blueprint for objects"
ModuleIIFE + closure"Private room with public door"
SingletonOne instance only"There can be only one"
MixinCombine behaviors"Mix ingredients together"
NamespaceOrganize under one object"File folders for code"
BuilderStep-by-step construction"Build LEGO piece by piece"
ObserverPublish/subscribe"Newsletter subscription"

Next Steps

  • β€’Study ES6 classes (syntactic sugar over these patterns)
  • β€’Learn about async patterns (Promise, async/await)
  • β€’Explore state management patterns (Flux, Redux concepts)
  • β€’Practice identifying when to use each pattern
README - JavaScript Tutorial | DeepML