javascript

examples

examples.js
/**
 * ========================================
 * 7.6 OBJECT PATTERNS - CODE EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Basic Factory Pattern
 * Creates objects without using `new`
 */
console.log('--- Example 1: Basic Factory Pattern ---');

function createPerson(name, age) {
  return {
    name,
    age,
    greet() {
      return `Hi, I'm ${this.name}`;
    },
  };
}

const alice = createPerson('Alice', 30);
const bob = createPerson('Bob', 25);

console.log(alice.greet()); // "Hi, I'm Alice"
console.log(bob.greet()); // "Hi, I'm Bob"

/**
 * EXAMPLE 2: Factory with Type Selection
 * Returns different object types based on input
 */
console.log('\n--- Example 2: Factory with Type Selection ---');

function createShape(type, options) {
  const shapes = {
    circle: () => ({
      type: 'circle',
      radius: options.radius,
      area() {
        return Math.PI * this.radius ** 2;
      },
    }),
    rectangle: () => ({
      type: 'rectangle',
      width: options.width,
      height: options.height,
      area() {
        return this.width * this.height;
      },
    }),
    triangle: () => ({
      type: 'triangle',
      base: options.base,
      height: options.height,
      area() {
        return 0.5 * this.base * this.height;
      },
    }),
  };

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

  return shapes[type]();
}

const circle = createShape('circle', { radius: 5 });
const rect = createShape('rectangle', { width: 4, height: 6 });

console.log(`Circle area: ${circle.area().toFixed(2)}`); // ~78.54
console.log(`Rectangle area: ${rect.area()}`); // 24

/**
 * EXAMPLE 3: Constructor Pattern with Prototype
 * Uses `new` keyword and shared methods
 */
console.log('\n--- Example 3: Constructor Pattern ---');

function Car(make, model, year) {
  this.make = make;
  this.model = model;
  this.year = year;
  this.running = false;
}

Car.prototype.start = function () {
  this.running = true;
  console.log(`${this.make} ${this.model} started`);
  return this;
};

Car.prototype.stop = function () {
  this.running = false;
  console.log(`${this.make} ${this.model} stopped`);
  return this;
};

Car.prototype.info = function () {
  return `${this.year} ${this.make} ${this.model}`;
};

const myCar = new Car('Toyota', 'Camry', 2020);
myCar.start().stop();
console.log(myCar.info()); // "2020 Toyota Camry"

/**
 * EXAMPLE 4: Constructor with Private State
 * Uses closures for encapsulation
 */
console.log('\n--- Example 4: Constructor with Private State ---');

function Counter(initial = 0) {
  // Private variable
  let count = initial;

  // Privileged methods (can access private state)
  this.increment = function () {
    count++;
    return this;
  };

  this.decrement = function () {
    count--;
    return this;
  };

  this.getCount = function () {
    return count;
  };

  this.reset = function () {
    count = initial;
    return this;
  };
}

const counter = new Counter(10);
counter.increment().increment().increment();
console.log(counter.getCount()); // 13
console.log(counter.count); // undefined (private!)

/**
 * EXAMPLE 5: Classic Module Pattern (IIFE)
 * Encapsulates private state with public API
 */
console.log('\n--- Example 5: Module Pattern ---');

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

  // Private function
  function addToHistory(operation) {
    history.push({
      operation,
      result,
      timestamp: new Date(),
    });
  }

  // Public API
  return {
    add(n) {
      result += n;
      addToHistory(`add ${n}`);
      return this;
    },

    subtract(n) {
      result -= n;
      addToHistory(`subtract ${n}`);
      return this;
    },

    multiply(n) {
      result *= n;
      addToHistory(`multiply ${n}`);
      return this;
    },

    divide(n) {
      if (n === 0) throw new Error('Division by zero');
      result /= n;
      addToHistory(`divide ${n}`);
      return this;
    },

    getResult() {
      return result;
    },

    getHistory() {
      return [...history]; // Return copy
    },

    reset() {
      result = 0;
      history = [];
      return this;
    },
  };
})();

Calculator.add(10).multiply(2).subtract(5);
console.log(Calculator.getResult()); // 15
console.log(Calculator.getHistory().length); // 3

/**
 * EXAMPLE 6: Revealing Module Pattern
 * All functions private, then revealed
 */
console.log('\n--- Example 6: Revealing Module Pattern ---');

const TaskManager = (function () {
  const tasks = [];

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

  function addTask(title, priority = 'normal') {
    const task = {
      id: generateId(),
      title,
      priority,
      completed: false,
      createdAt: new Date(),
    };
    tasks.push(task);
    return task;
  }

  function completeTask(id) {
    const task = tasks.find((t) => t.id === id);
    if (task) {
      task.completed = true;
      task.completedAt = new Date();
    }
    return task;
  }

  function removeTask(id) {
    const index = tasks.findIndex((t) => t.id === id);
    if (index > -1) {
      return tasks.splice(index, 1)[0];
    }
    return null;
  }

  function getPendingTasks() {
    return tasks.filter((t) => !t.completed);
  }

  function getAllTasks() {
    return [...tasks];
  }

  // Reveal public API
  return {
    add: addTask,
    complete: completeTask,
    remove: removeTask,
    getPending: getPendingTasks,
    getAll: getAllTasks,
  };
})();

const task1 = TaskManager.add('Learn JavaScript', 'high');
const task2 = TaskManager.add('Build project');
TaskManager.complete(task1.id);
console.log(TaskManager.getPending().length); // 1
console.log(TaskManager.getAll().length); // 2

/**
 * EXAMPLE 7: Singleton Pattern (Object Literal)
 * Simple singleton with object literal
 */
console.log('\n--- Example 7: Object Literal Singleton ---');

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

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

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

// Prevent modification (optional)
// Object.freeze(AppConfig);

console.log(AppConfig.get('apiUrl'));
AppConfig.set('debug', true);
console.log(AppConfig.debug); // true

/**
 * EXAMPLE 8: Lazy Singleton Pattern
 * Creates instance only when first needed
 */
console.log('\n--- Example 8: Lazy Singleton ---');

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

  function createConnection() {
    console.log('Creating database connection...');

    return {
      host: 'localhost',
      port: 5432,
      connected: false,

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

      disconnect() {
        this.connected = false;
        console.log('Disconnected from database');
        return this;
      },

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

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

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

/**
 * EXAMPLE 9: ES6 Class Singleton
 * Singleton using ES6 class syntax
 */
console.log('\n--- Example 9: Class Singleton ---');

class Logger {
  static instance = null;

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

    this.logs = [];
    this.level = 'info';
    Logger.instance = this;
  }

  setLevel(level) {
    this.level = level;
    return this;
  }

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

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

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

/**
 * EXAMPLE 10: Mixin Pattern with Objects
 * Combine behaviors from multiple sources
 */
console.log('\n--- Example 10: Object Mixins ---');

const Serializable = {
  toJSON() {
    return JSON.stringify(this);
  },
  fromJSON(json) {
    return Object.assign(this, JSON.parse(json));
  },
};

const Comparable = {
  equals(other) {
    return JSON.stringify(this) === JSON.stringify(other);
  },
};

const Timestamped = {
  touch() {
    this.updatedAt = new Date();
    return this;
  },
  getAge() {
    if (!this.createdAt) return 0;
    return Date.now() - this.createdAt.getTime();
  },
};

function Record(data) {
  Object.assign(this, data);
  this.createdAt = new Date();
}

// Apply mixins
Object.assign(Record.prototype, Serializable, Comparable, Timestamped);

const record = new Record({ name: 'Test', value: 42 });
record.touch();
console.log(record.toJSON());

/**
 * EXAMPLE 11: Functional Mixins
 * Mixins as functions that enhance objects
 */
console.log('\n--- Example 11: Functional Mixins ---');

const withLogging = (obj) => ({
  ...obj,
  log(...args) {
    console.log(`[${obj.name}]`, ...args);
  },
  error(...args) {
    console.error(`[${obj.name}] ERROR:`, ...args);
  },
});

const withEvents = (obj) => {
  const events = {};
  return {
    ...obj,
    on(event, callback) {
      if (!events[event]) events[event] = [];
      events[event].push(callback);
      return this;
    },
    emit(event, ...args) {
      if (events[event]) {
        events[event].forEach((cb) => cb(...args));
      }
      return this;
    },
  };
};

const withState = (initialState) => (obj) => {
  let state = { ...initialState };
  return {
    ...obj,
    getState() {
      return { ...state };
    },
    setState(updates) {
      state = { ...state, ...updates };
      return this;
    },
  };
};

// Compose mixins
const compose =
  (...fns) =>
  (x) =>
    fns.reduce((acc, fn) => fn(acc), x);

const createService = compose(
  withLogging,
  withEvents,
  withState({ count: 0, active: false })
);

const service = createService({ name: 'DataService' });
service.log('Service created');
service.setState({ active: true });
console.log(service.getState());

/**
 * EXAMPLE 12: Namespace Pattern
 * Organize code under single global object
 */
console.log('\n--- Example 12: Namespace Pattern ---');

const MyApp = {};

MyApp.Config = {
  version: '1.0.0',
  env: 'development',
};

MyApp.Utils = {
  format: {
    date(d) {
      return d.toLocaleDateString();
    },
    currency(n) {
      return `$${n.toFixed(2)}`;
    },
  },
  validators: {
    email(str) {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
    },
    phone(str) {
      return /^\d{10}$/.test(str);
    },
  },
};

MyApp.Models = {
  User: function (name, email) {
    this.name = name;
    this.email = email;
  },
};

console.log(MyApp.Utils.format.currency(99.5)); // "$99.50"
console.log(MyApp.Utils.validators.email('test@example.com')); // true

/**
 * EXAMPLE 13: Builder Pattern
 * Step-by-step object construction
 */
console.log('\n--- Example 13: Builder Pattern ---');

class URLBuilder {
  constructor() {
    this.url = {
      protocol: 'https',
      host: '',
      port: null,
      path: [],
      query: {},
      fragment: null,
    };
  }

  protocol(p) {
    this.url.protocol = p;
    return this;
  }

  host(h) {
    this.url.host = h;
    return this;
  }

  port(p) {
    this.url.port = p;
    return this;
  }

  path(...segments) {
    this.url.path.push(...segments);
    return this;
  }

  query(key, value) {
    this.url.query[key] = value;
    return this;
  }

  fragment(f) {
    this.url.fragment = f;
    return this;
  }

  build() {
    let result = `${this.url.protocol}://${this.url.host}`;

    if (this.url.port) {
      result += `:${this.url.port}`;
    }

    if (this.url.path.length) {
      result += '/' + this.url.path.join('/');
    }

    const queryString = Object.entries(this.url.query)
      .map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
      .join('&');

    if (queryString) {
      result += `?${queryString}`;
    }

    if (this.url.fragment) {
      result += `#${this.url.fragment}`;
    }

    return result;
  }
}

const url = new URLBuilder()
  .host('api.example.com')
  .path('v1', 'users')
  .query('page', 1)
  .query('limit', 10)
  .fragment('results')
  .build();

console.log(url);
// https://api.example.com/v1/users?page=1&limit=10#results

/**
 * EXAMPLE 14: HTML Builder
 * Building DOM-like structures
 */
console.log('\n--- Example 14: HTML Builder ---');

class HTMLBuilder {
  constructor(tag) {
    this.element = {
      tag,
      attributes: {},
      children: [],
      text: '',
    };
  }

  attr(key, value) {
    this.element.attributes[key] = value;
    return this;
  }

  text(content) {
    this.element.text = content;
    return this;
  }

  child(builder) {
    this.element.children.push(builder);
    return this;
  }

  build(indent = 0) {
    const pad = '  '.repeat(indent);
    const { tag, attributes, children, text } = this.element;

    let attrs = Object.entries(attributes)
      .map(([k, v]) => `${k}="${v}"`)
      .join(' ');

    if (attrs) attrs = ' ' + attrs;

    if (children.length === 0 && !text) {
      return `${pad}<${tag}${attrs} />`;
    }

    let html = `${pad}<${tag}${attrs}>`;

    if (text) {
      html += text;
    }

    if (children.length) {
      html += '\n';
      for (const child of children) {
        html += child.build(indent + 1) + '\n';
      }
      html += pad;
    }

    html += `</${tag}>`;
    return html;
  }
}

const el = (tag) => new HTMLBuilder(tag);

const html = el('div')
  .attr('class', 'container')
  .child(el('h1').text('Welcome'))
  .child(
    el('ul')
      .attr('class', 'list')
      .child(el('li').text('Item 1'))
      .child(el('li').text('Item 2'))
  )
  .build();

console.log(html);

/**
 * EXAMPLE 15: Observer Pattern (Event Emitter)
 * Publish-subscribe mechanism
 */
console.log('\n--- Example 15: Observer Pattern ---');

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

  listenerCount(event) {
    return this.events.has(event) ? this.events.get(event).length : 0;
  }
}

const emitter = new EventEmitter();

emitter.on('data', (data) => console.log('Received:', data));
emitter.once('connect', () => console.log('Connected!'));

emitter.emit('connect'); // "Connected!"
emitter.emit('connect'); // (nothing - was once)
emitter.emit('data', { value: 42 }); // "Received: { value: 42 }"

/**
 * EXAMPLE 16: Observable State
 * Reactive state with observers
 */
console.log('\n--- Example 16: Observable State ---');

function createStore(initialState) {
  let state = { ...initialState };
  const listeners = new Set();

  return {
    getState() {
      return { ...state };
    },

    setState(updates) {
      const prevState = state;
      state = { ...state, ...updates };

      // Notify all listeners
      for (const listener of listeners) {
        listener(state, prevState);
      }
    },

    subscribe(listener) {
      listeners.add(listener);

      // Return unsubscribe function
      return () => {
        listeners.delete(listener);
      };
    },
  };
}

const store = createStore({ count: 0, name: 'App' });

const unsubscribe = store.subscribe((newState, prevState) => {
  console.log('State changed:', prevState, '->', newState);
});

store.setState({ count: 1 });
store.setState({ count: 2 });
unsubscribe();
store.setState({ count: 3 }); // No output

/**
 * EXAMPLE 17: Proxy-based Observable
 * Automatic change detection with Proxy
 */
console.log('\n--- Example 17: Proxy Observable ---');

function observable(target, onChange) {
  return new Proxy(target, {
    get(obj, prop) {
      const value = obj[prop];
      if (typeof value === 'object' && value !== null) {
        return observable(value, onChange);
      }
      return value;
    },

    set(obj, prop, value) {
      const oldValue = obj[prop];
      obj[prop] = value;
      onChange(prop, value, oldValue);
      return true;
    },
  });
}

const data = observable(
  { user: { name: 'Alice', age: 30 }, active: true },
  (prop, newVal, oldVal) => {
    console.log(`Changed ${prop}: ${oldVal} -> ${newVal}`);
  }
);

data.active = false; // "Changed active: true -> false"
data.user.name = 'Bob'; // "Changed name: Alice -> Bob"

/**
 * EXAMPLE 18: Command Pattern
 * Encapsulate operations as objects
 */
console.log('\n--- Example 18: Command Pattern ---');

class CommandManager {
  constructor() {
    this.history = [];
    this.undoStack = [];
  }

  execute(command) {
    command.execute();
    this.history.push(command);
    this.undoStack = []; // Clear redo stack
    return this;
  }

  undo() {
    const command = this.history.pop();
    if (command) {
      command.undo();
      this.undoStack.push(command);
    }
    return this;
  }

  redo() {
    const command = this.undoStack.pop();
    if (command) {
      command.execute();
      this.history.push(command);
    }
    return this;
  }
}

// Example commands
const createAddCommand = (receiver, value) => ({
  execute() {
    receiver.value += value;
    console.log(`Added ${value}, result: ${receiver.value}`);
  },
  undo() {
    receiver.value -= value;
    console.log(`Undid add ${value}, result: ${receiver.value}`);
  },
});

const manager = new CommandManager();
const calc = { value: 0 };

manager.execute(createAddCommand(calc, 5));
manager.execute(createAddCommand(calc, 10));
console.log(`Current value: ${calc.value}`); // 15

manager.undo(); // Undid add 10
console.log(`After undo: ${calc.value}`); // 5

manager.redo(); // Added 10
console.log(`After redo: ${calc.value}`); // 15

/**
 * EXAMPLE 19: Strategy Pattern
 * Interchangeable algorithms
 */
console.log('\n--- Example 19: Strategy Pattern ---');

const sortStrategies = {
  bubble: (arr) => {
    const result = [...arr];
    for (let i = 0; i < result.length; i++) {
      for (let j = 0; j < result.length - i - 1; j++) {
        if (result[j] > result[j + 1]) {
          [result[j], result[j + 1]] = [result[j + 1], result[j]];
        }
      }
    }
    return result;
  },

  quick: (arr) => {
    if (arr.length <= 1) return arr;
    const pivot = arr[Math.floor(arr.length / 2)];
    const left = arr.filter((x) => x < pivot);
    const middle = arr.filter((x) => x === pivot);
    const right = arr.filter((x) => x > pivot);
    return [
      ...sortStrategies.quick(left),
      ...middle,
      ...sortStrategies.quick(right),
    ];
  },

  native: (arr) => [...arr].sort((a, b) => a - b),
};

class Sorter {
  constructor(strategy = 'native') {
    this.strategy = strategy;
  }

  setStrategy(strategy) {
    this.strategy = strategy;
    return this;
  }

  sort(arr) {
    console.log(`Sorting with ${this.strategy} strategy`);
    return sortStrategies[this.strategy](arr);
  }
}

const sorter = new Sorter();
const numbers = [64, 34, 25, 12, 22, 11, 90];

console.log(sorter.sort(numbers));
console.log(sorter.setStrategy('quick').sort(numbers));

/**
 * EXAMPLE 20: Complete Application Pattern
 * Combining multiple patterns
 */
console.log('\n--- Example 20: Combined Patterns Application ---');

// Application using multiple patterns
const TodoApp = (function () {
  // Private state (Module pattern)
  const todos = [];
  const events = new EventEmitter(); // Observer pattern

  // Private ID generator
  let nextId = 1;

  // Factory pattern for todo creation
  function createTodo(title, options = {}) {
    return {
      id: nextId++,
      title,
      completed: options.completed || false,
      priority: options.priority || 'normal',
      createdAt: new Date(),
    };
  }

  // Command pattern for undo/redo
  const commandHistory = [];
  const undoStack = [];

  function executeCommand(command) {
    command.execute();
    commandHistory.push(command);
    undoStack.length = 0;
  }

  // Public API (Revealing Module)
  return {
    add(title, options) {
      const todo = createTodo(title, options);
      const command = {
        execute() {
          todos.push(todo);
          events.emit('add', todo);
        },
        undo() {
          const index = todos.indexOf(todo);
          if (index > -1) todos.splice(index, 1);
          events.emit('remove', todo);
        },
      };
      executeCommand(command);
      return todo;
    },

    complete(id) {
      const todo = todos.find((t) => t.id === id);
      if (todo) {
        const wasCompleted = todo.completed;
        const command = {
          execute() {
            todo.completed = true;
            events.emit('complete', todo);
          },
          undo() {
            todo.completed = wasCompleted;
            events.emit('update', todo);
          },
        };
        executeCommand(command);
      }
      return todo;
    },

    undo() {
      const command = commandHistory.pop();
      if (command) {
        command.undo();
        undoStack.push(command);
      }
    },

    redo() {
      const command = undoStack.pop();
      if (command) {
        command.execute();
        commandHistory.push(command);
      }
    },

    getAll() {
      return [...todos];
    },

    on(event, callback) {
      events.on(event, callback);
      return this;
    },
  };
})();

// Usage
TodoApp.on('add', (todo) => console.log(`Added: ${todo.title}`));
TodoApp.on('complete', (todo) => console.log(`Completed: ${todo.title}`));

const todo1 = TodoApp.add('Learn patterns', { priority: 'high' });
TodoApp.add('Practice coding');
TodoApp.complete(todo1.id);

console.log('\nAll todos:', TodoApp.getAll());

TodoApp.undo(); // Undo complete
console.log('\nAfter undo:', TodoApp.getAll());

console.log('\n========================================');
console.log('End of Object Patterns Examples');
console.log('========================================');
Examples - JavaScript Tutorial | DeepML