+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 36 of 72

๐Ÿ”„ Method Overriding: Customizing Inherited Behavior

Master method overriding in TypeScript to customize parent class behavior with practical examples and best practices ๐Ÿš€

๐Ÿš€Intermediate
22 min read

Prerequisites

  • Class inheritance knowledge ๐Ÿ—๏ธ
  • Super keyword understanding ๐Ÿ“ž
  • Method definitions ๐Ÿ“

What you'll learn

  • Understand method overriding fundamentals ๐ŸŽฏ
  • Override methods effectively ๐Ÿ”„
  • Combine overriding with super calls ๐Ÿš€
  • Apply polymorphism patterns ๐ŸŽญ

๐ŸŽฏ Introduction

Welcome to the powerful world of method overriding! ๐ŸŽ‰ In this guide, weโ€™ll explore how to customize and enhance inherited behavior by replacing parent class methods with your own implementations.

Youโ€™ll discover how method overriding is like redecorating a room ๐Ÿ  - you keep the structure but change how things work inside. Whether youโ€™re building game characters with unique abilities ๐ŸŽฎ or payment systems with different processing methods ๐Ÿ’ณ, understanding method overriding is essential for flexible, maintainable code.

By the end of this tutorial, youโ€™ll feel confident customizing any inherited behavior! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Method Overriding

๐Ÿค” What is Method Overriding?

Method overriding is like giving a child class the ability to say โ€œIโ€™ll do this my own way!โ€ ๐Ÿ™‹โ€โ™‚๏ธ Think of it as customizing a recipe - you keep the recipe name but change the ingredients and steps.

In TypeScript terms, method overriding allows a child class to provide a specific implementation of a method thatโ€™s already defined in its parent class. This means you can:

  • โœจ Customize behavior for specific types
  • ๐Ÿš€ Implement specialized functionality
  • ๐Ÿ›ก๏ธ Maintain consistent interfaces
  • ๐Ÿ”ง Create polymorphic behavior

๐Ÿ’ก Why Use Method Overriding?

Hereโ€™s why developers love method overriding:

  1. Customization ๐ŸŽจ: Each subclass can behave differently
  2. Polymorphism ๐ŸŽญ: Same method name, different behaviors
  3. Flexibility ๐Ÿคธโ€โ™‚๏ธ: Change behavior without changing interface
  4. Specialization ๐ŸŽฏ: Fine-tune functionality for specific cases

Real-world example: Imagine a drawing application ๐ŸŽจ. Your base Shape class has a draw() method, but Circle, Rectangle, and Triangle each override it to draw themselves differently.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

// ๐ŸŽต Base music player
class MusicPlayer {
  currentSong: string = '';
  volume: number = 50;

  // ๐ŸŽต Basic play method
  play(song: string): void {
    this.currentSong = song;
    console.log(`๐ŸŽต Playing: "${song}" at volume ${this.volume}`);
  }

  // โน๏ธ Basic stop method
  stop(): void {
    console.log(`โน๏ธ Stopped playing: "${this.currentSong}"`);
    this.currentSong = '';
  }

  // ๐Ÿ”Š Volume control
  setVolume(volume: number): void {
    this.volume = Math.max(0, Math.min(100, volume));
    console.log(`๐Ÿ”Š Volume set to: ${this.volume}`);
  }

  // ๐Ÿ“Š Player status
  getStatus(): string {
    return this.currentSong 
      ? `โ–ถ๏ธ Playing: "${this.currentSong}" | ๐Ÿ”Š ${this.volume}%`
      : 'โน๏ธ Stopped';
  }
}

// ๐ŸŽง Premium player with enhanced features
class PremiumMusicPlayer extends MusicPlayer {
  private equalizer: string = 'flat';
  private playlistMode: boolean = false;

  // ๐Ÿ”„ Override play method with premium features
  play(song: string): void {
    // ๐ŸŽต Custom premium play logic
    this.currentSong = song;
    console.log(`๐ŸŽง Premium Playing: "${song}"`);
    console.log(`๐ŸŽ›๏ธ Equalizer: ${this.equalizer} | ๐Ÿ”Š Volume: ${this.volume}`);
    
    if (this.playlistMode) {
      console.log('๐Ÿ“‹ Playlist mode active - next song queued');
    }
  }

  // ๐Ÿ”„ Override stop method with premium features
  stop(): void {
    if (this.currentSong) {
      console.log(`โธ๏ธ Premium Stop: Saving position for "${this.currentSong}"`);
      console.log('๐Ÿ’พ Progress saved for resume later');
    }
    
    this.currentSong = '';
    console.log('โน๏ธ Premium player stopped');
  }

  // ๐ŸŽ›๏ธ Premium-specific method
  setEqualizer(preset: string): void {
    this.equalizer = preset;
    console.log(`๐ŸŽ›๏ธ Equalizer set to: ${preset}`);
  }

  // ๐Ÿ“‹ Toggle playlist mode
  togglePlaylistMode(): void {
    this.playlistMode = !this.playlistMode;
    console.log(`๐Ÿ“‹ Playlist mode: ${this.playlistMode ? 'ON' : 'OFF'}`);
  }

  // ๐Ÿ”„ Override status with premium info
  getStatus(): string {
    const baseStatus = this.currentSong 
      ? `โ–ถ๏ธ Premium Playing: "${this.currentSong}"`
      : 'โน๏ธ Premium Stopped';
    
    return `${baseStatus} | ๐ŸŽ›๏ธ ${this.equalizer} | ๐Ÿ“‹ ${this.playlistMode ? 'Playlist' : 'Single'} | ๐Ÿ”Š ${this.volume}%`;
  }
}

// ๐ŸŽฎ Let's test the overriding!
const basicPlayer = new MusicPlayer();
const premiumPlayer = new PremiumMusicPlayer();

console.log('=== Basic Player ===');
basicPlayer.play('TypeScript Blues');
console.log(basicPlayer.getStatus());
basicPlayer.stop();

console.log('\n=== Premium Player ===');
premiumPlayer.setEqualizer('rock');
premiumPlayer.togglePlaylistMode();
premiumPlayer.play('Advanced TypeScript Symphony'); // Different behavior!
console.log(premiumPlayer.getStatus());
premiumPlayer.stop(); // Different behavior!

๐Ÿ’ก Explanation: Notice how the PremiumMusicPlayer overrides play(), stop(), and getStatus() methods to provide enhanced functionality while keeping the same method signatures!

๐ŸŽฏ Overriding with Super Calls

Sometimes you want to enhance rather than completely replace parent behavior:

// ๐Ÿช Base online store
class OnlineStore {
  items: Map<string, number> = new Map(); // item -> price
  discountRate: number = 0;

  // ๐Ÿ›’ Add item to store
  addItem(name: string, price: number): void {
    this.items.set(name, price);
    console.log(`โž• Added "${name}" for $${price}`);
  }

  // ๐Ÿ’ฐ Calculate total price
  calculateTotal(itemsToBuy: string[]): number {
    let total = 0;
    
    for (const item of itemsToBuy) {
      const price = this.items.get(item);
      if (price) {
        total += price;
      } else {
        console.log(`โŒ Item "${item}" not found`);
      }
    }

    const discount = total * this.discountRate;
    const finalTotal = total - discount;
    
    console.log(`๐Ÿ’ฐ Subtotal: $${total.toFixed(2)}`);
    if (discount > 0) {
      console.log(`๐ŸŽŠ Discount: -$${discount.toFixed(2)}`);
    }
    console.log(`๐Ÿ’ณ Total: $${finalTotal.toFixed(2)}`);
    
    return finalTotal;
  }

  // ๐ŸŽฏ Apply discount
  setDiscount(rate: number): void {
    this.discountRate = Math.max(0, Math.min(1, rate));
    console.log(`๐ŸŽŠ Discount rate set to: ${(this.discountRate * 100).toFixed(1)}%`);
  }
}

// ๐Ÿ’Ž Premium store with loyalty rewards
class PremiumStore extends OnlineStore {
  loyaltyPoints: number = 0;
  membershipLevel: 'bronze' | 'silver' | 'gold' = 'bronze';

  // ๐Ÿ”„ Override addItem to include loyalty points
  addItem(name: string, price: number): void {
    // ๐Ÿ“ž Do the basic item addition
    super.addItem(name, price);
    
    // โœจ Add premium features
    const pointsEarned = Math.floor(price / 10); // 1 point per $10
    this.loyaltyPoints += pointsEarned;
    console.log(`โญ Earned ${pointsEarned} loyalty points! Total: ${this.loyaltyPoints}`);
  }

  // ๐Ÿ”„ Override calculateTotal with membership benefits
  calculateTotal(itemsToBy: string[]): number {
    // ๐Ÿ† Apply membership discount first
    const membershipDiscounts = {
      bronze: 0.05,  // 5%
      silver: 0.10,  // 10%
      gold: 0.15     // 15%
    };

    const originalDiscount = this.discountRate;
    const membershipDiscount = membershipDiscounts[this.membershipLevel];
    
    // ๐ŸŽฏ Combine discounts (don't stack multiplicatively)
    const totalDiscount = Math.min(0.5, originalDiscount + membershipDiscount);
    this.discountRate = totalDiscount;

    console.log(`๐Ÿ‘‘ Membership level: ${this.membershipLevel.toUpperCase()}`);
    console.log(`๐ŸŽŠ Total discount rate: ${(totalDiscount * 100).toFixed(1)}%`);

    // ๐Ÿ“ž Calculate with enhanced discount
    const total = super.calculateTotal(itemsToBy);

    // โœจ Award loyalty points for purchase
    const pointsEarned = Math.floor(total / 5); // 1 point per $5 spent
    this.loyaltyPoints += pointsEarned;
    console.log(`โญ Purchase bonus: +${pointsEarned} loyalty points! Total: ${this.loyaltyPoints}`);

    // ๐Ÿ”„ Restore original discount for next calculation
    this.discountRate = originalDiscount;

    return total;
  }

  // ๐Ÿ’Ž Upgrade membership based on loyalty points
  checkMembershipUpgrade(): void {
    let newLevel: 'bronze' | 'silver' | 'gold' = 'bronze';
    
    if (this.loyaltyPoints >= 1000) {
      newLevel = 'gold';
    } else if (this.loyaltyPoints >= 500) {
      newLevel = 'silver';
    }

    if (newLevel !== this.membershipLevel) {
      this.membershipLevel = newLevel;
      console.log(`๐ŸŽ‰ Congratulations! Upgraded to ${newLevel.toUpperCase()} membership!`);
    }
  }

  // ๐ŸŽ Redeem loyalty points
  redeemPoints(points: number): void {
    if (points <= this.loyaltyPoints && points >= 100) {
      this.loyaltyPoints -= points;
      const value = points / 10; // $1 per 10 points
      console.log(`๐ŸŽ Redeemed ${points} points for $${value} credit!`);
    } else {
      console.log('โŒ Invalid redemption amount');
    }
  }
}

// ๐Ÿ›’ Let's go shopping!
const premiumStore = new PremiumStore();

// Add some items
premiumStore.addItem('TypeScript Hoodie', 45);
premiumStore.addItem('Programming Mug', 15);
premiumStore.addItem('Mechanical Keyboard', 120);

// Make a purchase
const cart = ['TypeScript Hoodie', 'Programming Mug'];
premiumStore.calculateTotal(cart);

// Check for upgrades
premiumStore.checkMembershipUpgrade();

๐Ÿ’ก Practical Examples

๐ŸŽฎ Example 1: RPG Character Combat System

Letโ€™s create a combat system where different character types have unique fighting styles:

// โš”๏ธ Base combat character
class CombatCharacter {
  name: string;
  health: number;
  maxHealth: number;
  damage: number;
  defense: number;

  constructor(name: string, health: number, damage: number, defense: number) {
    this.name = name;
    this.health = health;
    this.maxHealth = health;
    this.damage = damage;
    this.defense = defense;
  }

  // โš”๏ธ Basic attack method - to be overridden
  attack(target: CombatCharacter): void {
    const actualDamage = Math.max(1, this.damage - target.defense);
    target.takeDamage(actualDamage);
    console.log(`โš”๏ธ ${this.name} attacks ${target.name} for ${actualDamage} damage!`);
  }

  // ๐Ÿ›ก๏ธ Take damage
  takeDamage(damage: number): void {
    this.health = Math.max(0, this.health - damage);
    console.log(`๐Ÿ’ฅ ${this.name} takes ${damage} damage! Health: ${this.health}/${this.maxHealth}`);
    
    if (this.health === 0) {
      console.log(`๐Ÿ’€ ${this.name} has been defeated!`);
    }
  }

  // ๐Ÿ’– Heal
  heal(amount: number): void {
    const oldHealth = this.health;
    this.health = Math.min(this.maxHealth, this.health + amount);
    const actualHeal = this.health - oldHealth;
    console.log(`๐Ÿ’– ${this.name} heals for ${actualHeal} HP! Health: ${this.health}/${this.maxHealth}`);
  }

  // ๐Ÿ“Š Character status
  getStatus(): string {
    const healthPercent = Math.round((this.health / this.maxHealth) * 100);
    return `${this.name}: ${this.health}/${this.maxHealth} HP (${healthPercent}%)`;
  }

  // ๐Ÿ’€ Check if defeated
  isDefeated(): boolean {
    return this.health <= 0;
  }
}

// ๐Ÿง™โ€โ™‚๏ธ Wizard with magical attacks
class Wizard extends CombatCharacter {
  mana: number;
  maxMana: number;
  spellPower: number;

  constructor(name: string) {
    super(name, 80, 25, 5); // Lower HP, higher damage, low defense
    this.mana = 100;
    this.maxMana = 100;
    this.spellPower = 30;
  }

  // ๐Ÿ”„ Override attack with magical spells
  attack(target: CombatCharacter): void {
    if (this.mana < 20) {
      // ๐Ÿ“– Mana too low, use basic staff hit
      console.log(`๐Ÿช„ ${this.name} is out of mana! Using staff attack...`);
      super.attack(target); // Use basic attack
      return;
    }

    // โœจ Cast spell
    this.mana -= 20;
    const spellDamage = this.spellPower + Math.floor(Math.random() * 15);
    const actualDamage = Math.max(spellDamage, spellDamage - Math.floor(target.defense / 2)); // Magic partially ignores armor
    
    target.takeDamage(actualDamage);
    console.log(`โœจ ${this.name} casts a spell at ${target.name} for ${actualDamage} magical damage!`);
    console.log(`๐Ÿ”ฎ Mana: ${this.mana}/${this.maxMana}`);
  }

  // ๐Ÿง˜โ€โ™‚๏ธ Restore mana
  meditate(): void {
    const restored = Math.min(30, this.maxMana - this.mana);
    this.mana += restored;
    console.log(`๐Ÿง˜โ€โ™‚๏ธ ${this.name} meditates and restores ${restored} mana. Current: ${this.mana}/${this.maxMana}`);
  }
}

// ๐Ÿ›ก๏ธ Tank with defensive abilities
class Tank extends CombatCharacter {
  shieldPoints: number;
  maxShieldPoints: number;

  constructor(name: string) {
    super(name, 150, 15, 20); // High HP, low damage, high defense
    this.shieldPoints = 50;
    this.maxShieldPoints = 50;
  }

  // ๐Ÿ”„ Override takeDamage to use shield
  takeDamage(damage: number): void {
    if (this.shieldPoints > 0) {
      const shieldAbsorbed = Math.min(this.shieldPoints, damage);
      this.shieldPoints -= shieldAbsorbed;
      const remainingDamage = damage - shieldAbsorbed;
      
      console.log(`๐Ÿ›ก๏ธ Shield absorbs ${shieldAbsorbed} damage! Shield: ${this.shieldPoints}/${this.maxShieldPoints}`);
      
      if (remainingDamage > 0) {
        super.takeDamage(remainingDamage);
      }
    } else {
      super.takeDamage(damage);
    }
  }

  // ๐Ÿ”„ Override attack with shield bash
  attack(target: CombatCharacter): void {
    // ๐Ÿ›ก๏ธ Shield bash attack
    const bashDamage = this.damage + Math.floor(this.shieldPoints / 10);
    const actualDamage = Math.max(1, bashDamage - target.defense);
    
    target.takeDamage(actualDamage);
    console.log(`๐Ÿ›ก๏ธ ${this.name} shield bashes ${target.name} for ${actualDamage} damage!`);
    
    // ๐Ÿ’ช Chance to stun (simplified)
    if (Math.random() < 0.3) {
      console.log(`๐Ÿ˜ต ${target.name} is stunned by the powerful bash!`);
    }
  }

  // ๐Ÿ”„ Enhanced status with shield info
  getStatus(): string {
    const baseStatus = super.getStatus();
    return `${baseStatus} | ๐Ÿ›ก๏ธ Shield: ${this.shieldPoints}/${this.maxShieldPoints}`;
  }

  // ๐Ÿ›ก๏ธ Repair shield
  repairShield(): void {
    const repaired = Math.min(20, this.maxShieldPoints - this.shieldPoints);
    this.shieldPoints += repaired;
    console.log(`๐Ÿ”ง ${this.name} repairs shield for ${repaired} points. Shield: ${this.shieldPoints}/${this.maxShieldPoints}`);
  }
}

// ๐Ÿƒโ€โ™‚๏ธ Rogue with stealth attacks
class Rogue extends CombatCharacter {
  stealthMode: boolean = false;
  criticalChance: number = 0.3;

  constructor(name: string) {
    super(name, 100, 35, 10); // Medium HP, high damage, medium defense
  }

  // ๐Ÿ”„ Override attack with stealth and critical hits
  attack(target: CombatCharacter): void {
    let damage = this.damage;
    let isCritical = Math.random() < this.criticalChance;
    
    // ๐ŸŒŸ Stealth bonus
    if (this.stealthMode) {
      damage *= 1.5; // 50% bonus damage
      isCritical = true; // Guaranteed crit from stealth
      this.stealthMode = false; // Stealth broken after attack
      console.log(`๐Ÿ—ก๏ธ ${this.name} strikes from the shadows!`);
    }

    // โšก Critical hit
    if (isCritical) {
      damage *= 2;
      console.log(`๐Ÿ’ฅ CRITICAL HIT!`);
    }

    const actualDamage = Math.max(1, damage - target.defense);
    target.takeDamage(actualDamage);
    
    const attackType = this.stealthMode ? 'sneak attacks' : 'strikes';
    console.log(`๐Ÿ—ก๏ธ ${this.name} ${attackType} ${target.name} for ${actualDamage} damage!`);
  }

  // ๐Ÿ‘ค Enter stealth mode
  enterStealth(): void {
    if (!this.stealthMode) {
      this.stealthMode = true;
      console.log(`๐Ÿ‘ค ${this.name} vanishes into the shadows...`);
    } else {
      console.log(`${this.name} is already in stealth mode`);
    }
  }

  // ๐Ÿ”„ Enhanced status with stealth info
  getStatus(): string {
    const baseStatus = super.getStatus();
    const stealthStatus = this.stealthMode ? ' | ๐Ÿ‘ค STEALTH' : '';
    return `${baseStatus}${stealthStatus}`;
  }
}

// ๐ŸŽฎ Epic battle simulation!
const gandalf = new Wizard('Gandalf');
const thorin = new Tank('Thorin');
const legolas = new Rogue('Legolas');

console.log('โš”๏ธ BATTLE BEGINS! โš”๏ธ\n');

// Display initial status
console.log('๐Ÿ“Š Initial Status:');
console.log(gandalf.getStatus());
console.log(thorin.getStatus());
console.log(legolas.getStatus());

console.log('\n๐ŸฅŠ Round 1:');
legolas.enterStealth();
legolas.attack(thorin); // Sneak attack!
gandalf.attack(legolas); // Spell attack!
thorin.attack(gandalf); // Shield bash!

console.log('\n๐Ÿ“Š After Round 1:');
console.log(gandalf.getStatus());
console.log(thorin.getStatus());
console.log(legolas.getStatus());

console.log('\n๐Ÿ”ง Recovery Phase:');
gandalf.meditate(); // Restore mana
thorin.repairShield(); // Fix shield
legolas.heal(15); // Drink potion

console.log('\n๐ŸฅŠ Round 2:');
gandalf.attack(thorin); // Spell vs tank!
thorin.attack(legolas); // Shield bash vs rogue!
legolas.attack(gandalf); // Critical strike chance!

๐Ÿš— Example 2: Vehicle Control System

Letโ€™s create a vehicle system where different vehicles override driving behavior:

// ๐Ÿš— Base vehicle class
class Vehicle {
  make: string;
  model: string;
  year: number;
  speed: number = 0;
  maxSpeed: number;
  fuelLevel: number = 100;
  isRunning: boolean = false;

  constructor(make: string, model: string, year: number, maxSpeed: number) {
    this.make = make;
    this.model = model;
    this.year = year;
    this.maxSpeed = maxSpeed;
  }

  // ๐Ÿ”‘ Start engine
  start(): void {
    if (!this.isRunning) {
      this.isRunning = true;
      console.log(`๐Ÿ”‘ ${this.getVehicleName()} engine started!`);
    } else {
      console.log(`${this.getVehicleName()} is already running`);
    }
  }

  // โน๏ธ Stop engine
  stop(): void {
    if (this.isRunning) {
      this.isRunning = false;
      this.speed = 0;
      console.log(`โน๏ธ ${this.getVehicleName()} engine stopped`);
    }
  }

  // ๐Ÿš€ Accelerate - basic implementation
  accelerate(amount: number): void {
    if (!this.isRunning) {
      console.log('โŒ Cannot accelerate - engine not running!');
      return;
    }

    if (this.fuelLevel <= 0) {
      console.log('โ›ฝ Out of fuel!');
      return;
    }

    const newSpeed = Math.min(this.maxSpeed, this.speed + amount);
    const actualAcceleration = newSpeed - this.speed;
    this.speed = newSpeed;
    
    // โ›ฝ Consume fuel
    this.fuelLevel = Math.max(0, this.fuelLevel - (actualAcceleration * 0.5));
    
    console.log(`๐Ÿš€ ${this.getVehicleName()} accelerated to ${this.speed} mph`);
    if (this.speed === this.maxSpeed) {
      console.log(`๐Ÿ Maximum speed reached!`);
    }
  }

  // ๐Ÿ›‘ Brake - basic implementation
  brake(amount: number): void {
    const newSpeed = Math.max(0, this.speed - amount);
    const actualDeceleration = this.speed - newSpeed;
    this.speed = newSpeed;
    
    console.log(`๐Ÿ›‘ ${this.getVehicleName()} slowed down to ${this.speed} mph`);
    if (this.speed === 0) {
      console.log(`๐Ÿ›‘ Vehicle stopped`);
    }
  }

  // ๐Ÿ“Š Vehicle info
  getVehicleName(): string {
    return `${this.year} ${this.make} ${this.model}`;
  }

  // ๐Ÿ“Š Status display
  getStatus(): string {
    const status = this.isRunning ? 'Running' : 'Stopped';
    return `๐Ÿš— ${this.getVehicleName()} | ${status} | ${this.speed}/${this.maxSpeed} mph | โ›ฝ ${this.fuelLevel.toFixed(1)}%`;
  }
}

// ๐ŸŽ๏ธ Sports car with turbo boost
class SportsCar extends Vehicle {
  turboMode: boolean = false;
  turboFuelConsumption: number = 2.0;

  constructor(make: string, model: string, year: number) {
    super(make, model, year, 200); // High max speed
  }

  // ๐Ÿ”„ Override accelerate with turbo boost
  accelerate(amount: number): void {
    if (!this.isRunning) {
      console.log('โŒ Cannot accelerate - engine not running!');
      return;
    }

    if (this.fuelLevel <= 0) {
      console.log('โ›ฝ Out of fuel!');
      return;
    }

    // ๐Ÿš€ Turbo boost
    if (this.turboMode) {
      amount *= 1.5; // 50% more acceleration
      console.log('๐Ÿ”ฅ TURBO BOOST ACTIVATED!');
    }

    const newSpeed = Math.min(this.maxSpeed, this.speed + amount);
    const actualAcceleration = newSpeed - this.speed;
    this.speed = newSpeed;
    
    // โ›ฝ Higher fuel consumption in turbo
    const fuelConsumption = this.turboMode 
      ? actualAcceleration * this.turboFuelConsumption 
      : actualAcceleration * 0.5;
    
    this.fuelLevel = Math.max(0, this.fuelLevel - fuelConsumption);
    
    console.log(`๐ŸŽ๏ธ ${this.getVehicleName()} ${this.turboMode ? 'TURBO ' : ''}accelerated to ${this.speed} mph`);
    
    if (this.speed === this.maxSpeed) {
      console.log(`๐Ÿ Top speed achieved! What a machine! ๐Ÿ”ฅ`);
    }
  }

  // ๐Ÿ”„ Override brake with sport brakes
  brake(amount: number): void {
    // ๐ŸŽ๏ธ Sports cars have better brakes
    amount *= 1.3; // 30% better braking
    
    const newSpeed = Math.max(0, this.speed - amount);
    console.log(`๐ŸŽ๏ธ ${this.getVehicleName()} sport brakes engaged!`);
    
    super.brake(amount); // Use enhanced braking amount
  }

  // ๐Ÿš€ Toggle turbo mode
  toggleTurbo(): void {
    if (this.fuelLevel < 20) {
      console.log('โŒ Insufficient fuel for turbo mode!');
      return;
    }

    this.turboMode = !this.turboMode;
    console.log(`๐Ÿ”ฅ Turbo mode: ${this.turboMode ? 'ON' : 'OFF'}`);
  }

  // ๐Ÿ”„ Enhanced status with turbo info
  getStatus(): string {
    const baseStatus = super.getStatus();
    const turboStatus = this.turboMode ? ' | ๐Ÿ”ฅ TURBO' : '';
    return `${baseStatus}${turboStatus}`;
  }
}

// ๐Ÿš› Truck with cargo handling
class Truck extends Vehicle {
  cargoWeight: number = 0;
  maxCargoWeight: number = 5000; // 5000 lbs
  trailerAttached: boolean = false;

  constructor(make: string, model: string, year: number) {
    super(make, model, year, 85); // Lower max speed
  }

  // ๐Ÿ”„ Override accelerate considering cargo weight
  accelerate(amount: number): void {
    if (!this.isRunning) {
      console.log('โŒ Cannot accelerate - engine not running!');
      return;
    }

    // ๐Ÿ“ฆ Cargo affects acceleration
    const cargoFactor = 1 - (this.cargoWeight / this.maxCargoWeight) * 0.6; // Up to 60% reduction
    const trailerFactor = this.trailerAttached ? 0.7 : 1.0; // 30% reduction with trailer
    
    const adjustedAmount = amount * cargoFactor * trailerFactor;
    
    if (adjustedAmount < amount * 0.5) {
      console.log('๐ŸŒ Heavy load - slow acceleration');
    }

    // ๐Ÿ”„ Use parent accelerate with adjusted amount
    const oldSpeed = this.speed;
    super.accelerate(adjustedAmount);
    
    // ๐Ÿ“ฆ Additional fuel consumption for cargo
    const cargoFuelPenalty = (this.cargoWeight / 1000) * 0.2;
    this.fuelLevel = Math.max(0, this.fuelLevel - cargoFuelPenalty);
    
    console.log(`๐Ÿš› Hauling ${this.cargoWeight} lbs${this.trailerAttached ? ' with trailer' : ''}`);
  }

  // ๐Ÿ”„ Override brake with cargo considerations
  brake(amount: number): void {
    // ๐Ÿš› Heavier loads need more distance to stop
    const cargoFactor = 1 - (this.cargoWeight / this.maxCargoWeight) * 0.4; // Reduced braking efficiency
    const adjustedAmount = amount * cargoFactor;
    
    if (cargoFactor < 0.8) {
      console.log('โš ๏ธ Heavy load - extended braking distance!');
    }
    
    super.brake(adjustedAmount);
  }

  // ๐Ÿ“ฆ Load cargo
  loadCargo(weight: number): void {
    if (this.cargoWeight + weight <= this.maxCargoWeight) {
      this.cargoWeight += weight;
      console.log(`๐Ÿ“ฆ Loaded ${weight} lbs. Total cargo: ${this.cargoWeight}/${this.maxCargoWeight} lbs`);
    } else {
      console.log(`โŒ Cannot load ${weight} lbs - would exceed capacity!`);
    }
  }

  // ๐Ÿ“ค Unload cargo
  unloadCargo(weight: number): void {
    if (weight <= this.cargoWeight) {
      this.cargoWeight -= weight;
      console.log(`๐Ÿ“ค Unloaded ${weight} lbs. Remaining cargo: ${this.cargoWeight} lbs`);
    } else {
      console.log(`โŒ Cannot unload ${weight} lbs - only ${this.cargoWeight} lbs loaded`);
    }
  }

  // ๐Ÿš› Attach/detach trailer
  toggleTrailer(): void {
    this.trailerAttached = !this.trailerAttached;
    console.log(`๐Ÿš› Trailer ${this.trailerAttached ? 'attached' : 'detached'}`);
  }

  // ๐Ÿ”„ Enhanced status with cargo info
  getStatus(): string {
    const baseStatus = super.getStatus();
    const cargoStatus = ` | ๐Ÿ“ฆ ${this.cargoWeight}/${this.maxCargoWeight} lbs`;
    const trailerStatus = this.trailerAttached ? ' | ๐Ÿš› Trailer' : '';
    return `${baseStatus}${cargoStatus}${trailerStatus}`;
  }
}

// โšก Electric car with battery management
class ElectricCar extends Vehicle {
  batteryLevel: number = 100; // Replace fuel with battery
  regenerativeBraking: boolean = true;
  ecoMode: boolean = false;

  constructor(make: string, model: string, year: number) {
    super(make, model, year, 120);
    this.fuelLevel = 0; // Electric cars don't use fuel
  }

  // ๐Ÿ”„ Override accelerate for electric efficiency
  accelerate(amount: number): void {
    if (!this.isRunning) {
      console.log('โŒ Cannot accelerate - car not ready!');
      return;
    }

    if (this.batteryLevel <= 0) {
      console.log('๐Ÿ”‹ Battery depleted!');
      return;
    }

    // โšก Eco mode affects acceleration
    if (this.ecoMode) {
      amount *= 0.7; // Reduced acceleration in eco mode
      console.log('๐ŸŒฑ Eco mode - gentle acceleration');
    }

    const newSpeed = Math.min(this.maxSpeed, this.speed + amount);
    const actualAcceleration = newSpeed - this.speed;
    this.speed = newSpeed;
    
    // ๐Ÿ”‹ Consume battery power
    const powerConsumption = this.ecoMode 
      ? actualAcceleration * 0.3 
      : actualAcceleration * 0.5;
    
    this.batteryLevel = Math.max(0, this.batteryLevel - powerConsumption);
    
    console.log(`โšก ${this.getVehicleName()} silently accelerated to ${this.speed} mph`);
  }

  // ๐Ÿ”„ Override brake with regenerative braking
  brake(amount: number): void {
    const initialSpeed = this.speed;
    
    // ๐Ÿ”„ Standard braking
    super.brake(amount);
    
    // ๐Ÿ”‹ Regenerative braking recovers energy
    if (this.regenerativeBraking && initialSpeed > this.speed) {
      const energyRecovered = (initialSpeed - this.speed) * 0.3;
      this.batteryLevel = Math.min(100, this.batteryLevel + energyRecovered);
      console.log(`๐Ÿ”‹ Regenerative braking recovered ${energyRecovered.toFixed(1)}% battery`);
    }
  }

  // ๐Ÿ”Œ Charge battery
  charge(minutes: number): void {
    const chargeRate = 2; // 2% per minute
    const chargeAmount = minutes * chargeRate;
    const oldLevel = this.batteryLevel;
    this.batteryLevel = Math.min(100, this.batteryLevel + chargeAmount);
    const actualCharge = this.batteryLevel - oldLevel;
    
    console.log(`๐Ÿ”Œ Charged for ${minutes} minutes. Battery: ${this.batteryLevel.toFixed(1)}% (+${actualCharge.toFixed(1)}%)`);
  }

  // ๐ŸŒฑ Toggle eco mode
  toggleEcoMode(): void {
    this.ecoMode = !this.ecoMode;
    console.log(`๐ŸŒฑ Eco mode: ${this.ecoMode ? 'ON' : 'OFF'}`);
  }

  // ๐Ÿ”„ Override status for electric car
  getStatus(): string {
    const status = this.isRunning ? 'Ready' : 'Parked';
    const ecoStatus = this.ecoMode ? ' | ๐ŸŒฑ ECO' : '';
    const regenStatus = this.regenerativeBraking ? ' | ๐Ÿ”‹ REGEN' : '';
    return `โšก ${this.getVehicleName()} | ${status} | ${this.speed}/${this.maxSpeed} mph | ๐Ÿ”‹ ${this.batteryLevel.toFixed(1)}%${ecoStatus}${regenStatus}`;
  }
}

// ๐Ÿš— Let's test our vehicles!
const ferrari = new SportsCar('Ferrari', '488 GTB', 2023);
const ford = new Truck('Ford', 'F-150', 2023);
const tesla = new ElectricCar('Tesla', 'Model S', 2023);

console.log('๐Ÿš— VEHICLE TEST DRIVE ๐Ÿš—\n');

// Ferrari test
console.log('=== ๐ŸŽ๏ธ Sports Car Test ===');
ferrari.start();
ferrari.accelerate(50);
ferrari.toggleTurbo();
ferrari.accelerate(30); // Turbo acceleration!
console.log(ferrari.getStatus());
ferrari.brake(40);
console.log(ferrari.getStatus());

console.log('\n=== ๐Ÿš› Truck Test ===');
ford.start();
ford.loadCargo(3000);
ford.toggleTrailer();
ford.accelerate(30); // Slow due to cargo and trailer
console.log(ford.getStatus());
ford.brake(20); // Extended braking
ford.unloadCargo(1000);
console.log(ford.getStatus());

console.log('\n=== โšก Electric Car Test ===');
tesla.start();
tesla.toggleEcoMode();
tesla.accelerate(40); // Eco mode acceleration
console.log(tesla.getStatus());
tesla.brake(25); // Regenerative braking
console.log(tesla.getStatus());
tesla.charge(10); // Quick charge
console.log(tesla.getStatus());

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Abstract Method Overriding

Sometimes you want to force child classes to implement specific methods:

// ๐ŸŽจ Abstract media player
abstract class MediaPlayer {
  title: string;
  isPlaying: boolean = false;

  constructor(title: string) {
    this.title = title;
  }

  // ๐Ÿ”„ Concrete method - same for all players
  togglePlay(): void {
    if (this.isPlaying) {
      this.pause();
    } else {
      this.play();
    }
  }

  // ๐ŸŽฏ Abstract methods - MUST be overridden
  abstract play(): void;
  abstract pause(): void;
  abstract getDuration(): number;
  abstract getFormat(): string;
}

// ๐ŸŽต Audio player implementation
class AudioPlayer extends MediaPlayer {
  private audioData: string;
  private currentTime: number = 0;
  private duration: number;

  constructor(title: string, audioData: string, duration: number) {
    super(title);
    this.audioData = audioData;
    this.duration = duration;
  }

  // โœ… Must implement - play method
  play(): void {
    this.isPlaying = true;
    console.log(`๐ŸŽต Playing audio: "${this.title}" from ${this.currentTime}s`);
  }

  // โœ… Must implement - pause method
  pause(): void {
    this.isPlaying = false;
    console.log(`โธ๏ธ Paused audio: "${this.title}" at ${this.currentTime}s`);
  }

  // โœ… Must implement - getDuration method
  getDuration(): number {
    return this.duration;
  }

  // โœ… Must implement - getFormat method
  getFormat(): string {
    return 'MP3 Audio ๐ŸŽต';
  }
}

// ๐Ÿ“น Video player implementation  
class VideoPlayer extends MediaPlayer {
  private videoData: string;
  private currentFrame: number = 0;
  private totalFrames: number;
  private fps: number = 30;

  constructor(title: string, videoData: string, totalFrames: number) {
    super(title);
    this.videoData = videoData;
    this.totalFrames = totalFrames;
  }

  // โœ… Must implement - play method
  play(): void {
    this.isPlaying = true;
    console.log(`๐Ÿ“น Playing video: "${this.title}" from frame ${this.currentFrame}`);
  }

  // โœ… Must implement - pause method
  pause(): void {
    this.isPlaying = false;
    console.log(`โธ๏ธ Paused video: "${this.title}" at frame ${this.currentFrame}`);
  }

  // โœ… Must implement - getDuration method
  getDuration(): number {
    return Math.round(this.totalFrames / this.fps);
  }

  // โœ… Must implement - getFormat method
  getFormat(): string {
    return `MP4 Video ๐Ÿ“น (${this.fps}fps)`;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Method Overriding with Generics

Combine overriding with generic types for maximum flexibility:

// ๐Ÿ—„๏ธ Generic data repository
abstract class Repository<T> {
  protected items: T[] = [];

  // ๐Ÿ“Š Common methods for all repositories
  getAll(): T[] {
    return [...this.items];
  }

  count(): number {
    return this.items.length;
  }

  // ๐ŸŽฏ Abstract methods to be overridden
  abstract add(item: T): void;
  abstract findById(id: string | number): T | undefined;
  abstract update(id: string | number, updates: Partial<T>): boolean;
  abstract delete(id: string | number): boolean;
}

// ๐Ÿ‘ค User repository with specific validation
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

class UserRepository extends Repository<User> {
  private nextId: number = 1;

  // ๐Ÿ”„ Override add with user validation
  add(user: Omit<User, 'id'>): void {
    // ๐Ÿ›ก๏ธ Validate email format
    if (!user.email.includes('@')) {
      console.log('โŒ Invalid email format');
      return;
    }

    // ๐Ÿ›ก๏ธ Check for duplicate email
    const existingUser = this.items.find(u => u.email === user.email);
    if (existingUser) {
      console.log('โŒ Email already exists');
      return;
    }

    const newUser: User = { ...user, id: this.nextId++ };
    this.items.push(newUser);
    console.log(`โœ… Added user: ${newUser.name} (ID: ${newUser.id})`);
  }

  // ๐Ÿ”„ Override findById
  findById(id: number): User | undefined {
    return this.items.find(user => user.id === id);
  }

  // ๐Ÿ”„ Override update with validation
  update(id: number, updates: Partial<User>): boolean {
    const userIndex = this.items.findIndex(user => user.id === id);
    if (userIndex === -1) {
      console.log('โŒ User not found');
      return false;
    }

    // ๐Ÿ›ก๏ธ Validate email if being updated
    if (updates.email && !updates.email.includes('@')) {
      console.log('โŒ Invalid email format');
      return false;
    }

    this.items[userIndex] = { ...this.items[userIndex], ...updates };
    console.log(`โœ… Updated user ID ${id}`);
    return true;
  }

  // ๐Ÿ”„ Override delete
  delete(id: number): boolean {
    const userIndex = this.items.findIndex(user => user.id === id);
    if (userIndex === -1) {
      console.log('โŒ User not found');
      return false;
    }

    const deletedUser = this.items.splice(userIndex, 1)[0];
    console.log(`๐Ÿ—‘๏ธ Deleted user: ${deletedUser.name}`);
    return true;
  }

  // ๐Ÿ” User-specific methods
  findByEmail(email: string): User | undefined {
    return this.items.find(user => user.email === email);
  }
}

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Changing Method Signatures

class Parent {
  greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

// โŒ Wrong - changing the signature!
class BadChild extends Parent {
  greet(name: string, age: number): string { // ๐Ÿ’ฅ Different signature!
    return `Hello, ${name}, age ${age}!`;
  }
}

// โœ… Correct - keep the same signature
class GoodChild extends Parent {
  greet(name: string): string { // โœ… Same signature
    return `Hey there, ${name}! Welcome!`;
  }
}

๐Ÿคฏ Pitfall 2: Forgetting Return Types

class Parent {
  calculate(): number {
    return 42;
  }
}

// โŒ Wrong - returning different type!
class BadChild extends Parent {
  calculate(): string { // ๐Ÿ’ฅ TypeScript error!
    return "42";
  }
}

// โœ… Correct - same return type
class GoodChild extends Parent {
  calculate(): number { // โœ… Same return type
    return 84; // Different value, same type
  }
}

๐Ÿ”„ Pitfall 3: Breaking the Liskov Substitution Principle

class Bird {
  fly(): void {
    console.log('Flying high! ๐Ÿฆ…');
  }
}

// โŒ Problematic - penguins can't fly!
class Penguin extends Bird {
  fly(): void {
    throw new Error('Penguins cannot fly!'); // ๐Ÿ’ฅ Breaks expectations
  }
}

// โœ… Better design - composition over inheritance
interface Flyable {
  fly(): void;
}

interface Swimmable {
  swim(): void;
}

class Eagle implements Flyable {
  fly(): void {
    console.log('Soaring through the sky! ๐Ÿฆ…');
  }
}

class Penguin implements Swimmable {
  swim(): void {
    console.log('Swimming gracefully! ๐Ÿง');
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Keep Same Signatures: Donโ€™t change parameter types or return types
  2. ๐Ÿ“ Follow LSP: Overridden methods should be substitutable
  3. ๐Ÿ”„ Use super() Wisely: Enhance rather than completely replace when possible
  4. ๐Ÿ›ก๏ธ Validate Consistently: Maintain or strengthen preconditions
  5. ๐Ÿ“– Document Changes: Make overriding behavior clear to users

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Payment Processing System

Create a payment system where different payment methods override processing behavior:

๐Ÿ“‹ Requirements:

  • โœ… Base PaymentProcessor class with amount, currency, fees
  • ๐Ÿ’ณ CreditCardProcessor with validation and fraud detection
  • ๐Ÿฆ BankTransferProcessor with clearing times and limits
  • ๐Ÿ’ฐ CryptoProcessor with blockchain confirmation and volatility
  • ๐Ÿ”„ Each processor should override processing methods uniquely

๐Ÿš€ Bonus Points:

  • Add refund processing with different rules
  • Implement transaction logging
  • Create payment analytics

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ’ณ Base payment processor
abstract class PaymentProcessor {
  amount: number;
  currency: string;
  baseFeeRate: number = 0.025; // 2.5%
  status: 'pending' | 'processing' | 'completed' | 'failed' = 'pending';
  transactionId: string;

  constructor(amount: number, currency: string = 'USD') {
    this.amount = amount;
    this.currency = currency;
    this.transactionId = `txn_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
  }

  // ๐Ÿ’ฐ Calculate fees - can be overridden
  calculateFees(): number {
    return this.amount * this.baseFeeRate;
  }

  // ๐Ÿ” Validate transaction - basic validation
  validateTransaction(): boolean {
    if (this.amount <= 0) {
      console.log('โŒ Invalid amount');
      return false;
    }
    if (!this.currency) {
      console.log('โŒ Currency required');
      return false;
    }
    return true;
  }

  // ๐Ÿ“Š Get transaction summary
  getTransactionSummary(): string {
    const fees = this.calculateFees();
    const total = this.amount + fees;
    return `๐Ÿ’ณ ${this.transactionId} | ${this.currency} ${this.amount.toFixed(2)} + ${fees.toFixed(2)} fees = ${total.toFixed(2)} total | Status: ${this.status.toUpperCase()}`;
  }

  // ๐ŸŽฏ Abstract method - must be overridden
  abstract processPayment(): Promise<boolean>;
}

// ๐Ÿ’ณ Credit card processor
class CreditCardProcessor extends PaymentProcessor {
  cardNumber: string;
  expiryDate: string;
  cvv: string;
  fraudScore: number = 0;

  constructor(amount: number, cardNumber: string, expiryDate: string, cvv: string, currency: string = 'USD') {
    super(amount, currency);
    this.cardNumber = cardNumber;
    this.expiryDate = expiryDate;
    this.cvv = cvv;
    this.baseFeeRate = 0.029; // 2.9% for credit cards
  }

  // ๐Ÿ”„ Override fee calculation with credit card fees
  calculateFees(): number {
    let baseFees = super.calculateFees();
    
    // ๐Ÿ’ณ Premium card types have higher fees
    if (this.cardNumber.startsWith('4')) {
      baseFees *= 1.1; // Visa surcharge
    } else if (this.cardNumber.startsWith('5')) {
      baseFees *= 1.15; // Mastercard surcharge
    }

    // ๐ŸŒ International transaction fees
    if (this.currency !== 'USD') {
      baseFees += this.amount * 0.01; // 1% international fee
    }

    return baseFees;
  }

  // ๐Ÿ”„ Override validation with card-specific checks
  validateTransaction(): boolean {
    // ๐Ÿ“ž Basic validation first
    if (!super.validateTransaction()) {
      return false;
    }

    // ๐Ÿ’ณ Credit card validation
    if (this.cardNumber.length < 13 || this.cardNumber.length > 19) {
      console.log('โŒ Invalid card number length');
      return false;
    }

    if (this.cvv.length < 3 || this.cvv.length > 4) {
      console.log('โŒ Invalid CVV');
      return false;
    }

    // ๐Ÿ“… Expiry date check (simplified)
    const today = new Date();
    const [month, year] = this.expiryDate.split('/');
    const expiryDate = new Date(2000 + parseInt(year), parseInt(month) - 1);
    
    if (expiryDate < today) {
      console.log('โŒ Card expired');
      return false;
    }

    return true;
  }

  // ๐Ÿ›ก๏ธ Fraud detection
  private performFraudCheck(): boolean {
    // ๐Ÿ” Simplified fraud scoring
    this.fraudScore = 0;

    // Large amount risk
    if (this.amount > 1000) this.fraudScore += 20;
    if (this.amount > 5000) this.fraudScore += 30;

    // International transaction risk
    if (this.currency !== 'USD') this.fraudScore += 15;

    // Velocity check (simplified - in reality would check recent transactions)
    if (Math.random() < 0.1) this.fraudScore += 25; // Random velocity hit

    console.log(`๐Ÿ” Fraud score: ${this.fraudScore}/100`);

    if (this.fraudScore > 50) {
      console.log('๐Ÿšจ High fraud risk - transaction blocked');
      return false;
    }

    return true;
  }

  // ๐Ÿ”„ Override payment processing
  async processPayment(): Promise<boolean> {
    console.log(`๐Ÿ’ณ Processing credit card payment: ${this.getTransactionSummary()}`);
    
    this.status = 'processing';

    // ๐Ÿ›ก๏ธ Validation
    if (!this.validateTransaction()) {
      this.status = 'failed';
      return false;
    }

    // ๐Ÿ” Fraud check
    if (!this.performFraudCheck()) {
      this.status = 'failed';
      return false;
    }

    // ๐Ÿ’ณ Simulate card processing
    console.log('๐Ÿ’ณ Contacting card issuer...');
    await new Promise(resolve => setTimeout(resolve, 1000));

    // ๐ŸŽฒ Simulate approval (90% success rate)
    if (Math.random() < 0.9) {
      this.status = 'completed';
      console.log('โœ… Credit card payment approved!');
      console.log(`๐Ÿ’ณ Charged ${this.cardNumber.slice(-4)} for ${this.currency} ${(this.amount + this.calculateFees()).toFixed(2)}`);
      return true;
    } else {
      this.status = 'failed';
      console.log('โŒ Credit card payment declined');
      return false;
    }
  }

  // ๐Ÿ’ณ Card-specific method
  getMaskedCardNumber(): string {
    return `****-****-****-${this.cardNumber.slice(-4)}`;
  }
}

// ๐Ÿฆ Bank transfer processor
class BankTransferProcessor extends PaymentProcessor {
  bankAccountNumber: string;
  routingNumber: string;
  accountType: 'checking' | 'savings';
  dailyLimit: number = 10000;
  processingTime: number = 1; // 1 day

  constructor(amount: number, bankAccountNumber: string, routingNumber: string, accountType: 'checking' | 'savings', currency: string = 'USD') {
    super(amount, currency);
    this.bankAccountNumber = bankAccountNumber;
    this.routingNumber = routingNumber;
    this.accountType = accountType;
    this.baseFeeRate = 0.01; // 1% for bank transfers
  }

  // ๐Ÿ”„ Override fee calculation - lower fees but flat rate for large amounts
  calculateFees(): number {
    const percentageFee = super.calculateFees();
    const flatFee = 5.00; // $5 flat fee
    
    // ๐Ÿฆ Use flat fee for large amounts if it's cheaper
    if (this.amount > 2000) {
      return Math.min(percentageFee, flatFee);
    }
    
    return Math.max(percentageFee, flatFee); // Minimum $5 fee
  }

  // ๐Ÿ”„ Override validation with bank-specific checks
  validateTransaction(): boolean {
    if (!super.validateTransaction()) {
      return false;
    }

    // ๐Ÿฆ Bank account validation
    if (this.bankAccountNumber.length < 8 || this.bankAccountNumber.length > 12) {
      console.log('โŒ Invalid account number length');
      return false;
    }

    if (this.routingNumber.length !== 9) {
      console.log('โŒ Invalid routing number');
      return false;
    }

    // ๐Ÿ’ฐ Daily limit check
    if (this.amount > this.dailyLimit) {
      console.log(`โŒ Amount exceeds daily limit of $${this.dailyLimit.toLocaleString()}`);
      return false;
    }

    return true;
  }

  // ๐Ÿ”„ Override payment processing with bank clearing
  async processPayment(): Promise<boolean> {
    console.log(`๐Ÿฆ Processing bank transfer: ${this.getTransactionSummary()}`);
    
    this.status = 'processing';

    if (!this.validateTransaction()) {
      this.status = 'failed';
      return false;
    }

    // ๐Ÿฆ Simulate bank verification
    console.log('๐Ÿฆ Verifying bank account...');
    await new Promise(resolve => setTimeout(resolve, 800));

    console.log(`โณ Bank transfer initiated - will clear in ${this.processingTime} business day(s)`);
    
    // ๐ŸŽฒ Very high success rate for bank transfers
    if (Math.random() < 0.95) {
      this.status = 'completed';
      console.log('โœ… Bank transfer authorized!');
      console.log(`๐Ÿฆ Debiting account ****${this.bankAccountNumber.slice(-4)} for ${this.currency} ${(this.amount + this.calculateFees()).toFixed(2)}`);
      return true;
    } else {
      this.status = 'failed';
      console.log('โŒ Bank transfer failed - insufficient funds');
      return false;
    }
  }

  // ๐Ÿฆ Bank-specific method
  getAccountSummary(): string {
    return `${this.accountType.toUpperCase()} ****${this.bankAccountNumber.slice(-4)} (Routing: ${this.routingNumber})`;
  }
}

// โ‚ฟ Cryptocurrency processor
class CryptoProcessor extends PaymentProcessor {
  walletAddress: string;
  cryptoCurrency: 'BTC' | 'ETH' | 'USDC';
  networkFees: number = 0;
  confirmationsRequired: number = 3;
  volatilityRisk: number = 0.05; // 5% volatility buffer

  constructor(amount: number, walletAddress: string, cryptoCurrency: 'BTC' | 'ETH' | 'USDC', currency: string = 'USD') {
    super(amount, currency);
    this.walletAddress = walletAddress;
    this.cryptoCurrency = cryptoCurrency;
    this.baseFeeRate = 0.015; // 1.5% for crypto
    
    // ๐ŸŒ Set network fees based on crypto type
    this.networkFees = cryptoCurrency === 'BTC' ? 15 : cryptoCurrency === 'ETH' ? 25 : 2;
  }

  // ๐Ÿ”„ Override fee calculation with network fees and volatility
  calculateFees(): number {
    const baseFees = super.calculateFees();
    const volatilityBuffer = this.amount * this.volatilityRisk;
    
    console.log(`โ‚ฟ Base fees: $${baseFees.toFixed(2)}, Network fees: $${this.networkFees}, Volatility buffer: $${volatilityBuffer.toFixed(2)}`);
    
    return baseFees + this.networkFees + volatilityBuffer;
  }

  // ๐Ÿ”„ Override validation with crypto-specific checks
  validateTransaction(): boolean {
    if (!super.validateTransaction()) {
      return false;
    }

    // โ‚ฟ Wallet address validation (simplified)
    if (this.walletAddress.length < 26 || this.walletAddress.length > 62) {
      console.log('โŒ Invalid wallet address format');
      return false;
    }

    // ๐Ÿ’ฐ Minimum amount for crypto (due to network fees)
    if (this.amount < 10) {
      console.log('โŒ Minimum transaction amount is $10 for crypto payments');
      return false;
    }

    return true;
  }

  // โ‚ฟ Get current crypto rate (simulated)
  private getCryptoRate(): number {
    const basRates = { BTC: 45000, ETH: 3000, USDC: 1 };
    const baseRate = baseRates[this.cryptoCurrency];
    
    // ๐Ÿ“ˆ Add some volatility
    const volatility = (Math.random() - 0.5) * 0.1; // ยฑ5% volatility
    return baseRate * (1 + volatility);
  }

  // ๐Ÿ”„ Override payment processing with blockchain
  async processPayment(): Promise<boolean> {
    console.log(`โ‚ฟ Processing crypto payment: ${this.getTransactionSummary()}`);
    
    this.status = 'processing';

    if (!this.validateTransaction()) {
      this.status = 'failed';
      return false;
    }

    // โ‚ฟ Get current rates
    const cryptoRate = this.getCryptoRate();
    const cryptoAmount = (this.amount + this.calculateFees()) / cryptoRate;
    
    console.log(`โ‚ฟ Converting ${this.currency} ${(this.amount + this.calculateFees()).toFixed(2)} to ${cryptoAmount.toFixed(8)} ${this.cryptoCurrency}`);
    console.log(`โ‚ฟ Current rate: 1 ${this.cryptoCurrency} = $${cryptoRate.toFixed(2)}`);

    // ๐ŸŒ Simulate blockchain transaction
    console.log('๐ŸŒ Broadcasting transaction to blockchain...');
    await new Promise(resolve => setTimeout(resolve, 1200));

    console.log(`โณ Waiting for ${this.confirmationsRequired} confirmations...`);
    
    // ๐ŸŽฒ High success rate but some network issues possible
    if (Math.random() < 0.88) {
      this.status = 'completed';
      console.log('โœ… Crypto payment confirmed on blockchain!');
      console.log(`โ‚ฟ Sent ${cryptoAmount.toFixed(8)} ${this.cryptoCurrency} to ${this.walletAddress.slice(0, 8)}...`);
      return true;
    } else {
      this.status = 'failed';
      console.log('โŒ Crypto payment failed - network congestion');
      return false;
    }
  }

  // โ‚ฟ Crypto-specific method
  getWalletSummary(): string {
    return `${this.cryptoCurrency} wallet: ${this.walletAddress.slice(0, 8)}...${this.walletAddress.slice(-8)}`;
  }
}

// ๐Ÿ’ณ Payment processor factory
class PaymentProcessorFactory {
  static createProcessor(type: 'credit_card' | 'bank_transfer' | 'crypto', amount: number, ...args: any[]): PaymentProcessor {
    switch (type) {
      case 'credit_card':
        return new CreditCardProcessor(amount, ...args);
      case 'bank_transfer':
        return new BankTransferProcessor(amount, ...args);
      case 'crypto':
        return new CryptoProcessor(amount, ...args);
      default:
        throw new Error('Unknown payment processor type');
    }
  }
}

// ๐ŸŽฎ Let's process some payments!
async function runPaymentDemo() {
  console.log('๐Ÿ’ณ PAYMENT PROCESSING DEMO ๐Ÿ’ณ\n');

  // Create different payment processors
  const creditCard = new CreditCardProcessor(299.99, '4111111111111111', '12/25', '123');
  const bankTransfer = new BankTransferProcessor(1500.00, '123456789012', '021000021', 'checking');
  const crypto = new CryptoProcessor(750.00, 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh', 'BTC');

  console.log('=== Payment Summaries ===');
  console.log(creditCard.getTransactionSummary());
  console.log(bankTransfer.getTransactionSummary());
  console.log(crypto.getTransactionSummary());

  console.log('\n=== Processing Credit Card Payment ===');
  await creditCard.processPayment();

  console.log('\n=== Processing Bank Transfer ===');
  await bankTransfer.processPayment();

  console.log('\n=== Processing Crypto Payment ===');
  await crypto.processPayment();

  console.log('\n=== Final Status ===');
  console.log(`๐Ÿ’ณ Credit Card: ${creditCard.status.toUpperCase()}`);
  console.log(`๐Ÿฆ Bank Transfer: ${bankTransfer.status.toUpperCase()}`);
  console.log(`โ‚ฟ Crypto: ${crypto.status.toUpperCase()}`);
}

// Run the demo
runPaymentDemo();

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered method overriding! Hereโ€™s what you can now do:

  • โœ… Override methods effectively while keeping signatures ๐Ÿ”„
  • โœ… Combine overriding with super calls for enhancement ๐Ÿš€
  • โœ… Create polymorphic behavior with consistent interfaces ๐ŸŽญ
  • โœ… Validate and handle edge cases in overridden methods ๐Ÿ›ก๏ธ
  • โœ… Avoid common overriding pitfalls like a pro ๐ŸŽฏ

Remember: Method overriding is about customization, not replacement - enhance what works! ๐ŸŒŸ

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered method overriding in TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the payment processing exercise above
  2. ๐Ÿ—๏ธ Build your own system with different overriding patterns
  3. ๐Ÿ“š Move on to our next tutorial: Abstract Classes and Methods: Creating Base Classes
  4. ๐ŸŒŸ Experiment with combining overriding, generics, and interfaces!

Remember: Every great object-oriented system uses method overriding effectively. Keep customizing, keep learning, and override your way to better code! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿš€โœจ