javascript

examples

examples.js
/**
 * ========================================
 * 8.4 PRIVATE FIELDS & METHODS - EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Basic Private Fields
 * Using # prefix for private instance fields
 */
console.log('--- Example 1: Basic Private Fields ---');

class Person {
  #ssn; // Private field - must be declared
  name; // Public field

  constructor(name, ssn) {
    this.name = name;
    this.#ssn = ssn;
  }

  // Public method to access private data
  getSSNLast4() {
    return '***-**-' + this.#ssn.slice(-4);
  }

  // Private data can only be accessed inside class
  verifySSN(ssn) {
    return this.#ssn === ssn;
  }
}

const person = new Person('Alice', '123-45-6789');
console.log(person.name); // "Alice"
console.log(person.getSSNLast4()); // "***-**-6789"
console.log(person.verifySSN('123-45-6789')); // true

// Cannot access private field externally
// console.log(person.#ssn);  // SyntaxError!
console.log(person.ssn); // undefined (not the private field)

/**
 * EXAMPLE 2: Multiple Private Fields
 * Encapsulating related data
 */
console.log('\n--- Example 2: Multiple Private Fields ---');

class BankAccount {
  #accountNumber;
  #balance;
  #pin;
  #transactionHistory = [];

  constructor(accountNumber, initialBalance, pin) {
    this.#accountNumber = accountNumber;
    this.#balance = initialBalance;
    this.#pin = pin;
  }

  // Getters for controlled access
  get accountNumber() {
    const masked = this.#accountNumber.slice(-4);
    return `****${masked}`;
  }

  get balance() {
    return this.#balance;
  }

  // PIN verification
  #verifyPin(pin) {
    return this.#pin === pin;
  }

  // Transaction logging
  #logTransaction(type, amount) {
    this.#transactionHistory.push({
      type,
      amount,
      balance: this.#balance,
      date: new Date(),
    });
  }

  deposit(amount) {
    if (amount <= 0) throw new Error('Invalid amount');
    this.#balance += amount;
    this.#logTransaction('deposit', amount);
    return this.#balance;
  }

  withdraw(amount, pin) {
    if (!this.#verifyPin(pin)) {
      throw new Error('Invalid PIN');
    }
    if (amount > this.#balance) {
      throw new Error('Insufficient funds');
    }
    this.#balance -= amount;
    this.#logTransaction('withdrawal', amount);
    return amount;
  }

  getStatement() {
    return this.#transactionHistory.map((t) => ({
      ...t,
      date: t.date.toISOString(),
    }));
  }
}

const account = new BankAccount('1234567890', 1000, '1234');
console.log('Account:', account.accountNumber); // "****7890"
console.log('Balance:', account.balance); // 1000

account.deposit(500);
account.withdraw(200, '1234');
console.log('After transactions:', account.balance); // 1300
console.log('Statement:', account.getStatement());

/**
 * EXAMPLE 3: Private Methods
 * Internal implementation details hidden
 */
console.log('\n--- Example 3: Private Methods ---');

class PasswordManager {
  #passwords = new Map();
  #masterKey;

  constructor(masterKey) {
    this.#masterKey = masterKey;
  }

  // Private encryption/decryption methods
  #encrypt(text) {
    // Simplified encryption (use proper crypto in production)
    return Buffer.from(text).toString('base64');
  }

  #decrypt(encrypted) {
    return Buffer.from(encrypted, 'base64').toString();
  }

  // Private validation
  #validateMasterKey(key) {
    return key === this.#masterKey;
  }

  // Public interface
  store(service, password, masterKey) {
    if (!this.#validateMasterKey(masterKey)) {
      throw new Error('Invalid master key');
    }
    this.#passwords.set(service, this.#encrypt(password));
  }

  retrieve(service, masterKey) {
    if (!this.#validateMasterKey(masterKey)) {
      throw new Error('Invalid master key');
    }
    const encrypted = this.#passwords.get(service);
    if (!encrypted) return null;
    return this.#decrypt(encrypted);
  }

  listServices() {
    return [...this.#passwords.keys()];
  }
}

const pm = new PasswordManager('secret123');
pm.store('email', 'myEmailPass', 'secret123');
pm.store('github', 'myGithubPass', 'secret123');

console.log('Services:', pm.listServices());
console.log('Email password:', pm.retrieve('email', 'secret123'));

/**
 * EXAMPLE 4: Private Static Members
 * Class-level private data
 */
console.log('\n--- Example 4: Private Static Members ---');

class IDGenerator {
  static #counter = 0;
  static #prefix = 'ID';
  static #separator = '-';

  // Private static method
  static #format(num) {
    return num.toString().padStart(6, '0');
  }

  // Public interface
  static next() {
    return `${this.#prefix}${this.#separator}${this.#format(++this.#counter)}`;
  }

  static setPrefix(prefix) {
    this.#prefix = prefix;
  }

  static get count() {
    return this.#counter;
  }
}

console.log(IDGenerator.next()); // "ID-000001"
console.log(IDGenerator.next()); // "ID-000002"

IDGenerator.setPrefix('USER');
console.log(IDGenerator.next()); // "USER-000003"

console.log('Total generated:', IDGenerator.count);

// Cannot access private static
// IDGenerator.#counter;  // SyntaxError

/**
 * EXAMPLE 5: Private Getters and Setters
 * Internal computed properties
 */
console.log('\n--- Example 5: Private Getters/Setters ---');

class Circle {
  #radius;

  constructor(radius) {
    this.#radius = radius;
  }

  // Private getter
  get #diameter() {
    return this.#radius * 2;
  }

  // Private setter
  set #diameter(value) {
    this.#radius = value / 2;
  }

  // Public interface using private accessors
  get radius() {
    return this.#radius;
  }

  set radius(value) {
    if (value <= 0) throw new Error('Radius must be positive');
    this.#radius = value;
  }

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

  get circumference() {
    return Math.PI * this.#diameter;
  }

  scale(factor) {
    this.#diameter = this.#diameter * factor;
    return this;
  }
}

const circle = new Circle(5);
console.log('Radius:', circle.radius);
console.log('Area:', circle.area.toFixed(2));
console.log('Circumference:', circle.circumference.toFixed(2));

circle.scale(2);
console.log('After scaling - Radius:', circle.radius); // 10

/**
 * EXAMPLE 6: Checking for Private Fields
 * Using `in` operator with private fields
 */
console.log('\n--- Example 6: Checking Private Fields ---');

class User {
  #id;
  #isAdmin;

  constructor(id, isAdmin = false) {
    this.#id = id;
    this.#isAdmin = isAdmin;
  }

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

  // Check if object has #id field
  static isUser(obj) {
    return #id in obj;
  }

  // Check if object has #isAdmin field
  static hasAdminField(obj) {
    return #isAdmin in obj;
  }
}

const user = new User(1);
const fakeUser = { id: 1 };

console.log('user is User:', User.isUser(user)); // true
console.log('fakeUser is User:', User.isUser(fakeUser)); // false

/**
 * EXAMPLE 7: Private Fields in Inheritance
 * Subclass cannot access parent's private fields
 */
console.log('\n--- Example 7: Private Fields in Inheritance ---');

class Parent {
  #privateField = 'parent private';
  _protectedField = 'parent protected'; // Convention
  publicField = 'parent public';

  getPrivate() {
    return this.#privateField;
  }

  #privateMethod() {
    return 'Called parent private method';
  }

  callPrivateMethod() {
    return this.#privateMethod();
  }
}

class Child extends Parent {
  #childPrivate = 'child private';

  showFields() {
    console.log('Public:', this.publicField); // Works
    console.log('Protected (convention):', this._protectedField); // Works
    // console.log("Private:", this.#privateField);  // SyntaxError!

    // Access via inherited method
    console.log('Parent private via method:', this.getPrivate());
    console.log('Parent private method:', this.callPrivateMethod());
  }
}

const child = new Child();
child.showFields();

/**
 * EXAMPLE 8: Immutable-like Objects with Private Fields
 * Preventing external mutation
 */
console.log('\n--- Example 8: Immutable-like Objects ---');

class Point {
  #x;
  #y;

  constructor(x, y) {
    this.#x = x;
    this.#y = y;
    Object.freeze(this); // Prevent adding new properties
  }

  get x() {
    return this.#x;
  }
  get y() {
    return this.#y;
  }

  // Returns new instances instead of mutating
  add(point) {
    return new Point(this.#x + point.x, this.#y + point.y);
  }

  subtract(point) {
    return new Point(this.#x - point.x, this.#y - point.y);
  }

  scale(factor) {
    return new Point(this.#x * factor, this.#y * factor);
  }

  distance(point) {
    const dx = this.#x - point.x;
    const dy = this.#y - point.y;
    return Math.sqrt(dx * dx + dy * dy);
  }

  toString() {
    return `Point(${this.#x}, ${this.#y})`;
  }
}

const p1 = new Point(3, 4);
const p2 = new Point(6, 8);
const p3 = p1.add(p2);

console.log(p1.toString()); // Point(3, 4) - unchanged
console.log(p3.toString()); // Point(9, 12) - new point
console.log('Distance:', p1.distance(p2));

// Cannot modify
// p1.x = 10;  // Fails silently (frozen) or throws in strict mode

/**
 * EXAMPLE 9: WeakMap Alternative Pattern
 * Pre-ES2022 way to achieve privacy
 */
console.log('\n--- Example 9: WeakMap Alternative ---');

// Old pattern using WeakMap
const _data = new WeakMap();

class PersonOld {
  constructor(name, secret) {
    _data.set(this, { name, secret });
  }

  get name() {
    return _data.get(this).name;
  }

  getSecret(password) {
    if (password !== 'opensesame') {
      throw new Error('Wrong password');
    }
    return _data.get(this).secret;
  }
}

// Modern pattern using #
class PersonNew {
  #name;
  #secret;

  constructor(name, secret) {
    this.#name = name;
    this.#secret = secret;
  }

  get name() {
    return this.#name;
  }

  getSecret(password) {
    if (password !== 'opensesame') {
      throw new Error('Wrong password');
    }
    return this.#secret;
  }
}

const oldPerson = new PersonOld('Alice', 'I love cats');
const newPerson = new PersonNew('Bob', 'I love dogs');

console.log('Old pattern:', oldPerson.name);
console.log('New pattern:', newPerson.name);
console.log('Old secret:', oldPerson.getSecret('opensesame'));
console.log('New secret:', newPerson.getSecret('opensesame'));

/**
 * EXAMPLE 10: State Machine with Private State
 * Encapsulating complex state transitions
 */
console.log('\n--- Example 10: State Machine ---');

class TrafficLight {
  static #STATES = ['green', 'yellow', 'red'];
  static #DURATIONS = {
    green: 3000,
    yellow: 1000,
    red: 2000,
  };

  #currentIndex = 0;
  #timerId = null;
  #isRunning = false;

  constructor() {
    // Initial state
  }

  get state() {
    return TrafficLight.#STATES[this.#currentIndex];
  }

  #transition() {
    this.#currentIndex = (this.#currentIndex + 1) % TrafficLight.#STATES.length;
    console.log(`Light changed to: ${this.state}`);
    return this.state;
  }

  #scheduleNext() {
    if (!this.#isRunning) return;

    const duration = TrafficLight.#DURATIONS[this.state];
    this.#timerId = setTimeout(() => {
      this.#transition();
      this.#scheduleNext();
    }, duration);
  }

  start() {
    if (this.#isRunning) return;
    this.#isRunning = true;
    console.log(`Starting at: ${this.state}`);
    this.#scheduleNext();
  }

  stop() {
    this.#isRunning = false;
    if (this.#timerId) {
      clearTimeout(this.#timerId);
      this.#timerId = null;
    }
    console.log('Traffic light stopped');
  }
}

const light = new TrafficLight();
console.log('Current state:', light.state);
// Uncomment to run the traffic light
// light.start();
// setTimeout(() => light.stop(), 10000);

/**
 * EXAMPLE 11: Validation with Private Methods
 * Internal validation logic
 */
console.log('\n--- Example 11: Validation ---');

class UserForm {
  #email;
  #password;
  #errors = [];

  constructor(email, password) {
    this.#errors = [];

    if (this.#validateEmail(email)) {
      this.#email = email;
    }

    if (this.#validatePassword(password)) {
      this.#password = password;
    }
  }

  // Private validation methods
  #validateEmail(email) {
    if (!email || !email.includes('@')) {
      this.#errors.push('Invalid email format');
      return false;
    }
    return true;
  }

  #validatePassword(password) {
    const errors = [];

    if (!password || password.length < 8) {
      errors.push('Password must be at least 8 characters');
    }
    if (!/[A-Z]/.test(password)) {
      errors.push('Password must contain uppercase letter');
    }
    if (!/[0-9]/.test(password)) {
      errors.push('Password must contain a number');
    }

    if (errors.length > 0) {
      this.#errors.push(...errors);
      return false;
    }
    return true;
  }

  get isValid() {
    return this.#errors.length === 0;
  }

  get errors() {
    return [...this.#errors];
  }

  getData() {
    if (!this.isValid) {
      throw new Error('Form is invalid');
    }
    return { email: this.#email, password: '****' };
  }
}

const validForm = new UserForm('test@example.com', 'Password123');
console.log('Valid form:', validForm.isValid); // true
console.log('Data:', validForm.getData());

const invalidForm = new UserForm('invalid', 'weak');
console.log('Invalid form:', invalidForm.isValid); // false
console.log('Errors:', invalidForm.errors);

/**
 * EXAMPLE 12: Event Emitter with Private Storage
 * Encapsulated event handling
 */
console.log('\n--- Example 12: Event Emitter ---');

class EventEmitter {
  #events = new Map();
  #maxListeners = 10;

  // Private helper methods
  #getListeners(event) {
    if (!this.#events.has(event)) {
      this.#events.set(event, []);
    }
    return this.#events.get(event);
  }

  #checkMaxListeners(event) {
    const listeners = this.#getListeners(event);
    if (listeners.length >= this.#maxListeners) {
      console.warn(
        `Warning: Event '${event}' has ${listeners.length} listeners. ` +
          `Consider increasing maxListeners.`
      );
    }
  }

  // Public interface
  on(event, callback) {
    this.#checkMaxListeners(event);
    this.#getListeners(event).push(callback);
    return this;
  }

  off(event, callback) {
    const listeners = this.#getListeners(event);
    const index = listeners.indexOf(callback);
    if (index !== -1) {
      listeners.splice(index, 1);
    }
    return this;
  }

  emit(event, ...args) {
    for (const callback of this.#getListeners(event)) {
      callback(...args);
    }
    return this;
  }

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

  setMaxListeners(n) {
    this.#maxListeners = n;
    return this;
  }
}

const emitter = new EventEmitter();

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

emitter.emit('connect');
emitter.emit('connect'); // Won't fire again
emitter.emit('message', 'Hello');
emitter.emit('message', 'World');

/**
 * EXAMPLE 13: Cache with Private TTL Logic
 * Encapsulated caching behavior
 */
console.log('\n--- Example 13: Cache with TTL ---');

class Cache {
  #store = new Map();
  #defaultTTL;

  constructor(defaultTTL = 60000) {
    // 1 minute default
    this.#defaultTTL = defaultTTL;
  }

  // Private expiry check
  #isExpired(entry) {
    if (!entry.expiry) return false;
    return Date.now() > entry.expiry;
  }

  // Private cleanup
  #cleanup() {
    for (const [key, entry] of this.#store) {
      if (this.#isExpired(entry)) {
        this.#store.delete(key);
      }
    }
  }

  set(key, value, ttl = this.#defaultTTL) {
    this.#store.set(key, {
      value,
      expiry: ttl ? Date.now() + ttl : null,
      createdAt: Date.now(),
    });
    return this;
  }

  get(key) {
    const entry = this.#store.get(key);
    if (!entry) return undefined;

    if (this.#isExpired(entry)) {
      this.#store.delete(key);
      return undefined;
    }

    return entry.value;
  }

  has(key) {
    return this.get(key) !== undefined;
  }

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

  clear() {
    this.#store.clear();
  }

  // Periodic cleanup
  startCleanup(interval = 60000) {
    setInterval(() => this.#cleanup(), interval);
  }

  get size() {
    this.#cleanup();
    return this.#store.size;
  }
}

const cache = new Cache(1000); // 1 second TTL
cache.set('temp', 'temporary value');
cache.set('permanent', 'permanent value', null); // No expiry

console.log('Temp:', cache.get('temp')); // "temporary value"
console.log('Size:', cache.size); // 2

// Wait for expiry
setTimeout(() => {
  console.log('After 1.5s - Temp:', cache.get('temp')); // undefined
  console.log('After 1.5s - Permanent:', cache.get('permanent')); // "permanent value"
}, 1500);

/**
 * EXAMPLE 14: Observable with Private Observers
 * Reactive pattern with encapsulation
 */
console.log('\n--- Example 14: Observable ---');

class Observable {
  #value;
  #observers = new Set();

  constructor(initialValue) {
    this.#value = initialValue;
  }

  get value() {
    return this.#value;
  }

  set value(newValue) {
    const oldValue = this.#value;
    this.#value = newValue;
    this.#notify(newValue, oldValue);
  }

  #notify(newValue, oldValue) {
    for (const observer of this.#observers) {
      observer(newValue, oldValue);
    }
  }

  subscribe(observer) {
    this.#observers.add(observer);
    // Return unsubscribe function
    return () => this.#observers.delete(observer);
  }

  // Computed observable
  map(transform) {
    const computed = new Observable(transform(this.#value));
    this.subscribe((newValue) => {
      computed.value = transform(newValue);
    });
    return computed;
  }
}

const count = new Observable(0);

const unsubscribe = count.subscribe((newVal, oldVal) => {
  console.log(`Count changed: ${oldVal} → ${newVal}`);
});

const doubled = count.map((x) => x * 2);
doubled.subscribe((val) => console.log(`Doubled: ${val}`));

count.value = 5;
count.value = 10;

unsubscribe();
count.value = 15; // No log from first observer

/**
 * EXAMPLE 15: Complete Example - Secure Session Manager
 * Comprehensive use of private fields and methods
 */
console.log('\n--- Example 15: Secure Session Manager ---');

class SessionManager {
  static #instance = null;
  static #SECRET_KEY = 'app-secret-key-123';

  #sessions = new Map();
  #tokenExpiry = 3600000; // 1 hour

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

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

  // Private token generation
  #generateToken() {
    const chars =
      'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let token = '';
    for (let i = 0; i < 32; i++) {
      token += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return token;
  }

  // Private session validation
  #isValidSession(session) {
    if (!session) return false;
    if (Date.now() > session.expiresAt) {
      return false;
    }
    return true;
  }

  // Private session cleanup
  #cleanupExpired() {
    for (const [token, session] of this.#sessions) {
      if (!this.#isValidSession(session)) {
        this.#sessions.delete(token);
      }
    }
  }

  createSession(userId, data = {}) {
    const token = this.#generateToken();
    const session = {
      userId,
      data,
      createdAt: Date.now(),
      expiresAt: Date.now() + this.#tokenExpiry,
    };

    this.#sessions.set(token, session);
    return token;
  }

  getSession(token) {
    const session = this.#sessions.get(token);
    if (!this.#isValidSession(session)) {
      this.#sessions.delete(token);
      return null;
    }
    return { ...session };
  }

  updateSession(token, data) {
    const session = this.#sessions.get(token);
    if (!this.#isValidSession(session)) {
      throw new Error('Invalid or expired session');
    }
    session.data = { ...session.data, ...data };
    return true;
  }

  destroySession(token) {
    return this.#sessions.delete(token);
  }

  get activeSessions() {
    this.#cleanupExpired();
    return this.#sessions.size;
  }
}

const sessionMgr = SessionManager.getInstance();
const token = sessionMgr.createSession('user123', { role: 'admin' });

console.log('Token:', token);
console.log('Session:', sessionMgr.getSession(token));
console.log('Active sessions:', sessionMgr.activeSessions);

sessionMgr.updateSession(token, { lastAction: 'login' });
console.log('Updated session:', sessionMgr.getSession(token));

/**
 * EXAMPLE 16: Private Fields with Proxy
 * Note: Proxies have limitations with private fields
 */
console.log('\n--- Example 16: Private Fields with Proxy ---');

class Counter {
  #count = 0;

  increment() {
    this.#count++;
    return this.#count;
  }

  get count() {
    return this.#count;
  }
}

const counter = new Counter();

// Creating a proxy for logging
const loggedCounter = new Proxy(counter, {
  get(target, prop, receiver) {
    const value = Reflect.get(target, prop, receiver);
    if (typeof value === 'function') {
      return function (...args) {
        console.log(`Calling ${String(prop)}()`);
        // Must call with original target for private fields
        return value.apply(target, args);
      };
    }
    return value;
  },
});

console.log('Via proxy:', loggedCounter.increment()); // Works
console.log('Count via proxy:', loggedCounter.count); // Works

/**
 * EXAMPLE 17: Symbol vs Private Fields Comparison
 * When to use which approach
 */
console.log('\n--- Example 17: Symbol vs Private Fields ---');

// Symbol approach (discoverable via reflection)
const _secretSym = Symbol('secret');

class SymbolPrivate {
  constructor(secret) {
    this[_secretSym] = secret;
  }

  getSecret() {
    return this[_secretSym];
  }
}

// True private fields (not discoverable)
class TruePrivate {
  #secret;

  constructor(secret) {
    this.#secret = secret;
  }

  getSecret() {
    return this.#secret;
  }
}

const symInstance = new SymbolPrivate('symbol secret');
const privateInstance = new TruePrivate('private secret');

// Symbol properties are discoverable
console.log('Symbol props:', Object.getOwnPropertySymbols(symInstance)); // [Symbol(secret)]

// Private fields are not
console.log('Own props:', Object.getOwnPropertyNames(privateInstance)); // []
console.log('Keys:', Object.keys(privateInstance)); // []

/**
 * EXAMPLE 18: Private Methods for Strategy Pattern
 * Encapsulating strategy implementations
 */
console.log('\n--- Example 18: Strategy with Private Methods ---');

class Sorter {
  #strategy = 'quick';

  // Private sorting strategies
  #bubbleSort(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;
  }

  #quickSort(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 [...this.#quickSort(left), ...middle, ...this.#quickSort(right)];
  }

  #mergeSort(arr) {
    if (arr.length <= 1) return arr;
    const mid = Math.floor(arr.length / 2);
    const left = this.#mergeSort(arr.slice(0, mid));
    const right = this.#mergeSort(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];
  }

  setStrategy(strategy) {
    const valid = ['bubble', 'quick', 'merge'];
    if (!valid.includes(strategy)) {
      throw new Error(`Invalid strategy. Use: ${valid.join(', ')}`);
    }
    this.#strategy = strategy;
  }

  sort(arr) {
    switch (this.#strategy) {
      case 'bubble':
        return this.#bubbleSort(arr);
      case 'quick':
        return this.#quickSort(arr);
      case 'merge':
        return this.#mergeSort(arr);
      default:
        return this.#quickSort(arr);
    }
  }
}

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

console.log('Quick sort:', sorter.sort(data));

sorter.setStrategy('merge');
console.log('Merge sort:', sorter.sort(data));

/**
 * EXAMPLE 19: Builder Pattern with Private State
 * Fluent builder with encapsulated state
 */
console.log('\n--- Example 19: Builder Pattern ---');

class RequestBuilder {
  #method = 'GET';
  #url = '';
  #headers = new Map();
  #body = null;
  #timeout = 30000;

  // Fluent methods
  get(url) {
    this.#method = 'GET';
    this.#url = url;
    return this;
  }

  post(url) {
    this.#method = 'POST';
    this.#url = url;
    return this;
  }

  put(url) {
    this.#method = 'PUT';
    this.#url = url;
    return this;
  }

  header(key, value) {
    this.#headers.set(key, value);
    return this;
  }

  json(data) {
    this.#body = JSON.stringify(data);
    this.#headers.set('Content-Type', 'application/json');
    return this;
  }

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

  build() {
    return {
      method: this.#method,
      url: this.#url,
      headers: Object.fromEntries(this.#headers),
      body: this.#body,
      timeout: this.#timeout,
    };
  }

  // Reset builder for reuse
  reset() {
    this.#method = 'GET';
    this.#url = '';
    this.#headers.clear();
    this.#body = null;
    this.#timeout = 30000;
    return this;
  }
}

const request = new RequestBuilder()
  .post('https://api.example.com/users')
  .header('Authorization', 'Bearer token123')
  .json({ name: 'Alice', email: 'alice@example.com' })
  .timeout(5000)
  .build();

console.log('Built request:', request);

/**
 * EXAMPLE 20: Complex State Machine with Private Internals
 * Full state machine implementation
 */
console.log('\n--- Example 20: Complex State Machine ---');

class StateMachine {
  #currentState;
  #states = new Map();
  #transitions = [];
  #onEnter = new Map();
  #onExit = new Map();

  constructor(initialState) {
    this.#currentState = initialState;
  }

  // Private transition finder
  #findTransition(event) {
    return this.#transitions.find(
      (t) => t.from === this.#currentState && t.event === event
    );
  }

  // Private state change handler
  #changeState(newState) {
    const oldState = this.#currentState;

    // Exit handler
    const exitHandler = this.#onExit.get(oldState);
    if (exitHandler) exitHandler(oldState, newState);

    this.#currentState = newState;

    // Enter handler
    const enterHandler = this.#onEnter.get(newState);
    if (enterHandler) enterHandler(newState, oldState);
  }

  // Public configuration methods
  addState(name) {
    this.#states.set(name, { name });
    return this;
  }

  addTransition(from, event, to, guard = null) {
    this.#transitions.push({ from, event, to, guard });
    return this;
  }

  onEnterState(state, handler) {
    this.#onEnter.set(state, handler);
    return this;
  }

  onExitState(state, handler) {
    this.#onExit.set(state, handler);
    return this;
  }

  // Public action methods
  dispatch(event, data = null) {
    const transition = this.#findTransition(event);

    if (!transition) {
      console.log(`No transition for '${event}' from '${this.#currentState}'`);
      return false;
    }

    // Check guard condition
    if (transition.guard && !transition.guard(data)) {
      console.log(`Guard prevented transition for '${event}'`);
      return false;
    }

    console.log(
      `Transition: ${this.#currentState} --${event}--> ${transition.to}`
    );
    this.#changeState(transition.to);
    return true;
  }

  get state() {
    return this.#currentState;
  }

  can(event) {
    return this.#findTransition(event) !== null;
  }
}

// Create an order state machine
const orderMachine = new StateMachine('pending')
  .addState('pending')
  .addState('confirmed')
  .addState('shipped')
  .addState('delivered')
  .addState('cancelled')

  .addTransition('pending', 'confirm', 'confirmed')
  .addTransition('pending', 'cancel', 'cancelled')
  .addTransition('confirmed', 'ship', 'shipped')
  .addTransition('confirmed', 'cancel', 'cancelled')
  .addTransition('shipped', 'deliver', 'delivered')

  .onEnterState('confirmed', () => console.log('📧 Sending confirmation email'))
  .onEnterState('shipped', () => console.log('📦 Generating tracking number'))
  .onEnterState('delivered', () => console.log('✅ Order completed!'));

console.log('Initial state:', orderMachine.state);
orderMachine.dispatch('confirm');
orderMachine.dispatch('ship');
orderMachine.dispatch('deliver');

// Try invalid transition
orderMachine.dispatch('cancel'); // No transition from 'delivered'

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