+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 34 of 72

๐Ÿ— ๏ธ Inheritance in TypeScript: Extending Classes

Master class inheritance in TypeScript with practical examples, best practices, and real-world applications ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Classes in TypeScript basics ๐Ÿ“
  • Constructors and methods ๐Ÿ—๏ธ
  • Access modifiers knowledge ๐Ÿ”’

What you'll learn

  • Understand inheritance fundamentals ๐ŸŽฏ
  • Create class hierarchies effectively ๐Ÿ—๏ธ
  • Use extends keyword properly โœจ
  • Apply inheritance in real projects ๐Ÿš€

๐ŸŽฏ Introduction

Welcome to the exciting world of TypeScript inheritance! ๐ŸŽ‰ In this guide, weโ€™ll explore one of the most powerful features of object-oriented programming: the ability to create new classes based on existing ones.

Youโ€™ll discover how inheritance can help you write more reusable, maintainable code by building class hierarchies. Whether youโ€™re building game characters ๐ŸŽฎ, UI components ๐Ÿ–ผ๏ธ, or business entities ๐Ÿ’ผ, understanding inheritance is essential for writing scalable TypeScript applications.

By the end of this tutorial, youโ€™ll feel confident creating class hierarchies that follow best practices! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Inheritance

๐Ÿค” What is Inheritance?

Inheritance is like a family tree ๐ŸŒณ. Think of it as a way for classes to share characteristics and behaviors, just like how children inherit traits from their parents.

In TypeScript terms, inheritance allows you to create a new class (child/derived class) that inherits properties and methods from an existing class (parent/base class). This means you can:

  • โœจ Reuse existing code without duplication
  • ๐Ÿš€ Build upon proven functionality
  • ๐Ÿ›ก๏ธ Maintain consistent interfaces
  • ๐Ÿ”ง Extend behavior without modifying original code

๐Ÿ’ก Why Use Inheritance?

Hereโ€™s why developers love inheritance:

  1. Code Reusability โ™ป๏ธ: Write once, inherit everywhere
  2. Maintainability ๐Ÿ”ง: Changes in base class affect all children
  3. Logical Hierarchy ๐Ÿ—๏ธ: Models real-world relationships
  4. Polymorphism ๐ŸŽญ: Treat different objects uniformly

Real-world example: Imagine building a zoo management system ๐Ÿฆ. You can create an Animal base class and inherit specific animals like Lion, Elephant, and Penguin.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

// ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ Base class - the parent
class Person {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  // ๐Ÿ‘‹ Basic greeting method
  greet(): string {
    return `Hello, I'm ${this.name}! ๐Ÿ˜Š`;
  }

  // ๐ŸŽ‚ Birthday celebration
  haveBirthday(): void {
    this.age++;
    console.log(`๐ŸŽ‰ ${this.name} is now ${this.age} years old!`);
  }
}

// ๐Ÿ‘จโ€๐ŸŽ“ Child class - extends the parent
class Student extends Person {
  studentId: string;
  major: string;

  constructor(name: string, age: number, studentId: string, major: string) {
    super(name, age); // ๐Ÿ“ž Call parent constructor
    this.studentId = studentId;
    this.major = major;
  }

  // ๐Ÿ“š Student-specific method
  study(): string {
    return `${this.name} is studying ${this.major}! ๐Ÿ“–`;
  }

  // ๐Ÿ“ Get student info
  getStudentInfo(): string {
    return `Student ID: ${this.studentId}, Major: ${this.major} ๐ŸŽ“`;
  }
}

๐Ÿ’ก Explanation: The Student class inherits all properties and methods from Person and adds its own specific features!

๐ŸŽฏ Using Inherited Classes

Hereโ€™s how to use our inherited classes:

// ๐Ÿ—๏ธ Create instances
const person = new Person("Alice", 30);
const student = new Student("Bob", 20, "S12345", "Computer Science");

// ๐Ÿ‘ค Regular person
console.log(person.greet()); // "Hello, I'm Alice! ๐Ÿ˜Š"
person.haveBirthday(); // "๐ŸŽ‰ Alice is now 31 years old!"

// ๐Ÿ‘จโ€๐ŸŽ“ Student (has all Person methods + Student methods)
console.log(student.greet()); // "Hello, I'm Bob! ๐Ÿ˜Š" (inherited!)
console.log(student.study()); // "Bob is studying Computer Science! ๐Ÿ“–"
console.log(student.getStudentInfo()); // "Student ID: S12345, Major: Computer Science ๐ŸŽ“"

// ๐ŸŽ‚ Student can also have birthdays!
student.haveBirthday(); // "๐ŸŽ‰ Bob is now 21 years old!"

๐Ÿ’ก Practical Examples

๐ŸŽฎ Example 1: Game Character System

Letโ€™s build a fun game character hierarchy:

// ๐Ÿฐ Base character class
class GameCharacter {
  name: string;
  health: number;
  level: number;
  experience: number;

  constructor(name: string) {
    this.name = name;
    this.health = 100;
    this.level = 1;
    this.experience = 0;
  }

  // โš”๏ธ Basic attack method
  attack(): number {
    const damage = Math.floor(Math.random() * 20) + 5;
    console.log(`${this.name} attacks for ${damage} damage! โš”๏ธ`);
    return 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} โค๏ธ`);
    
    if (this.health === 0) {
      console.log(`๐Ÿ’€ ${this.name} has been defeated!`);
    }
  }

  // โญ Gain experience
  gainExperience(xp: number): void {
    this.experience += xp;
    if (this.experience >= this.level * 100) {
      this.levelUp();
    }
  }

  // ๐Ÿ“ˆ Level up
  private levelUp(): void {
    this.level++;
    this.health = 100; // ๐Ÿ”„ Full heal on level up
    console.log(`๐ŸŽ‰ ${this.name} leveled up to ${this.level}!`);
  }
}

// ๐Ÿง™โ€โ™‚๏ธ Wizard class - magic focused
class Wizard extends GameCharacter {
  mana: number;
  spellPower: number;

  constructor(name: string) {
    super(name); // ๐Ÿ“ž Call parent constructor
    this.mana = 50;
    this.spellPower = 15;
  }

  // โœจ Cast magic spell
  castSpell(): number {
    if (this.mana < 10) {
      console.log(`${this.name} doesn't have enough mana! ๐Ÿ˜”`);
      return 0;
    }

    this.mana -= 10;
    const damage = this.spellPower + Math.floor(Math.random() * 10);
    console.log(`${this.name} casts a spell for ${damage} magical damage! โœจ`);
    return damage;
  }

  // ๐Ÿง˜โ€โ™‚๏ธ Meditate to restore mana
  meditate(): void {
    this.mana = Math.min(100, this.mana + 20);
    console.log(`${this.name} meditates and restores mana. Current: ${this.mana} ๐Ÿง˜โ€โ™‚๏ธ`);
  }
}

// โš”๏ธ Warrior class - combat focused
class Warrior extends GameCharacter {
  armor: number;
  strength: number;

  constructor(name: string) {
    super(name);
    this.armor = 25;
    this.strength = 20;
  }

  // ๐Ÿ›ก๏ธ Defensive stance
  defendersStance(): void {
    console.log(`${this.name} takes a defensive stance! ๐Ÿ›ก๏ธ`);
    this.armor += 10;
    
    // ๐Ÿ• Remove bonus after some time (simplified)
    setTimeout(() => {
      this.armor -= 10;
      console.log(`${this.name}'s defensive stance ends.`);
    }, 3000);
  }

  // ๐Ÿ’ฅ Power attack
  powerAttack(): number {
    const damage = this.strength + Math.floor(Math.random() * 15) + 10;
    console.log(`${this.name} performs a powerful attack for ${damage} damage! ๐Ÿ’ฅ`);
    return damage;
  }

  // ๐Ÿ›ก๏ธ Override takeDamage to account for armor
  takeDamage(damage: number): void {
    const reducedDamage = Math.max(1, damage - this.armor);
    super.takeDamage(reducedDamage);
    console.log(`๐Ÿ›ก๏ธ Armor reduced damage from ${damage} to ${reducedDamage}`);
  }
}

// ๐ŸŽฎ Let's play!
const gandalf = new Wizard("Gandalf");
const conan = new Warrior("Conan");

gandalf.castSpell(); // Magical attack!
conan.powerAttack(); // Physical attack!
conan.defendersStance(); // Defensive move!

๐ŸŽฏ Try it yourself: Add a Rogue class with stealth and backstab abilities!

๐Ÿช Example 2: E-commerce Product System

Letโ€™s create a product hierarchy for an online store:

// ๐Ÿ›๏ธ Base product class
class Product {
  id: string;
  name: string;
  price: number;
  category: string;
  inStock: boolean;

  constructor(id: string, name: string, price: number, category: string) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.category = category;
    this.inStock = true;
  }

  // ๐Ÿ’ฐ Get formatted price
  getFormattedPrice(): string {
    return `$${this.price.toFixed(2)} ๐Ÿ’ฐ`;
  }

  // ๐Ÿ“ฆ Check availability
  checkAvailability(): boolean {
    return this.inStock;
  }

  // ๐Ÿท๏ธ Apply discount
  applyDiscount(percentage: number): void {
    if (percentage > 0 && percentage <= 100) {
      this.price = this.price * (1 - percentage / 100);
      console.log(`๐ŸŽŠ ${percentage}% discount applied! New price: ${this.getFormattedPrice()}`);
    }
  }

  // ๐Ÿ“‹ Get product summary
  getSummary(): string {
    return `${this.name} - ${this.getFormattedPrice()} [${this.category}] ๐Ÿ“ฆ`;
  }
}

// ๐Ÿ‘• Clothing product
class Clothing extends Product {
  size: string;
  color: string;
  material: string;

  constructor(id: string, name: string, price: number, size: string, color: string, material: string) {
    super(id, name, price, "Clothing");
    this.size = size;
    this.color = color;
    this.material = material;
  }

  // ๐Ÿ‘” Get clothing details
  getClothingDetails(): string {
    return `Size: ${this.size}, Color: ${this.color}, Material: ${this.material} ๐Ÿ‘”`;
  }

  // ๐Ÿงบ Washing instructions
  getWashingInstructions(): string {
    const instructions = {
      cotton: "Machine wash cold, tumble dry low ๐Ÿงบ",
      silk: "Dry clean only โœจ",
      wool: "Hand wash cold, lay flat to dry ๐Ÿ‘",
      polyester: "Machine wash warm, tumble dry medium ๐Ÿ”„"
    };
    
    return instructions[this.material.toLowerCase()] || "Follow care label instructions ๐Ÿ“";
  }
}

// ๐Ÿ“ฑ Electronics product
class Electronics extends Product {
  brand: string;
  warranty: number; // in months
  powerRating: string;

  constructor(id: string, name: string, price: number, brand: string, warranty: number, powerRating: string) {
    super(id, name, price, "Electronics");
    this.brand = brand;
    this.warranty = warranty;
    this.powerRating = powerRating;
  }

  // ๐Ÿ”ง Get tech specs
  getTechSpecs(): string {
    return `Brand: ${this.brand}, Warranty: ${this.warranty} months, Power: ${this.powerRating} โšก`;
  }

  // ๐Ÿ›ก๏ธ Extended warranty
  extendWarranty(additionalMonths: number): void {
    this.warranty += additionalMonths;
    console.log(`๐Ÿ›ก๏ธ Warranty extended! New warranty: ${this.warranty} months`);
  }

  // ๐Ÿ”‹ Power efficiency rating
  getPowerEfficiency(): string {
    const rating = this.powerRating.toLowerCase();
    if (rating.includes("low") || rating.includes("5w")) return "Excellent ๐ŸŒŸ";
    if (rating.includes("medium") || rating.includes("15w")) return "Good ๐Ÿ‘";
    return "Standard โšก";
  }
}

// ๐Ÿ›’ Let's go shopping!
const tshirt = new Clothing("C001", "Cool TypeScript T-Shirt", 29.99, "L", "Blue", "Cotton");
const laptop = new Electronics("E001", "DevBook Pro", 1299.99, "TechCorp", 24, "65W");

console.log(tshirt.getSummary()); // Product info
console.log(tshirt.getClothingDetails()); // Clothing-specific info
console.log(tshirt.getWashingInstructions()); // Care instructions

console.log(laptop.getSummary()); // Product info  
console.log(laptop.getTechSpecs()); // Electronics-specific info
laptop.extendWarranty(12); // Extend warranty

// ๐ŸŽŠ Apply discounts
tshirt.applyDiscount(20); // 20% off clothing
laptop.applyDiscount(10); // 10% off electronics

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Protected Members

When building inheritance hierarchies, you often want to share data between parent and child classes, but not expose it publicly:

// ๐Ÿฆ Bank account hierarchy
class BankAccount {
  protected accountNumber: string; // ๐Ÿ”’ Protected - accessible by children
  protected balance: number;
  private pin: string; // ๐Ÿšซ Private - only this class

  constructor(accountNumber: string, initialBalance: number, pin: string) {
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
    this.pin = pin;
  }

  // ๐Ÿ” Public method to check balance
  public getBalance(): number {
    return this.balance;
  }

  // ๐Ÿ”’ Protected method - children can use this
  protected validateTransaction(amount: number): boolean {
    return amount > 0 && amount <= this.balance;
  }
}

// ๐Ÿ’ณ Savings account with interest
class SavingsAccount extends BankAccount {
  private interestRate: number;

  constructor(accountNumber: string, initialBalance: number, pin: string, interestRate: number) {
    super(accountNumber, initialBalance, pin);
    this.interestRate = interestRate;
  }

  // ๐Ÿ’ฐ Add interest - uses protected balance
  addInterest(): void {
    const interest = this.balance * (this.interestRate / 100);
    this.balance += interest; // โœ… Can access protected member
    console.log(`๐Ÿ’ฐ Interest added: $${interest.toFixed(2)}. New balance: $${this.balance.toFixed(2)}`);
  }

  // ๐Ÿฆ Withdraw with validation - uses protected method
  withdraw(amount: number): boolean {
    if (this.validateTransaction(amount)) { // โœ… Can use protected method
      this.balance -= amount;
      console.log(`๐Ÿ’ธ Withdrew $${amount}. New balance: $${this.balance.toFixed(2)}`);
      return true;
    }
    console.log("โŒ Invalid transaction");
    return false;
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Abstract Base Classes

Sometimes you want to create a base class that should never be instantiated directly:

// ๐ŸŽจ Abstract shape class - blueprint only
abstract class Shape {
  protected x: number;
  protected y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  // ๐Ÿ“ Concrete method - all shapes can move
  move(deltaX: number, deltaY: number): void {
    this.x += deltaX;
    this.y += deltaY;
    console.log(`๐Ÿ”„ Moved to (${this.x}, ${this.y})`);
  }

  // ๐ŸŽฏ Abstract methods - must be implemented by children
  abstract getArea(): number;
  abstract getPerimeter(): number;
  abstract draw(): void;
}

// ๐Ÿ”ต Circle implementation
class Circle extends Shape {
  private radius: number;

  constructor(x: number, y: number, radius: number) {
    super(x, y);
    this.radius = radius;
  }

  getArea(): number {
    return Math.PI * this.radius * this.radius;
  }

  getPerimeter(): number {
    return 2 * Math.PI * this.radius;
  }

  draw(): void {
    console.log(`๐Ÿ”ต Drawing circle at (${this.x}, ${this.y}) with radius ${this.radius}`);
  }
}

// ๐Ÿ”ท Rectangle implementation
class Rectangle extends Shape {
  private width: number;
  private height: number;

  constructor(x: number, y: number, width: number, height: number) {
    super(x, y);
    this.width = width;
    this.height = height;
  }

  getArea(): number {
    return this.width * this.height;
  }

  getPerimeter(): number {
    return 2 * (this.width + this.height);
  }

  draw(): void {
    console.log(`๐Ÿ”ท Drawing rectangle at (${this.x}, ${this.y}) - ${this.width}x${this.height}`);
  }
}

// ๐ŸŽจ Using our shapes
const circle = new Circle(10, 20, 5);
const rectangle = new Rectangle(0, 0, 10, 8);

circle.draw(); // ๐Ÿ”ต Drawing circle...
console.log(`Circle area: ${circle.getArea().toFixed(2)} ๐Ÿ“`);

rectangle.move(5, 5); // ๐Ÿ”„ Moved to (5, 5)
rectangle.draw(); // ๐Ÿ”ท Drawing rectangle...

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Forgetting to Call super()

// โŒ Wrong way - missing super() call!
class BadStudent extends Person {
  studentId: string;

  constructor(name: string, age: number, studentId: string) {
    // Missing super(name, age) - This will cause an error!
    this.studentId = studentId;
  }
}

// โœ… Correct way - always call super()!
class GoodStudent extends Person {
  studentId: string;

  constructor(name: string, age: number, studentId: string) {
    super(name, age); // โœ… Call parent constructor first!
    this.studentId = studentId;
  }
}

๐Ÿคฏ Pitfall 2: Overusing Inheritance

// โŒ Dangerous - too many inheritance levels!
class Animal { }
class Mammal extends Animal { }
class Carnivore extends Mammal { }
class Feline extends Carnivore { }
class BigCat extends Feline { }
class Lion extends BigCat { } // ๐Ÿ˜ต Too deep!

// โœ… Better - use composition when appropriate!
interface Carnivorous {
  hunt(): void;
}

interface Feline {
  purr(): void;
  retractClaws(): void;
}

class Lion extends Animal implements Carnivorous, Feline {
  hunt(): void {
    console.log("๐Ÿฆ Lion hunts for prey!");
  }

  purr(): void {
    console.log("๐Ÿ˜บ Lion purrs contentedly");
  }

  retractClaws(): void {
    console.log("๐Ÿพ Lion retracts claws");
  }
}

๐Ÿ”„ Pitfall 3: Not Understanding Method Resolution

class Parent {
  greet(): string {
    return "Hello from Parent! ๐Ÿ‘‹";
  }
}

class Child extends Parent {
  greet(): string {
    return "Hello from Child! ๐Ÿ‘ถ";
  }

  // โŒ Common confusion - which greet() gets called?
  confusingMethod(): void {
    console.log(this.greet()); // Will call Child's greet(), not Parent's!
  }

  // โœ… Correct - explicitly call parent method when needed
  clarMethod(): void {
    console.log(super.greet()); // Calls Parent's greet()
    console.log(this.greet());  // Calls Child's greet()
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Favor Composition Over Inheritance: Donโ€™t inherit just to reuse code
  2. ๐Ÿ“ Keep Hierarchies Shallow: Avoid deep inheritance chains (max 3-4 levels)
  3. ๐Ÿ›ก๏ธ Use Protected Wisely: Share data with children, not the world
  4. โœจ Call super() First: Always call parent constructor before your code
  5. ๐ŸŽจ Consider Interfaces: Sometimes interface implementation is better than inheritance

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Media Library System

Create a type-safe media library with inheritance:

๐Ÿ“‹ Requirements:

  • โœ… Base MediaItem class with title, duration, rating
  • ๐ŸŽฌ Movie class with director, genre, cast
  • ๐ŸŽต Song class with artist, album, genre
  • ๐Ÿ“š Audiobook class with author, narrator, chapters
  • ๐ŸŽฎ Each item needs appropriate emoji and methods!

๐Ÿš€ Bonus Points:

  • Add a Playlist class that can contain any media type
  • Implement rating system and reviews
  • Add search and filter functionality

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐ŸŽญ Base media item class
abstract class MediaItem {
  title: string;
  duration: number; // in minutes
  rating: number; // 1-5 stars
  dateAdded: Date;
  
  constructor(title: string, duration: number) {
    this.title = title;
    this.duration = duration;
    this.rating = 0;
    this.dateAdded = new Date();
  }

  // โญ Set rating
  setRating(rating: number): void {
    if (rating >= 1 && rating <= 5) {
      this.rating = rating;
      console.log(`โญ Rated "${this.title}": ${rating}/5 stars`);
    }
  }

  // ๐Ÿ• Get formatted duration
  getFormattedDuration(): string {
    const hours = Math.floor(this.duration / 60);
    const minutes = this.duration % 60;
    return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
  }

  // ๐Ÿ“Š Abstract methods
  abstract getInfo(): string;
  abstract getCategory(): string;
  abstract play(): void;
}

// ๐ŸŽฌ Movie class
class Movie extends MediaItem {
  director: string;
  genre: string;
  cast: string[];
  year: number;

  constructor(title: string, duration: number, director: string, genre: string, cast: string[], year: number) {
    super(title, duration);
    this.director = director;
    this.genre = genre;
    this.cast = cast;
    this.year = year;
  }

  getCategory(): string {
    return "Movie ๐ŸŽฌ";
  }

  getInfo(): string {
    return `๐ŸŽฌ ${this.title} (${this.year})
๐Ÿ“ฝ๏ธ Director: ${this.director}
๐ŸŽญ Genre: ${this.genre}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
๐ŸŒŸ Cast: ${this.cast.join(", ")}
โญ Rating: ${this.rating}/5`;
  }

  play(): void {
    console.log(`๐ŸŽฌ Now playing: "${this.title}" directed by ${this.director}`);
  }

  // ๐Ÿ† Get Oscar potential
  getOscarPotential(): string {
    if (this.rating >= 4.5) return "Oscar worthy! ๐Ÿ†";
    if (this.rating >= 4.0) return "Strong contender ๐Ÿฅ‡";
    if (this.rating >= 3.5) return "Decent film ๐Ÿ‘";
    return "Popcorn movie ๐Ÿฟ";
  }
}

// ๐ŸŽต Song class
class Song extends MediaItem {
  artist: string;
  album: string;
  genre: string;
  year: number;

  constructor(title: string, duration: number, artist: string, album: string, genre: string, year: number) {
    super(title, duration);
    this.artist = artist;
    this.album = album;
    this.genre = genre;
    this.year = year;
  }

  getCategory(): string {
    return "Song ๐ŸŽต";
  }

  getInfo(): string {
    return `๐ŸŽต ${this.title}
๐ŸŽค Artist: ${this.artist}
๐Ÿ’ฟ Album: ${this.album}
๐ŸŽผ Genre: ${this.genre}
๐Ÿ“… Year: ${this.year}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
โญ Rating: ${this.rating}/5`;
  }

  play(): void {
    console.log(`๐ŸŽต Now playing: "${this.title}" by ${this.artist}`);
  }

  // ๐Ÿ”ฅ Check if it's a hit
  isHit(): boolean {
    return this.rating >= 4.0 && this.duration <= 4; // Great rating, not too long
  }
}

// ๐Ÿ“š Audiobook class
class Audiobook extends MediaItem {
  author: string;
  narrator: string;
  chapters: number;
  genre: string;

  constructor(title: string, duration: number, author: string, narrator: string, chapters: number, genre: string) {
    super(title, duration);
    this.author = author;
    this.narrator = narrator;
    this.chapters = chapters;
    this.genre = genre;
  }

  getCategory(): string {
    return "Audiobook ๐Ÿ“š";
  }

  getInfo(): string {
    return `๐Ÿ“š ${this.title}
โœ๏ธ Author: ${this.author}
๐ŸŽ™๏ธ Narrator: ${this.narrator}
๐Ÿ“‘ Chapters: ${this.chapters}
๐Ÿท๏ธ Genre: ${this.genre}
โฑ๏ธ Duration: ${this.getFormattedDuration()}
โญ Rating: ${this.rating}/5`;
  }

  play(): void {
    console.log(`๐Ÿ“š Now playing: "${this.title}" by ${this.author}, narrated by ${this.narrator}`);
  }

  // ๐Ÿ“– Average chapter length
  getAverageChapterLength(): string {
    const avgMinutes = Math.round(this.duration / this.chapters);
    return `${avgMinutes} minutes per chapter`;
  }
}

// ๐Ÿ—‚๏ธ Media library management
class MediaLibrary {
  private items: MediaItem[] = [];

  addItem(item: MediaItem): void {
    this.items.push(item);
    console.log(`โž• Added ${item.getCategory()}: "${item.title}"`);
  }

  searchByTitle(title: string): MediaItem[] {
    return this.items.filter(item => 
      item.title.toLowerCase().includes(title.toLowerCase())
    );
  }

  getByCategory(category: string): MediaItem[] {
    return this.items.filter(item => 
      item.getCategory().toLowerCase().includes(category.toLowerCase())
    );
  }

  getTopRated(count: number = 5): MediaItem[] {
    return this.items
      .filter(item => item.rating > 0)
      .sort((a, b) => b.rating - a.rating)
      .slice(0, count);
  }

  getTotalDuration(): string {
    const totalMinutes = this.items.reduce((sum, item) => sum + item.duration, 0);
    const hours = Math.floor(totalMinutes / 60);
    const minutes = totalMinutes % 60;
    return `${hours}h ${minutes}m of content! ๐Ÿ“Š`;
  }

  showLibrary(): void {
    console.log("๐Ÿ—‚๏ธ Media Library Contents:");
    this.items.forEach((item, index) => {
      console.log(`${index + 1}. ${item.getCategory()}: "${item.title}" โญ${item.rating}/5`);
    });
    console.log(`\n๐Ÿ“Š Total: ${this.items.length} items, ${this.getTotalDuration()}`);
  }
}

// ๐ŸŽฎ Let's build our library!
const library = new MediaLibrary();

// Add some content
const movie = new Movie("The TypeScript Chronicles", 142, "Code Director", "Sci-Fi", ["Dev Hero", "Bug Villain"], 2023);
const song = new Song("Async Await Blues", 3, "The Promises", "Callback Hell", "Tech Rock", 2022);
const audiobook = new Audiobook("Clean Code", 720, "Robert Martin", "Tech Narrator", 24, "Programming");

movie.setRating(5);
song.setRating(4);
audiobook.setRating(5);

library.addItem(movie);
library.addItem(song);
library.addItem(audiobook);

// Explore our library
library.showLibrary();
console.log("\n๐Ÿ† Top rated content:");
library.getTopRated(3).forEach(item => console.log(item.getInfo()));

๐ŸŽ“ Key Takeaways

Youโ€™ve learned so much about inheritance! Hereโ€™s what you can now do:

  • โœ… Create class hierarchies with confidence ๐Ÿ’ช
  • โœ… Use extends keyword properly ๐Ÿ—๏ธ
  • โœ… Call parent constructors with super() ๐Ÿ“ž
  • โœ… Share code effectively between related classes โ™ป๏ธ
  • โœ… Apply inheritance in real-world scenarios ๐ŸŽฏ

Remember: Inheritance is a powerful tool, but use it wisely! Sometimes composition is better than inheritance. ๐Ÿค

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered TypeScript inheritance!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the game character exercise above
  2. ๐Ÿ—๏ธ Build your own class hierarchy for a domain youโ€™re interested in
  3. ๐Ÿ“š Move on to our next tutorial: Super Keyword: Calling Parent Class Members
  4. ๐ŸŒŸ Experiment with abstract classes and protected members!

Remember: Every TypeScript expert started with classes and inheritance. Keep building, keep learning, and most importantly, have fun! ๐Ÿš€


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