javascript

examples

examples.js⚡
/**
 * ========================================
 * 8.5 CLASS PATTERNS - CODE EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Factory Pattern
 * Creating objects without specifying exact class
 */
console.log('--- Example 1: Factory Pattern ---');

// Product classes
class Car {
  constructor(options) {
    this.type = 'car';
    this.wheels = 4;
    this.make = options.make;
    this.model = options.model;
  }

  describe() {
    return `${this.make} ${this.model} (${this.type})`;
  }
}

class Motorcycle {
  constructor(options) {
    this.type = 'motorcycle';
    this.wheels = 2;
    this.make = options.make;
    this.model = options.model;
  }

  describe() {
    return `${this.make} ${this.model} (${this.type})`;
  }
}

class Truck {
  constructor(options) {
    this.type = 'truck';
    this.wheels = 6;
    this.make = options.make;
    this.model = options.model;
    this.payload = options.payload || 0;
  }

  describe() {
    return `${this.make} ${this.model} (${this.type}, payload: ${this.payload}kg)`;
  }
}

// Factory
class VehicleFactory {
  static create(type, options) {
    switch (type.toLowerCase()) {
      case 'car':
        return new Car(options);
      case 'motorcycle':
        return new Motorcycle(options);
      case 'truck':
        return new Truck(options);
      default:
        throw new Error(`Unknown vehicle type: ${type}`);
    }
  }
}

const car = VehicleFactory.create('car', { make: 'Toyota', model: 'Camry' });
const bike = VehicleFactory.create('motorcycle', {
  make: 'Honda',
  model: 'CBR',
});
const truck = VehicleFactory.create('truck', {
  make: 'Ford',
  model: 'F-150',
  payload: 1000,
});

console.log(car.describe());
console.log(bike.describe());
console.log(truck.describe());

/**
 * EXAMPLE 2: Abstract Factory Pattern
 * Factory for creating related objects
 */
console.log('\n--- Example 2: Abstract Factory ---');

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

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

// Concrete products - Light theme
class LightButton extends Button {
  render() {
    return '<button class="btn-light">Click</button>';
  }
}

class LightInput extends Input {
  render() {
    return '<input class="input-light" />';
  }
}

// Concrete products - Dark theme
class DarkButton extends Button {
  render() {
    return '<button class="btn-dark">Click</button>';
  }
}

class DarkInput extends Input {
  render() {
    return '<input class="input-dark" />';
  }
}

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

// Concrete factories
class LightUIFactory extends UIFactory {
  createButton() {
    return new LightButton();
  }
  createInput() {
    return new LightInput();
  }
}

class DarkUIFactory extends UIFactory {
  createButton() {
    return new DarkButton();
  }
  createInput() {
    return new DarkInput();
  }
}

// Usage
function createUI(factory) {
  const button = factory.createButton();
  const input = factory.createInput();
  return { button, input };
}

const lightUI = createUI(new LightUIFactory());
const darkUI = createUI(new DarkUIFactory());

console.log('Light UI:', lightUI.button.render());
console.log('Dark UI:', darkUI.button.render());

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

class Pizza {
  constructor() {
    this.size = 'medium';
    this.crust = 'regular';
    this.toppings = [];
    this.sauce = 'tomato';
  }

  describe() {
    return (
      `${this.size} pizza with ${this.crust} crust, ` +
      `${this.sauce} sauce, and ${this.toppings.join(', ') || 'no toppings'}`
    );
  }
}

class PizzaBuilder {
  constructor() {
    this.pizza = new Pizza();
  }

  setSize(size) {
    this.pizza.size = size;
    return this;
  }

  setCrust(crust) {
    this.pizza.crust = crust;
    return this;
  }

  setSauce(sauce) {
    this.pizza.sauce = sauce;
    return this;
  }

  addTopping(topping) {
    this.pizza.toppings.push(topping);
    return this;
  }

  build() {
    return this.pizza;
  }
}

// Director for common configurations
class PizzaDirector {
  static createMargherita(builder) {
    return builder
      .setSize('medium')
      .setCrust('thin')
      .setSauce('tomato')
      .addTopping('mozzarella')
      .addTopping('basil')
      .build();
  }

  static createMeatLovers(builder) {
    return builder
      .setSize('large')
      .setCrust('thick')
      .setSauce('tomato')
      .addTopping('pepperoni')
      .addTopping('sausage')
      .addTopping('bacon')
      .addTopping('ham')
      .build();
  }
}

const customPizza = new PizzaBuilder()
  .setSize('large')
  .setCrust('stuffed')
  .addTopping('cheese')
  .addTopping('mushrooms')
  .build();

console.log(customPizza.describe());

const margherita = PizzaDirector.createMargherita(new PizzaBuilder());
console.log(margherita.describe());

/**
 * EXAMPLE 4: Advanced Builder - Query Builder
 */
console.log('\n--- Example 4: Query Builder ---');

class QueryBuilder {
  #table = '';
  #columns = ['*'];
  #conditions = [];
  #joins = [];
  #orderBy = [];
  #limit = null;
  #offset = null;

  select(...columns) {
    this.#columns = columns.length ? columns : ['*'];
    return this;
  }

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

  where(condition) {
    this.#conditions.push({ type: 'AND', condition });
    return this;
  }

  orWhere(condition) {
    this.#conditions.push({ type: 'OR', condition });
    return this;
  }

  join(table, condition, type = 'INNER') {
    this.#joins.push(`${type} JOIN ${table} ON ${condition}`);
    return this;
  }

  leftJoin(table, condition) {
    return this.join(table, condition, 'LEFT');
  }

  orderBy(column, direction = 'ASC') {
    this.#orderBy.push(`${column} ${direction}`);
    return this;
  }

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

  offset(n) {
    this.#offset = n;
    return this;
  }

  build() {
    let query = `SELECT ${this.#columns.join(', ')} FROM ${this.#table}`;

    if (this.#joins.length) {
      query += ` ${this.#joins.join(' ')}`;
    }

    if (this.#conditions.length) {
      const whereClause = this.#conditions
        .map((c, i) => (i === 0 ? c.condition : `${c.type} ${c.condition}`))
        .join(' ');
      query += ` WHERE ${whereClause}`;
    }

    if (this.#orderBy.length) {
      query += ` ORDER BY ${this.#orderBy.join(', ')}`;
    }

    if (this.#limit !== null) {
      query += ` LIMIT ${this.#limit}`;
    }

    if (this.#offset !== null) {
      query += ` OFFSET ${this.#offset}`;
    }

    return query;
  }
}

const query = new QueryBuilder()
  .select('users.name', 'orders.total')
  .from('users')
  .leftJoin('orders', 'users.id = orders.user_id')
  .where('users.active = true')
  .where('orders.total > 100')
  .orderBy('orders.total', 'DESC')
  .limit(10)
  .build();

console.log(query);

/**
 * EXAMPLE 5: Singleton Pattern
 * Single instance throughout application
 */
console.log('\n--- Example 5: Singleton Pattern ---');

class Logger {
  static #instance = null;
  #logs = [];

  constructor() {
    if (Logger.#instance) {
      return Logger.#instance;
    }
    Logger.#instance = this;
  }

  static getInstance() {
    if (!Logger.#instance) {
      new Logger();
    }
    return Logger.#instance;
  }

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

  info(message) {
    this.log('INFO', message);
  }
  warn(message) {
    this.log('WARN', message);
  }
  error(message) {
    this.log('ERROR', message);
  }

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

const logger1 = Logger.getInstance();
const logger2 = Logger.getInstance();

console.log('Same instance:', logger1 === logger2);

logger1.info('Application started');
logger2.warn('Low memory');

/**
 * EXAMPLE 6: Mixin Pattern
 * Composable behaviors
 */
console.log('\n--- Example 6: Mixin Pattern ---');

// Mixin factories
const Timestamped = (Base) =>
  class extends Base {
    constructor(...args) {
      super(...args);
      this.createdAt = new Date();
      this.updatedAt = new Date();
    }

    touch() {
      this.updatedAt = new Date();
      return this;
    }
  };

const Identifiable = (Base) =>
  class extends Base {
    static #nextId = 1;

    constructor(...args) {
      super(...args);
      this.id = Identifiable.#nextId++;
    }
  };

const Serializable = (Base) =>
  class extends Base {
    toJSON() {
      return { ...this };
    }

    toString() {
      return JSON.stringify(this.toJSON());
    }

    static fromJSON(json) {
      const data = typeof json === 'string' ? JSON.parse(json) : json;
      return Object.assign(new this(), data);
    }
  };

const Validatable = (Base) =>
  class extends Base {
    #rules = new Map();

    addRule(field, validator) {
      if (!this.#rules.has(field)) {
        this.#rules.set(field, []);
      }
      this.#rules.get(field).push(validator);
      return this;
    }

    validate() {
      const errors = [];
      for (const [field, validators] of this.#rules) {
        for (const validator of validators) {
          const result = validator(this[field]);
          if (result !== true) {
            errors.push({ field, error: result });
          }
        }
      }
      return { valid: errors.length === 0, errors };
    }
  };

// Base class
class Entity {
  constructor() {}
}

// Compose mixins
class User extends Serializable(
  Validatable(Identifiable(Timestamped(Entity)))
) {
  constructor(name, email) {
    super();
    this.name = name;
    this.email = email;
  }
}

const user = new User('Alice', 'alice@example.com');
user.addRule('email', (v) => v.includes('@') || 'Invalid email');
user.addRule('name', (v) => v.length >= 2 || 'Name too short');

console.log('User:', user.toJSON());
console.log('Validation:', user.validate());

/**
 * EXAMPLE 7: Observer Pattern
 * Event-driven communication
 */
console.log('\n--- Example 7: Observer Pattern ---');

class EventEmitter {
  #events = new Map();

  on(event, callback) {
    if (!this.#events.has(event)) {
      this.#events.set(event, new Set());
    }
    this.#events.get(event).add(callback);
    return () => this.off(event, callback);
  }

  off(event, callback) {
    const callbacks = this.#events.get(event);
    if (callbacks) {
      callbacks.delete(callback);
    }
  }

  emit(event, ...args) {
    const callbacks = this.#events.get(event);
    if (callbacks) {
      for (const callback of callbacks) {
        callback(...args);
      }
    }
  }

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

class Store extends EventEmitter {
  #state = {};

  getState() {
    return { ...this.#state };
  }

  setState(newState) {
    const oldState = this.#state;
    this.#state = { ...this.#state, ...newState };
    this.emit('change', this.#state, oldState);
  }
}

const store = new Store();

const unsubscribe = store.on('change', (newState, oldState) => {
  console.log('State changed:', oldState, '→', newState);
});

store.setState({ count: 1 });
store.setState({ count: 2, name: 'test' });

unsubscribe();
store.setState({ count: 3 }); // No log

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

// Strategy interface
class SortStrategy {
  sort(arr) {
    throw new Error('Must implement sort()');
  }
}

// Concrete strategies
class BubbleSort extends SortStrategy {
  sort(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]];
        }
      }
    }
    console.log('Using Bubble Sort');
    return result;
  }
}

class QuickSort extends SortStrategy {
  sort(arr) {
    console.log('Using Quick Sort');
    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 [...this.sort(left), ...middle, ...this.sort(right)];
  }
}

class MergeSort extends SortStrategy {
  sort(arr) {
    console.log('Using Merge Sort');
    if (arr.length <= 1) return arr;
    const mid = Math.floor(arr.length / 2);
    const left = this.sort(arr.slice(0, mid));
    const right = this.sort(arr.slice(mid));
    return this.#merge(left, right);
  }

  #merge(left, right) {
    const result = [];
    while (left.length && right.length) {
      result.push(left[0] < right[0] ? left.shift() : right.shift());
    }
    return [...result, ...left, ...right];
  }
}

// Context
class Sorter {
  #strategy;

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

  sort(arr) {
    if (!this.#strategy) {
      throw new Error('No sorting strategy set');
    }
    return this.#strategy.sort(arr);
  }
}

const data = [5, 2, 8, 1, 9];
const sorter = new Sorter();

console.log(sorter.setStrategy(new BubbleSort()).sort(data));
console.log(sorter.setStrategy(new QuickSort()).sort(data));
console.log(sorter.setStrategy(new MergeSort()).sort(data));

/**
 * EXAMPLE 9: Decorator Pattern
 * Add behavior dynamically
 */
console.log('\n--- Example 9: Decorator Pattern ---');

// Base component
class Coffee {
  cost() {
    return 5;
  }
  description() {
    return 'Coffee';
  }
}

// Base decorator
class CoffeeDecorator {
  constructor(coffee) {
    this.coffee = coffee;
  }

  cost() {
    return this.coffee.cost();
  }

  description() {
    return this.coffee.description();
  }
}

// Concrete decorators
class MilkDecorator extends CoffeeDecorator {
  cost() {
    return super.cost() + 2;
  }

  description() {
    return `${super.description()} + Milk`;
  }
}

class SugarDecorator extends CoffeeDecorator {
  cost() {
    return super.cost() + 0.5;
  }

  description() {
    return `${super.description()} + Sugar`;
  }
}

class WhipDecorator extends CoffeeDecorator {
  cost() {
    return super.cost() + 1.5;
  }

  description() {
    return `${super.description()} + Whipped Cream`;
  }
}

class SizeDecorator extends CoffeeDecorator {
  constructor(coffee, size = 'medium') {
    super(coffee);
    this.size = size;
    this.multipliers = { small: 0.8, medium: 1, large: 1.5 };
  }

  cost() {
    return super.cost() * this.multipliers[this.size];
  }

  description() {
    return `${
      this.size.charAt(0).toUpperCase() + this.size.slice(1)
    } ${super.description()}`;
  }
}

// Build a drink
let order = new Coffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
order = new WhipDecorator(order);
order = new SizeDecorator(order, 'large');

console.log(order.description()); // "Large Coffee + Milk + Sugar + Whipped Cream"
console.log(`Total: $${order.cost().toFixed(2)}`); // Total: $13.50

/**
 * EXAMPLE 10: Command Pattern
 * Encapsulate actions for undo/redo
 */
console.log('\n--- Example 10: Command Pattern ---');

// Command interface
class Command {
  execute() {
    throw new Error('Must implement execute()');
  }
  undo() {
    throw new Error('Must implement undo()');
  }
}

// Receiver
class TextEditor {
  #content = '';

  getContent() {
    return this.#content;
  }

  insertText(text, position) {
    const before = this.#content.slice(0, position);
    const after = this.#content.slice(position);
    this.#content = before + text + after;
  }

  deleteText(start, length) {
    const deleted = this.#content.slice(start, start + length);
    this.#content =
      this.#content.slice(0, start) + this.#content.slice(start + length);
    return deleted;
  }
}

// Concrete commands
class InsertCommand extends Command {
  #editor;
  #text;
  #position;

  constructor(editor, text, position) {
    super();
    this.#editor = editor;
    this.#text = text;
    this.#position = position;
  }

  execute() {
    this.#editor.insertText(this.#text, this.#position);
  }

  undo() {
    this.#editor.deleteText(this.#position, this.#text.length);
  }
}

class DeleteCommand extends Command {
  #editor;
  #start;
  #length;
  #deletedText;

  constructor(editor, start, length) {
    super();
    this.#editor = editor;
    this.#start = start;
    this.#length = length;
  }

  execute() {
    this.#deletedText = this.#editor.deleteText(this.#start, this.#length);
  }

  undo() {
    this.#editor.insertText(this.#deletedText, this.#start);
  }
}

// Invoker
class CommandManager {
  #history = [];
  #redoStack = [];

  execute(command) {
    command.execute();
    this.#history.push(command);
    this.#redoStack = [];
  }

  undo() {
    const command = this.#history.pop();
    if (command) {
      command.undo();
      this.#redoStack.push(command);
    }
  }

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

const editor = new TextEditor();
const commandManager = new CommandManager();

commandManager.execute(new InsertCommand(editor, 'Hello', 0));
console.log(editor.getContent()); // "Hello"

commandManager.execute(new InsertCommand(editor, ' World', 5));
console.log(editor.getContent()); // "Hello World"

commandManager.undo();
console.log(editor.getContent()); // "Hello"

commandManager.redo();
console.log(editor.getContent()); // "Hello World"

/**
 * EXAMPLE 11: Repository Pattern
 * Abstract data access layer
 */
console.log('\n--- Example 11: Repository Pattern ---');

// Repository interface
class Repository {
  findById(id) {
    throw new Error('Not implemented');
  }
  findAll() {
    throw new Error('Not implemented');
  }
  save(entity) {
    throw new Error('Not implemented');
  }
  delete(id) {
    throw new Error('Not implemented');
  }
  exists(id) {
    return this.findById(id) !== null;
  }
  count() {
    return this.findAll().length;
  }
}

// In-memory implementation
class InMemoryRepository extends Repository {
  #store = new Map();

  findById(id) {
    return this.#store.get(id) || null;
  }

  findAll() {
    return [...this.#store.values()];
  }

  save(entity) {
    if (!entity.id) {
      entity.id = Date.now();
    }
    this.#store.set(entity.id, entity);
    return entity;
  }

  delete(id) {
    return this.#store.delete(id);
  }
}

// Specialized repository
class UserRepository extends InMemoryRepository {
  findByEmail(email) {
    return this.findAll().find((user) => user.email === email) || null;
  }

  findActiveUsers() {
    return this.findAll().filter((user) => user.active);
  }
}

const userRepo = new UserRepository();
userRepo.save({
  id: 1,
  name: 'Alice',
  email: 'alice@example.com',
  active: true,
});
userRepo.save({ id: 2, name: 'Bob', email: 'bob@example.com', active: false });
userRepo.save({
  id: 3,
  name: 'Charlie',
  email: 'charlie@example.com',
  active: true,
});

console.log(
  'All users:',
  userRepo.findAll().map((u) => u.name)
);
console.log(
  'Active users:',
  userRepo.findActiveUsers().map((u) => u.name)
);
console.log('Find by email:', userRepo.findByEmail('bob@example.com'));

/**
 * EXAMPLE 12: State Pattern
 * Object behavior changes with state
 */
console.log('\n--- Example 12: State Pattern ---');

// State interface
class State {
  handle(context) {
    throw new Error('Must implement handle()');
  }
}

// Concrete states
class DraftState extends State {
  handle(context) {
    console.log('Document is in Draft state');
  }

  publish(context) {
    console.log('Moving from Draft to Moderation');
    context.setState(new ModerationState());
  }
}

class ModerationState extends State {
  handle(context) {
    console.log('Document is under Moderation');
  }

  approve(context) {
    console.log('Moving from Moderation to Published');
    context.setState(new PublishedState());
  }

  reject(context) {
    console.log('Moving from Moderation back to Draft');
    context.setState(new DraftState());
  }
}

class PublishedState extends State {
  handle(context) {
    console.log('Document is Published');
  }

  unpublish(context) {
    console.log('Moving from Published to Draft');
    context.setState(new DraftState());
  }
}

// Context
class Document {
  #state;

  constructor() {
    this.#state = new DraftState();
  }

  setState(state) {
    this.#state = state;
  }

  status() {
    this.#state.handle(this);
  }

  publish() {
    if (this.#state.publish) this.#state.publish(this);
  }

  approve() {
    if (this.#state.approve) this.#state.approve(this);
  }

  reject() {
    if (this.#state.reject) this.#state.reject(this);
  }

  unpublish() {
    if (this.#state.unpublish) this.#state.unpublish(this);
  }
}

const doc = new Document();
doc.status(); // Draft
doc.publish(); // → Moderation
doc.status(); // Moderation
doc.approve(); // → Published
doc.status(); // Published

/**
 * EXAMPLE 13: Adapter Pattern
 * Convert interface to another
 */
console.log('\n--- Example 13: Adapter Pattern ---');

// Old API
class OldPaymentSystem {
  processPayment(data) {
    console.log(`Old system: Processing ${data.currency}${data.amount}`);
    return { status: 'ok', transactionId: `OLD-${Date.now()}` };
  }
}

// New API expected interface
class PaymentProcessor {
  pay(amount, currency) {
    throw new Error('Must implement pay()');
  }
}

// Adapter
class OldPaymentAdapter extends PaymentProcessor {
  #oldSystem;

  constructor() {
    super();
    this.#oldSystem = new OldPaymentSystem();
  }

  pay(amount, currency) {
    // Adapt new interface to old system
    const result = this.#oldSystem.processPayment({
      amount,
      currency,
    });

    // Adapt old response to new format
    return {
      success: result.status === 'ok',
      id: result.transactionId,
    };
  }
}

// New system
class NewPaymentSystem extends PaymentProcessor {
  pay(amount, currency) {
    console.log(`New system: Processing ${currency}${amount}`);
    return { success: true, id: `NEW-${Date.now()}` };
  }
}

// Client code works with both
function processOrder(processor, amount, currency) {
  return processor.pay(amount, currency);
}

const oldAdapter = new OldPaymentAdapter();
const newSystem = new NewPaymentSystem();

console.log(processOrder(oldAdapter, 100, '$'));
console.log(processOrder(newSystem, 100, '$'));

/**
 * EXAMPLE 14: Dependency Injection
 * Loosely coupled components
 */
console.log('\n--- Example 14: Dependency Injection ---');

// Dependencies (can be mocked for testing)
class EmailService {
  send(to, subject, body) {
    console.log(`Sending email to ${to}: ${subject}`);
    return true;
  }
}

class Logger {
  log(message) {
    console.log(`[LOG] ${message}`);
  }
}

class UserRepository {
  save(user) {
    console.log(`Saving user: ${user.name}`);
    return { ...user, id: Date.now() };
  }

  findById(id) {
    return { id, name: 'Test User', email: 'test@example.com' };
  }
}

// Service with injected dependencies
class UserService {
  #userRepo;
  #emailService;
  #logger;

  constructor(userRepo, emailService, logger) {
    this.#userRepo = userRepo;
    this.#emailService = emailService;
    this.#logger = logger;
  }

  async createUser(userData) {
    this.#logger.log(`Creating user: ${userData.name}`);

    const user = this.#userRepo.save(userData);

    this.#emailService.send(user.email, 'Welcome!', 'Thanks for signing up.');

    this.#logger.log(`User created: ${user.id}`);

    return user;
  }
}

// DI Container (simple version)
class Container {
  #services = new Map();

  register(name, factory) {
    this.#services.set(name, { factory, instance: null });
  }

  registerSingleton(name, factory) {
    this.#services.set(name, { factory, singleton: true, instance: null });
  }

  resolve(name) {
    const service = this.#services.get(name);
    if (!service) throw new Error(`Service not found: ${name}`);

    if (service.singleton && service.instance) {
      return service.instance;
    }

    const instance = service.factory(this);

    if (service.singleton) {
      service.instance = instance;
    }

    return instance;
  }
}

// Setup container
const container = new Container();
container.registerSingleton('logger', () => new Logger());
container.registerSingleton('emailService', () => new EmailService());
container.registerSingleton('userRepo', () => new UserRepository());
container.register(
  'userService',
  (c) =>
    new UserService(
      c.resolve('userRepo'),
      c.resolve('emailService'),
      c.resolve('logger')
    )
);

// Usage
const userService = container.resolve('userService');
userService.createUser({ name: 'Alice', email: 'alice@example.com' });

/**
 * EXAMPLE 15: Composite Pattern
 * Tree structures with uniform interface
 */
console.log('\n--- Example 15: Composite Pattern ---');

// Component interface
class FileSystemItem {
  constructor(name) {
    this.name = name;
  }

  getSize() {
    throw new Error('Must implement getSize()');
  }

  print(indent = 0) {
    throw new Error('Must implement print()');
  }
}

// Leaf
class File extends FileSystemItem {
  constructor(name, size) {
    super(name);
    this.size = size;
  }

  getSize() {
    return this.size;
  }

  print(indent = 0) {
    console.log(`${'  '.repeat(indent)}📄 ${this.name} (${this.size}KB)`);
  }
}

// Composite
class Directory extends FileSystemItem {
  #children = [];

  constructor(name) {
    super(name);
  }

  add(item) {
    this.#children.push(item);
    return this;
  }

  remove(item) {
    const index = this.#children.indexOf(item);
    if (index > -1) {
      this.#children.splice(index, 1);
    }
    return this;
  }

  getSize() {
    return this.#children.reduce((total, child) => total + child.getSize(), 0);
  }

  print(indent = 0) {
    console.log(`${'  '.repeat(indent)}📁 ${this.name}/ (${this.getSize()}KB)`);
    for (const child of this.#children) {
      child.print(indent + 1);
    }
  }
}

// Build file system
const root = new Directory('root');
const docs = new Directory('documents');
const images = new Directory('images');

docs.add(new File('readme.txt', 5)).add(new File('notes.md', 10));

images.add(new File('photo1.jpg', 500)).add(new File('photo2.jpg', 750));

root.add(docs).add(images).add(new File('config.json', 2));

root.print();
console.log(`Total size: ${root.getSize()}KB`);

/**
 * EXAMPLE 16: Prototype Pattern (using classes)
 * Clone objects
 */
console.log('\n--- Example 16: Prototype Pattern ---');

class Shape {
  constructor(x = 0, y = 0, color = 'black') {
    this.x = x;
    this.y = y;
    this.color = color;
  }

  clone() {
    return Object.assign(Object.create(Object.getPrototypeOf(this)), this);
  }
}

class Rectangle extends Shape {
  constructor(x, y, width, height, color) {
    super(x, y, color);
    this.width = width;
    this.height = height;
  }

  area() {
    return this.width * this.height;
  }
}

class Circle extends Shape {
  constructor(x, y, radius, color) {
    super(x, y, color);
    this.radius = radius;
  }

  area() {
    return Math.PI * this.radius ** 2;
  }
}

const original = new Rectangle(10, 20, 100, 50, 'blue');
const cloned = original.clone();

cloned.x = 30;
cloned.color = 'red';

console.log('Original:', original);
console.log('Cloned:', cloned);
console.log('Same type:', cloned instanceof Rectangle);

/**
 * EXAMPLE 17: Facade Pattern
 * Simplified interface to complex subsystem
 */
console.log('\n--- Example 17: Facade Pattern ---');

// Complex subsystem classes
class CPU {
  freeze() {
    console.log('CPU: Freezing...');
  }
  jump(address) {
    console.log(`CPU: Jumping to ${address}`);
  }
  execute() {
    console.log('CPU: Executing...');
  }
}

class Memory {
  load(address, data) {
    console.log(`Memory: Loading "${data}" at ${address}`);
  }
}

class HardDrive {
  read(sector, size) {
    console.log(`HardDrive: Reading ${size} bytes from sector ${sector}`);
    return 'boot_data';
  }
}

// Facade
class ComputerFacade {
  #cpu;
  #memory;
  #hardDrive;

  constructor() {
    this.#cpu = new CPU();
    this.#memory = new Memory();
    this.#hardDrive = new HardDrive();
  }

  start() {
    console.log('Computer: Starting...');
    this.#cpu.freeze();
    const bootData = this.#hardDrive.read(0, 1024);
    this.#memory.load(0x00, bootData);
    this.#cpu.jump(0x00);
    this.#cpu.execute();
    console.log('Computer: Started!');
  }
}

const computer = new ComputerFacade();
computer.start();

/**
 * EXAMPLE 18: Chain of Responsibility
 * Pass requests along a chain
 */
console.log('\n--- Example 18: Chain of Responsibility ---');

class Handler {
  #next = null;

  setNext(handler) {
    this.#next = handler;
    return handler;
  }

  handle(request) {
    if (this.#next) {
      return this.#next.handle(request);
    }
    return null;
  }
}

class AuthHandler extends Handler {
  handle(request) {
    if (!request.userId) {
      return { error: 'Authentication required' };
    }
    console.log('Auth: User authenticated');
    return super.handle(request);
  }
}

class ValidationHandler extends Handler {
  handle(request) {
    if (!request.data || request.data.length === 0) {
      return { error: 'Invalid request data' };
    }
    console.log('Validation: Request data valid');
    return super.handle(request);
  }
}

class RateLimitHandler extends Handler {
  #requests = new Map();
  #limit = 3;

  handle(request) {
    const count = this.#requests.get(request.userId) || 0;
    if (count >= this.#limit) {
      return { error: 'Rate limit exceeded' };
    }
    this.#requests.set(request.userId, count + 1);
    console.log('RateLimit: Request allowed');
    return super.handle(request);
  }
}

class RequestHandler extends Handler {
  handle(request) {
    console.log('Handler: Processing request');
    return { success: true, data: `Processed: ${request.data}` };
  }
}

// Build chain
const auth = new AuthHandler();
const validate = new ValidationHandler();
const rateLimit = new RateLimitHandler();
const requestHandler = new RequestHandler();

auth.setNext(validate).setNext(rateLimit).setNext(requestHandler);

console.log('\nValid request:');
console.log(auth.handle({ userId: 'user1', data: 'test data' }));

console.log('\nNo auth:');
console.log(auth.handle({ data: 'test data' }));

console.log('\nNo data:');
console.log(auth.handle({ userId: 'user1' }));

/**
 * EXAMPLE 19: Mediator Pattern
 * Centralized communication
 */
console.log('\n--- Example 19: Mediator Pattern ---');

class ChatRoom {
  #users = new Map();

  register(user) {
    this.#users.set(user.name, user);
    user.chatRoom = this;
  }

  send(message, from, to) {
    if (to) {
      // Direct message
      const user = this.#users.get(to);
      if (user) {
        user.receive(message, from);
      }
    } else {
      // Broadcast
      for (const [name, user] of this.#users) {
        if (name !== from) {
          user.receive(message, from);
        }
      }
    }
  }
}

class ChatUser {
  constructor(name) {
    this.name = name;
    this.chatRoom = null;
  }

  send(message, to) {
    console.log(
      `${this.name} sends: ${message}${to ? ` to ${to}` : ' (broadcast)'}`
    );
    this.chatRoom.send(message, this.name, to);
  }

  receive(message, from) {
    console.log(`${this.name} received from ${from}: ${message}`);
  }
}

const chatRoom = new ChatRoom();

const alice = new ChatUser('Alice');
const bob = new ChatUser('Bob');
const charlie = new ChatUser('Charlie');

chatRoom.register(alice);
chatRoom.register(bob);
chatRoom.register(charlie);

alice.send('Hello everyone!');
bob.send('Hi Alice!', 'Alice');

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

// Mixin for timestamps
const Timestamped = (Base) =>
  class extends Base {
    constructor(...args) {
      super(...args);
      this.createdAt = new Date();
    }
  };

// Entity base class
class Entity {
  constructor() {}
}

// Task model
class Task extends Timestamped(Entity) {
  constructor(title, description) {
    super();
    this.id = Date.now();
    this.title = title;
    this.description = description;
    this.status = 'pending';
  }

  complete() {
    this.status = 'completed';
  }
}

// Repository
class TaskRepository {
  #tasks = new Map();

  save(task) {
    this.#tasks.set(task.id, task);
    return task;
  }

  findById(id) {
    return this.#tasks.get(id);
  }

  findAll() {
    return [...this.#tasks.values()];
  }

  findByStatus(status) {
    return this.findAll().filter((t) => t.status === status);
  }
}

// Command for undo/redo
class CompleteTaskCommand {
  constructor(task) {
    this.task = task;
    this.previousStatus = null;
  }

  execute() {
    this.previousStatus = this.task.status;
    this.task.complete();
  }

  undo() {
    this.task.status = this.previousStatus;
  }
}

// Observer for notifications
class TaskEventEmitter extends EventEmitter {}

// Service combining patterns
class TaskService {
  #repo;
  #events;
  #commandHistory = [];

  constructor(repo, events) {
    this.#repo = repo;
    this.#events = events;
  }

  createTask(title, description) {
    const task = new Task(title, description);
    this.#repo.save(task);
    this.#events.emit('task:created', task);
    return task;
  }

  completeTask(taskId) {
    const task = this.#repo.findById(taskId);
    if (!task) throw new Error('Task not found');

    const command = new CompleteTaskCommand(task);
    command.execute();
    this.#commandHistory.push(command);

    this.#events.emit('task:completed', task);
    return task;
  }

  undoLastAction() {
    const command = this.#commandHistory.pop();
    if (command) {
      command.undo();
      this.#events.emit('task:undone', command.task);
    }
  }

  getAllTasks() {
    return this.#repo.findAll();
  }

  getPendingTasks() {
    return this.#repo.findByStatus('pending');
  }
}

// Usage
const taskRepo = new TaskRepository();
const taskEvents = new TaskEventEmitter();

taskEvents.on('task:created', (task) =>
  console.log(`📝 Task created: ${task.title}`)
);
taskEvents.on('task:completed', (task) =>
  console.log(`✅ Task completed: ${task.title}`)
);
taskEvents.on('task:undone', (task) =>
  console.log(`â†Šī¸ Action undone: ${task.title}`)
);

const taskService = new TaskService(taskRepo, taskEvents);

const task1 = taskService.createTask('Learn Patterns', 'Study design patterns');
const task2 = taskService.createTask('Build App', 'Create example application');

taskService.completeTask(task1.id);

console.log(
  '\nPending tasks:',
  taskService.getPendingTasks().map((t) => t.title)
);

taskService.undoLastAction();

console.log(
  'Pending after undo:',
  taskService.getPendingTasks().map((t) => t.title)
);

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