javascript

examples

examples.js
/**
 * ========================================
 * 8.2 CLASS INHERITANCE - CODE EXAMPLES
 * ========================================
 */

/**
 * EXAMPLE 1: Basic Inheritance with extends
 * Child class inherits from parent class
 */
console.log('--- Example 1: Basic Inheritance ---');

class Animal {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(`${this.name} makes a sound`);
  }

  move(distance) {
    console.log(`${this.name} moved ${distance} meters`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name} barks: Woof!`);
  }
}

const dog = new Dog('Rex');
dog.speak(); // Inherited from Animal
dog.bark(); // Own method
dog.move(10); // Inherited from Animal

/**
 * EXAMPLE 2: super() in Constructor
 * Calling parent constructor
 */
console.log('\n--- Example 2: super() in Constructor ---');

class Vehicle {
  constructor(make, model) {
    this.make = make;
    this.model = model;
    this.speed = 0;
  }

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

class Car extends Vehicle {
  constructor(make, model, doors = 4) {
    // Must call super() before using 'this'
    super(make, model);
    this.doors = doors;
  }

  describe() {
    return `${super.describe()} with ${this.doors} doors`;
  }
}

const car = new Car('Toyota', 'Camry', 4);
console.log(car.describe()); // "Toyota Camry with 4 doors"
console.log(car.make); // "Toyota" (inherited property)

/**
 * EXAMPLE 3: Method Overriding
 * Child class overrides parent methods
 */
console.log('\n--- Example 3: Method Overriding ---');

class Shape {
  constructor(color) {
    this.color = color;
  }

  describe() {
    return `A ${this.color} shape`;
  }

  area() {
    throw new Error('area() must be implemented by subclass');
  }
}

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

  // Override describe
  describe() {
    return `A ${this.color} circle with radius ${this.radius}`;
  }

  // Implement abstract method
  area() {
    return Math.PI * this.radius ** 2;
  }
}

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

  describe() {
    return `A ${this.color} rectangle (${this.width}x${this.height})`;
  }

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

const circle = new Circle('red', 5);
const rect = new Rectangle('blue', 4, 6);

console.log(circle.describe()); // "A red circle with radius 5"
console.log(`Circle area: ${circle.area().toFixed(2)}`);

console.log(rect.describe()); // "A blue rectangle (4x6)"
console.log(`Rectangle area: ${rect.area()}`);

/**
 * EXAMPLE 4: super.method() - Calling Parent Methods
 * Extending parent behavior instead of replacing
 */
console.log('\n--- Example 4: super.method() ---');

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

  error(message) {
    console.error(`[ERROR]: ${message}`);
  }
}

class TimestampLogger extends Logger {
  log(message) {
    const timestamp = new Date().toISOString();
    super.log(`[${timestamp}] ${message}`);
  }

  error(message) {
    const timestamp = new Date().toISOString();
    super.error(`[${timestamp}] ${message}`);
  }
}

class PrefixLogger extends TimestampLogger {
  constructor(prefix) {
    super();
    this.prefix = prefix;
  }

  log(message) {
    super.log(`${this.prefix}: ${message}`);
  }
}

const logger = new PrefixLogger('APP');
logger.log('Application started');

/**
 * EXAMPLE 5: Prototype Chain Verification
 * Understanding the inheritance chain
 */
console.log('\n--- Example 5: Prototype Chain ---');

class A {
  methodA() {
    return 'A';
  }
}

class B extends A {
  methodB() {
    return 'B';
  }
}

class C extends B {
  methodC() {
    return 'C';
  }
}

const c = new C();

// Verify prototype chain
console.log('c instanceof C:', c instanceof C); // true
console.log('c instanceof B:', c instanceof B); // true
console.log('c instanceof A:', c instanceof A); // true
console.log('c instanceof Object:', c instanceof Object); // true

// Prototype chain
console.log(Object.getPrototypeOf(c) === C.prototype); // true
console.log(Object.getPrototypeOf(C.prototype) === B.prototype); // true
console.log(Object.getPrototypeOf(B.prototype) === A.prototype); // true

// All methods accessible
console.log(c.methodA(), c.methodB(), c.methodC()); // "A" "B" "C"

/**
 * EXAMPLE 6: Extending Built-in Array
 * Creating custom array types
 */
console.log('\n--- Example 6: Extending Array ---');

class Stack extends Array {
  peek() {
    return this[this.length - 1];
  }

  isEmpty() {
    return this.length === 0;
  }

  // Control what methods like map() return
  static get [Symbol.species]() {
    return Array;
  }
}

const stack = new Stack();
stack.push(1, 2, 3, 4, 5);

console.log('Stack:', [...stack]); // [1, 2, 3, 4, 5]
console.log('Peek:', stack.peek()); // 5
console.log('Pop:', stack.pop()); // 5
console.log('isEmpty:', stack.isEmpty()); // false

// Inherited methods work
console.log(
  'Filter:',
  stack.filter((x) => x > 2)
); // [3, 4]

// Symbol.species means map returns Array, not Stack
const mapped = stack.map((x) => x * 2);
console.log('Mapped is Array:', mapped instanceof Array); // true
console.log('Mapped is Stack:', mapped instanceof Stack); // false

/**
 * EXAMPLE 7: Extending Built-in Error
 * Custom error types
 */
console.log('\n--- Example 7: Custom Errors ---');

class AppError extends Error {
  constructor(message, code) {
    super(message);
    this.name = 'AppError';
    this.code = code;
    this.timestamp = new Date();

    // Capture stack trace (V8 engines)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, AppError);
    }
  }
}

class ValidationError extends AppError {
  constructor(message, field) {
    super(message, 'VALIDATION_ERROR');
    this.name = 'ValidationError';
    this.field = field;
  }
}

class NetworkError extends AppError {
  constructor(message, statusCode) {
    super(message, 'NETWORK_ERROR');
    this.name = 'NetworkError';
    this.statusCode = statusCode;
  }
}

function handleError(error) {
  if (error instanceof ValidationError) {
    console.log(`Validation failed for '${error.field}': ${error.message}`);
  } else if (error instanceof NetworkError) {
    console.log(`Network error (${error.statusCode}): ${error.message}`);
  } else if (error instanceof AppError) {
    console.log(`App error [${error.code}]: ${error.message}`);
  } else {
    console.log(`Unknown error: ${error.message}`);
  }
}

handleError(new ValidationError('Invalid email format', 'email'));
handleError(new NetworkError('Connection refused', 503));

/**
 * EXAMPLE 8: Extending Map with Default Values
 * Enhanced Map with default value support
 */
console.log('\n--- Example 8: Extending Map ---');

class DefaultMap extends Map {
  constructor(defaultFactory, entries) {
    super(entries);
    this.defaultFactory = defaultFactory;
  }

  get(key) {
    if (!this.has(key)) {
      const defaultValue =
        typeof this.defaultFactory === 'function'
          ? this.defaultFactory()
          : this.defaultFactory;
      this.set(key, defaultValue);
      return defaultValue;
    }
    return super.get(key);
  }
}

// Counter (default 0)
const counter = new DefaultMap(0);
counter.set('a', counter.get('a') + 1);
counter.set('a', counter.get('a') + 1);
counter.set('b', counter.get('b') + 1);
console.log('Counter a:', counter.get('a')); // 2
console.log('Counter b:', counter.get('b')); // 1

// Grouping (default empty array)
const groups = new DefaultMap(() => []);
groups.get('admins').push('Alice');
groups.get('admins').push('Bob');
groups.get('users').push('Charlie');
console.log('Groups:', Object.fromEntries(groups));

/**
 * EXAMPLE 9: Abstract Base Class Pattern
 * Simulating abstract classes
 */
console.log('\n--- Example 9: Abstract Base Class ---');

class AbstractRepository {
  constructor() {
    if (new.target === AbstractRepository) {
      throw new Error('AbstractRepository cannot be instantiated directly');
    }
  }

  // Abstract methods
  findById(id) {
    throw new Error('findById() must be implemented');
  }

  findAll() {
    throw new Error('findAll() must be implemented');
  }

  save(entity) {
    throw new Error('save() must be implemented');
  }

  delete(id) {
    throw new Error('delete() must be implemented');
  }

  // Concrete method using abstract methods
  exists(id) {
    return this.findById(id) !== null;
  }
}

class UserRepository extends AbstractRepository {
  constructor() {
    super();
    this.users = new Map();
  }

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

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

  save(user) {
    this.users.set(user.id, user);
    return user;
  }

  delete(id) {
    return this.users.delete(id);
  }
}

const userRepo = new UserRepository();
userRepo.save({ id: 1, name: 'Alice' });
userRepo.save({ id: 2, name: 'Bob' });

console.log('Find by ID:', userRepo.findById(1));
console.log('Exists:', userRepo.exists(1));
console.log('All users:', userRepo.findAll());

// This would throw:
// const abstract = new AbstractRepository(); // Error!

/**
 * EXAMPLE 10: Multi-Level Inheritance
 * Deep inheritance hierarchies
 */
console.log('\n--- Example 10: Multi-Level Inheritance ---');

class Entity {
  constructor(id) {
    this.id = id;
    this.createdAt = new Date();
  }

  toString() {
    return `Entity(${this.id})`;
  }
}

class Person extends Entity {
  constructor(id, name, email) {
    super(id);
    this.name = name;
    this.email = email;
  }

  toString() {
    return `Person(${this.id}, ${this.name})`;
  }
}

class Employee extends Person {
  constructor(id, name, email, department) {
    super(id, name, email);
    this.department = department;
  }

  toString() {
    return `Employee(${this.id}, ${this.name}, ${this.department})`;
  }
}

class Manager extends Employee {
  constructor(id, name, email, department) {
    super(id, name, email, department);
    this.directReports = [];
  }

  addReport(employee) {
    this.directReports.push(employee);
  }

  toString() {
    return `Manager(${this.id}, ${this.name}, ${this.directReports.length} reports)`;
  }
}

const manager = new Manager(1, 'Alice', 'alice@example.com', 'Engineering');
const emp1 = new Employee(2, 'Bob', 'bob@example.com', 'Engineering');
const emp2 = new Employee(3, 'Charlie', 'charlie@example.com', 'Engineering');

manager.addReport(emp1);
manager.addReport(emp2);

console.log(manager.toString()); // "Manager(1, Alice, 2 reports)"
console.log(manager instanceof Manager); // true
console.log(manager instanceof Employee); // true
console.log(manager instanceof Person); // true
console.log(manager instanceof Entity); // true

/**
 * EXAMPLE 11: Overriding Getters and Setters
 * Inheriting and extending accessors
 */
console.log('\n--- Example 11: Overriding Accessors ---');

class Product {
  constructor(name, price) {
    this.name = name;
    this._price = price;
  }

  get price() {
    return this._price;
  }

  set price(value) {
    if (value < 0) throw new Error('Price cannot be negative');
    this._price = value;
  }

  get displayPrice() {
    return `$${this._price.toFixed(2)}`;
  }
}

class DiscountedProduct extends Product {
  constructor(name, price, discount) {
    super(name, price);
    this.discount = discount; // Percentage (0-100)
  }

  // Override getter to apply discount
  get price() {
    const discounted = super.price * (1 - this.discount / 100);
    return Math.round(discounted * 100) / 100;
  }

  get originalPrice() {
    return super.price;
  }

  get displayPrice() {
    return `$${this.price.toFixed(2)} (was ${super.displayPrice})`;
  }
}

const product = new Product('Widget', 100);
console.log(product.displayPrice); // "$100.00"

const discounted = new DiscountedProduct('Widget', 100, 20);
console.log(discounted.price); // 80
console.log(discounted.originalPrice); // 100
console.log(discounted.displayPrice); // "$80.00 (was $100.00)"

/**
 * EXAMPLE 12: instanceof and Custom Checks
 * Customizing instanceof behavior
 */
console.log('\n--- Example 12: instanceof Customization ---');

class Validator {
  static [Symbol.hasInstance](obj) {
    return (
      obj !== null &&
      typeof obj === 'object' &&
      typeof obj.validate === 'function'
    );
  }
}

const validatable1 = {
  validate() {
    return true;
  },
};

const validatable2 = {
  check() {
    return true;
  },
};

console.log(validatable1 instanceof Validator); // true
console.log(validatable2 instanceof Validator); // false

// Useful for duck typing
class Iterable {
  static [Symbol.hasInstance](obj) {
    return obj != null && typeof obj[Symbol.iterator] === 'function';
  }
}

console.log([1, 2, 3] instanceof Iterable); // true
console.log('hello' instanceof Iterable); // true
console.log({ a: 1 } instanceof Iterable); // false

/**
 * EXAMPLE 13: Constructor Return Override
 * Returning different object from constructor
 */
console.log('\n--- Example 13: Constructor Return Override ---');

class Singleton {
  static instance = null;

  constructor(name) {
    if (Singleton.instance) {
      return Singleton.instance;
    }

    this.name = name;
    this.createdAt = new Date();
    Singleton.instance = this;
  }
}

const s1 = new Singleton('First');
const s2 = new Singleton('Second');

console.log(s1 === s2); // true
console.log(s1.name); // "First" (Second creation ignored)

// Extending singleton
class ChildSingleton extends Singleton {
  constructor(name, value) {
    super(name);
    // Only sets if this is first instance
    if (!this.value) {
      this.value = value;
    }
  }
}

/**
 * EXAMPLE 14: Method Resolution Order
 * How methods are looked up in the chain
 */
console.log('\n--- Example 14: Method Resolution ---');

class Base {
  method() {
    return 'Base.method()';
  }

  static staticMethod() {
    return 'Base.staticMethod()';
  }
}

class Middle extends Base {
  method() {
    return `Middle.method() -> ${super.method()}`;
  }
}

class Derived extends Middle {
  method() {
    return `Derived.method() -> ${super.method()}`;
  }
}

const d = new Derived();
console.log(d.method());
// "Derived.method() -> Middle.method() -> Base.method()"

// Static method resolution
console.log(Derived.staticMethod()); // "Base.staticMethod()" (inherited)

/**
 * EXAMPLE 15: Calling Different Parent Constructor
 * Dynamic parent selection (mixin-like)
 */
console.log('\n--- Example 15: Dynamic Inheritance ---');

function createAnimalClass(BaseClass) {
  return class extends BaseClass {
    speak() {
      console.log(`${this.name} makes a sound`);
    }
  };
}

class Named {
  constructor(name) {
    this.name = name;
  }
}

class Identified {
  constructor(id) {
    this.id = id;
    this.name = `Entity-${id}`;
  }
}

const NamedAnimal = createAnimalClass(Named);
const IdentifiedAnimal = createAnimalClass(Identified);

const namedDog = new NamedAnimal('Rex');
const identifiedDog = new IdentifiedAnimal(123);

namedDog.speak(); // "Rex makes a sound"
identifiedDog.speak(); // "Entity-123 makes a sound"

/**
 * EXAMPLE 16: Protected-like Members
 * Simulating protected access
 */
console.log('\n--- Example 16: Protected Members ---');

// Convention: Use _ prefix for "protected" members
class Component {
  constructor(name) {
    this.name = name;
    this._state = {}; // "Protected" - for subclasses
  }

  _updateState(newState) {
    // "Protected" method
    this._state = { ...this._state, ...newState };
    this._onStateChange();
  }

  _onStateChange() {
    // Hook for subclasses
  }

  getState() {
    return { ...this._state };
  }
}

class Counter extends Component {
  constructor() {
    super('Counter');
    this._updateState({ count: 0 });
  }

  increment() {
    this._updateState({ count: this._state.count + 1 });
  }

  decrement() {
    this._updateState({ count: this._state.count - 1 });
  }

  _onStateChange() {
    console.log(`Counter state: ${this._state.count}`);
  }
}

const counter = new Counter();
counter.increment();
counter.increment();
counter.decrement();

/**
 * EXAMPLE 17: Polymorphism Example
 * Same interface, different implementations
 */
console.log('\n--- Example 17: Polymorphism ---');

class PaymentProcessor {
  process(amount) {
    throw new Error('process() must be implemented');
  }

  refund(transactionId) {
    throw new Error('refund() must be implemented');
  }
}

class CreditCardProcessor extends PaymentProcessor {
  process(amount) {
    console.log(`Processing $${amount} via Credit Card`);
    return { success: true, transactionId: 'CC-' + Date.now() };
  }

  refund(transactionId) {
    console.log(`Refunding transaction ${transactionId}`);
    return { success: true };
  }
}

class PayPalProcessor extends PaymentProcessor {
  process(amount) {
    console.log(`Processing $${amount} via PayPal`);
    return { success: true, transactionId: 'PP-' + Date.now() };
  }

  refund(transactionId) {
    console.log(`Refunding PayPal transaction ${transactionId}`);
    return { success: true };
  }
}

class CryptoProcessor extends PaymentProcessor {
  process(amount) {
    console.log(`Processing $${amount} in cryptocurrency`);
    return { success: true, transactionId: 'CRYPTO-' + Date.now() };
  }

  refund(transactionId) {
    console.log(`Crypto refund for ${transactionId}`);
    return { success: true };
  }
}

// Polymorphic function - works with any PaymentProcessor
function checkout(processor, amount) {
  const result = processor.process(amount);
  if (result.success) {
    console.log(`Transaction ID: ${result.transactionId}`);
  }
  return result;
}

const processors = [
  new CreditCardProcessor(),
  new PayPalProcessor(),
  new CryptoProcessor(),
];

for (const processor of processors) {
  checkout(processor, 99.99);
}

/**
 * EXAMPLE 18: Template Method Pattern
 * Parent defines algorithm, children provide implementation
 */
console.log('\n--- Example 18: Template Method Pattern ---');

class DataExporter {
  // Template method - defines the algorithm
  export(data) {
    const prepared = this.prepare(data);
    const formatted = this.format(prepared);
    const validated = this.validate(formatted);
    return this.output(validated);
  }

  // Hook methods - can be overridden
  prepare(data) {
    return data;
  }

  validate(data) {
    return data;
  }

  // Abstract methods - must be overridden
  format(data) {
    throw new Error('format() must be implemented');
  }

  output(data) {
    throw new Error('output() must be implemented');
  }
}

class JSONExporter extends DataExporter {
  format(data) {
    return JSON.stringify(data, null, 2);
  }

  output(data) {
    console.log('JSON Output:', data);
    return data;
  }
}

class CSVExporter extends DataExporter {
  prepare(data) {
    // Ensure array format
    return Array.isArray(data) ? data : [data];
  }

  format(data) {
    if (data.length === 0) return '';
    const headers = Object.keys(data[0]);
    const rows = data.map((row) => headers.map((h) => row[h]).join(','));
    return [headers.join(','), ...rows].join('\n');
  }

  output(data) {
    console.log('CSV Output:\n' + data);
    return data;
  }
}

const data = [
  { name: 'Alice', age: 30 },
  { name: 'Bob', age: 25 },
];

const jsonExporter = new JSONExporter();
const csvExporter = new CSVExporter();

jsonExporter.export(data);
csvExporter.export(data);

/**
 * EXAMPLE 19: Complete Inheritance Example
 * Real-world scenario with full inheritance chain
 */
console.log('\n--- Example 19: Complete Example ---');

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

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

class Collection extends EventEmitter {
  constructor() {
    super();
    this._items = [];
  }

  add(item) {
    this._items.push(item);
    this.emit('add', item);
    return this;
  }

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

  get items() {
    return [...this._items];
  }

  get length() {
    return this._items.length;
  }
}

class UniqueCollection extends Collection {
  add(item) {
    if (!this._items.includes(item)) {
      super.add(item);
    }
    return this;
  }
}

class SortedCollection extends Collection {
  constructor(compareFn = (a, b) => a - b) {
    super();
    this._compareFn = compareFn;
  }

  add(item) {
    super.add(item);
    this._items.sort(this._compareFn);
    return this;
  }
}

// Usage
const unique = new UniqueCollection();
unique.on('add', (item) => console.log(`Added: ${item}`));

unique.add(1).add(2).add(1).add(3);
console.log('Unique items:', unique.items); // [1, 2, 3]

const sorted = new SortedCollection();
sorted.add(3).add(1).add(2);
console.log('Sorted items:', sorted.items); // [1, 2, 3]

/**
 * EXAMPLE 20: Mixin-like Inheritance
 * Combining behaviors from multiple sources
 */
console.log('\n--- Example 20: Mixin-like Inheritance ---');

// 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 Serializable = (Base) =>
  class extends Base {
    toJSON() {
      return { ...this };
    }

    toObject() {
      return Object.fromEntries(
        Object.entries(this).filter(([key]) => !key.startsWith('_'))
      );
    }
  };

const Validatable = (Base) =>
  class extends Base {
    validate() {
      const errors = [];
      for (const [field, rules] of Object.entries(
        this.constructor.validationRules || {}
      )) {
        for (const rule of rules) {
          const error = rule(this[field], field);
          if (error) errors.push(error);
        }
      }
      return { valid: errors.length === 0, errors };
    }
  };

// Compose mixins
class BaseModel {
  constructor(data) {
    Object.assign(this, data);
  }
}

class User extends Validatable(Serializable(Timestamped(BaseModel))) {
  static validationRules = {
    name: [
      (v, f) => (!v ? `${f} is required` : null),
      (v, f) => (v && v.length < 2 ? `${f} too short` : null),
    ],
    email: [(v, f) => (!v ? `${f} is required` : null)],
  };
}

const user = new User({ name: 'Alice', email: 'alice@example.com' });
console.log('User created at:', user.createdAt);
console.log('Validation:', user.validate());
console.log('JSON:', user.toJSON());

user.touch();
console.log('Updated at:', user.updatedAt);

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