+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 37 of 72

๐Ÿ— ๏ธ Abstract Classes and Methods: Creating Base Classes

Master abstract classes in TypeScript to create powerful base classes with enforced contracts and shared functionality ๐Ÿš€

๐Ÿš€Intermediate
25 min read

Prerequisites

  • Understanding of class inheritance ๐Ÿ“
  • Knowledge of method overriding โšก
  • Familiarity with class hierarchies ๐Ÿ’ป

What you'll learn

  • Understand abstract class fundamentals ๐ŸŽฏ
  • Create abstract methods and enforce implementation ๐Ÿ—๏ธ
  • Design robust class hierarchies with abstractions ๐Ÿ›
  • Apply the Template Method pattern effectively โœจ

๐ŸŽฏ Introduction

Welcome to the powerful world of abstract classes! ๐ŸŽ‰ In this guide, weโ€™ll explore how to create base classes that serve as blueprints for your class hierarchies, ensuring consistency while allowing flexibility.

Youโ€™ll discover how abstract classes are like architectural blueprints ๐Ÿ›๏ธ - they define the structure and rules, but leave the specific implementation details to the builders. Whether youโ€™re designing game frameworks ๐ŸŽฎ, building UI component libraries ๐ŸŽจ, or creating data processing pipelines ๐Ÿ”„, understanding abstract classes is essential for creating maintainable, scalable TypeScript applications.

By the end of this tutorial, youโ€™ll be confidently designing abstract base classes that enforce contracts while sharing common functionality! Letโ€™s dive in! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Abstract Classes

๐Ÿค” What are Abstract Classes?

Abstract classes are like incomplete recipes ๐Ÿ“– - they provide the basic structure and some ingredients, but require you to fill in the specific steps. Think of them as a contract combined with partial implementation.

In TypeScript terms, abstract classes:

  • โœจ Cannot be instantiated directly (no new AbstractClass())
  • ๐Ÿš€ Can contain both implemented and abstract methods
  • ๐Ÿ›ก๏ธ Force child classes to implement specific methods
  • ๐Ÿ”ง Share common functionality across all subclasses

๐Ÿ’ก Why Use Abstract Classes?

Hereโ€™s why developers love abstract classes:

  1. Enforced Contracts ๐Ÿ“œ: Guarantee certain methods exist in all subclasses
  2. Code Reuse โ™ป๏ธ: Share common implementation across related classes
  3. Type Safety ๐Ÿ›ก๏ธ: Compile-time checking for required implementations
  4. Design Patterns ๐ŸŽจ: Enable powerful patterns like Template Method

Real-world example: Imagine building a payment processing system ๐Ÿ’ณ. Your abstract PaymentProcessor class defines the steps every payment must follow, while specific processors like CreditCardProcessor and PayPalProcessor implement the details.

๐Ÿ”ง Basic Syntax and Usage

๐Ÿ“ Simple Example

Letโ€™s start with a friendly example:

// ๐ŸŽจ Abstract shape class - cannot be instantiated
abstract class Shape {
  // ๐Ÿ“Š Regular properties - shared by all shapes
  color: string;
  name: string;

  constructor(color: string, name: string) {
    this.color = color;
    this.name = name;
    console.log(`๐ŸŽจ Creating ${color} ${name}`);
  }

  // โœ… Concrete method - implemented and shared by all shapes
  describe(): string {
    return `This is a ${this.color} ${this.name}`;
  }

  // ๐ŸŽฏ Abstract methods - MUST be implemented by subclasses
  abstract calculateArea(): number;
  abstract calculatePerimeter(): number;
  
  // ๐Ÿ–ผ๏ธ Another abstract method
  abstract draw(): void;

  // โœ… Concrete method that uses abstract methods
  printInfo(): void {
    console.log(`๐Ÿ“Š ${this.describe()}`);
    console.log(`๐Ÿ“ Area: ${this.calculateArea().toFixed(2)} square units`);
    console.log(`๐Ÿ“ Perimeter: ${this.calculatePerimeter().toFixed(2)} units`);
    this.draw();
  }
}

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

  constructor(color: string, radius: number) {
    super(color, 'Circle'); // ๐Ÿ“ž Call abstract class constructor
    this.radius = radius;
  }

  // โœ… Must implement all abstract methods
  calculateArea(): number {
    return Math.PI * this.radius ** 2;
  }

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

  draw(): void {
    console.log(`๐Ÿ”ต Drawing a ${this.color} circle with radius ${this.radius}`);
  }
}

// ๐ŸŸฆ Rectangle implementation
class Rectangle extends Shape {
  width: number;
  height: number;

  constructor(color: string, width: number, height: number) {
    super(color, 'Rectangle');
    this.width = width;
    this.height = height;
  }

  // โœ… Implementing all abstract methods
  calculateArea(): number {
    return this.width * this.height;
  }

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

  draw(): void {
    console.log(`๐ŸŸฆ Drawing a ${this.color} rectangle: ${this.width}x${this.height}`);
  }
}

// ๐ŸŽฎ Using our shapes
const circle = new Circle('red', 5);
const rectangle = new Rectangle('blue', 10, 6);

// โŒ This would error: Cannot create an instance of an abstract class
// const shape = new Shape('green', 'Generic');

// โœ… Using the shapes
circle.printInfo();
console.log('\n');
rectangle.printInfo();

๐Ÿ’ก Explanation: The abstract Shape class provides common functionality (describe(), printInfo()) while forcing subclasses to implement specific methods (calculateArea(), draw())!

๐ŸŽฏ Abstract Properties

Abstract classes can also have abstract properties:

// ๐Ÿข Abstract employee class
abstract class Employee {
  name: string;
  id: string;

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

  // ๐ŸŽฏ Abstract property - subclasses must define
  abstract readonly department: string;
  abstract salary: number;

  // ๐ŸŽฏ Abstract methods
  abstract calculateBonus(): number;
  abstract getResponsibilities(): string[];

  // โœ… Concrete methods using abstract members
  getEmployeeInfo(): string {
    return `๐Ÿ‘ค ${this.name} (${this.id})
๐Ÿข Department: ${this.department}
๐Ÿ’ฐ Salary: $${this.salary.toLocaleString()}
๐ŸŽ Bonus: $${this.calculateBonus().toLocaleString()}`;
  }

  listResponsibilities(): void {
    console.log(`๐Ÿ“‹ Responsibilities for ${this.name}:`);
    this.getResponsibilities().forEach((resp, index) => {
      console.log(`  ${index + 1}. ${resp}`);
    });
  }
}

// ๐Ÿ‘จโ€๐Ÿ’ป Developer implementation
class Developer extends Employee {
  readonly department = 'Engineering'; // โœ… Implementing abstract property
  salary: number;
  programmingLanguages: string[];

  constructor(name: string, id: string, salary: number, languages: string[]) {
    super(name, id);
    this.salary = salary;
    this.programmingLanguages = languages;
  }

  // โœ… Implementing abstract methods
  calculateBonus(): number {
    // 15% bonus for developers
    return this.salary * 0.15;
  }

  getResponsibilities(): string[] {
    return [
      '๐Ÿ’ป Write clean, maintainable code',
      '๐Ÿ› Debug and fix issues',
      '๐Ÿ‘ฅ Participate in code reviews',
      '๐Ÿ“š Keep up with new technologies',
      `๐Ÿ”ง Expert in: ${this.programmingLanguages.join(', ')}`
    ];
  }

  // ๐Ÿ‘จโ€๐Ÿ’ป Developer-specific method
  code(): void {
    console.log(`${this.name} is coding in ${this.programmingLanguages[0]}! โŒจ๏ธ`);
  }
}

// ๐Ÿ‘” Manager implementation
class Manager extends Employee {
  readonly department = 'Management';
  salary: number;
  teamSize: number;

  constructor(name: string, id: string, salary: number, teamSize: number) {
    super(name, id);
    this.salary = salary;
    this.teamSize = teamSize;
  }

  calculateBonus(): number {
    // 20% bonus + extra for team size
    return this.salary * 0.20 + (this.teamSize * 1000);
  }

  getResponsibilities(): string[] {
    return [
      '๐Ÿ‘ฅ Lead and motivate team',
      '๐Ÿ“Š Track project progress',
      '๐Ÿ’ผ Conduct performance reviews',
      `๐ŸŽฏ Manage team of ${this.teamSize} people`,
      '๐Ÿค Facilitate communication'
    ];
  }

  // ๐Ÿ‘” Manager-specific method
  conductMeeting(): void {
    console.log(`${this.name} is conducting a team meeting! ๐Ÿ“…`);
  }
}

// ๐ŸŽฎ Let's use our employees
const developer = new Developer('Alice', 'DEV001', 85000, ['TypeScript', 'React', 'Node.js']);
const manager = new Manager('Bob', 'MGR001', 95000, 8);

console.log(developer.getEmployeeInfo());
developer.listResponsibilities();
developer.code();

console.log('\n' + manager.getEmployeeInfo());
manager.listResponsibilities();
manager.conductMeeting();

๐Ÿ’ก Practical Examples

๐ŸŽฎ Example 1: Game Entity System

Letโ€™s create a game entity system with abstract base classes:

// ๐ŸŽฎ Abstract game entity
abstract class GameEntity {
  protected x: number;
  protected y: number;
  protected health: number;
  protected maxHealth: number;
  protected isAlive: boolean = true;

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

  // ๐ŸŽฏ Abstract methods that all entities must implement
  abstract render(): void;
  abstract update(deltaTime: number): void;
  abstract onCollision(other: GameEntity): void;
  abstract getType(): string;

  // โœ… Concrete methods shared by all entities
  takeDamage(amount: number): void {
    if (!this.isAlive) return;

    this.health = Math.max(0, this.health - amount);
    console.log(`๐Ÿ’ฅ ${this.getType()} takes ${amount} damage! Health: ${this.health}/${this.maxHealth}`);

    if (this.health === 0) {
      this.onDeath();
    }
  }

  heal(amount: number): void {
    if (!this.isAlive) return;

    const oldHealth = this.health;
    this.health = Math.min(this.maxHealth, this.health + amount);
    const actualHeal = this.health - oldHealth;
    
    if (actualHeal > 0) {
      console.log(`๐Ÿ’š ${this.getType()} heals for ${actualHeal}! Health: ${this.health}/${this.maxHealth}`);
    }
  }

  move(dx: number, dy: number): void {
    this.x += dx;
    this.y += dy;
  }

  getPosition(): { x: number; y: number } {
    return { x: this.x, y: this.y };
  }

  isNearby(other: GameEntity, range: number): boolean {
    const dx = this.x - other.x;
    const dy = this.y - other.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    return distance <= range;
  }

  // ๐ŸŽฏ Protected method that subclasses can override
  protected onDeath(): void {
    this.isAlive = false;
    console.log(`โ˜ ๏ธ ${this.getType()} has been defeated!`);
  }
}

// ๐Ÿ‘ค Abstract character class (extends GameEntity)
abstract class Character extends GameEntity {
  protected name: string;
  protected level: number;
  protected experience: number = 0;

  constructor(name: string, x: number, y: number, health: number, level: number = 1) {
    super(x, y, health);
    this.name = name;
    this.level = level;
  }

  // ๐ŸŽฏ Additional abstract methods for characters
  abstract attack(target: GameEntity): void;
  abstract useAbility(): void;
  abstract getClass(): string;

  // โœ… Concrete character methods
  gainExperience(amount: number): void {
    this.experience += amount;
    console.log(`โญ ${this.name} gains ${amount} XP!`);

    // Level up every 100 XP
    while (this.experience >= this.level * 100) {
      this.levelUp();
    }
  }

  protected levelUp(): void {
    this.level++;
    this.maxHealth += 20;
    this.health = this.maxHealth; // Full heal on level up
    console.log(`๐ŸŽ‰ ${this.name} reached level ${this.level}!`);
  }

  getType(): string {
    return `${this.getClass()} ${this.name}`;
  }
}

// โš”๏ธ Warrior implementation
class Warrior extends Character {
  private rage: number = 0;
  private maxRage: number = 100;

  constructor(name: string, x: number, y: number) {
    super(name, x, y, 120, 1); // Warriors have high health
  }

  getClass(): string {
    return 'Warrior';
  }

  attack(target: GameEntity): void {
    if (!this.isAlive) return;

    const damage = 15 + this.level * 3;
    console.log(`โš”๏ธ ${this.name} swings their sword!`);
    target.takeDamage(damage);

    // Build rage
    this.rage = Math.min(this.maxRage, this.rage + 10);
    console.log(`๐Ÿ”ฅ Rage: ${this.rage}/${this.maxRage}`);
  }

  useAbility(): void {
    if (!this.isAlive) return;

    if (this.rage >= 50) {
      console.log(`๐Ÿ’ฅ ${this.name} enters BERSERKER MODE!`);
      this.rage = 0;
      // Temporary damage boost would go here
    } else {
      console.log(`โŒ Not enough rage! Need 50, have ${this.rage}`);
    }
  }

  render(): void {
    console.log(`โš”๏ธ Rendering Warrior ${this.name} at (${this.x}, ${this.y})`);
  }

  update(deltaTime: number): void {
    // Regenerate rage slowly
    if (this.rage < this.maxRage) {
      this.rage = Math.min(this.maxRage, this.rage + deltaTime * 2);
    }
  }

  onCollision(other: GameEntity): void {
    console.log(`โš”๏ธ ${this.name} bumps into ${other.getType()}`);
  }
}

// ๐Ÿง™โ€โ™‚๏ธ Mage implementation
class Mage extends Character {
  private mana: number;
  private maxMana: number = 100;

  constructor(name: string, x: number, y: number) {
    super(name, x, y, 80, 1); // Mages have lower health
    this.mana = this.maxMana;
  }

  getClass(): string {
    return 'Mage';
  }

  attack(target: GameEntity): void {
    if (!this.isAlive) return;

    if (this.mana >= 10) {
      const damage = 25 + this.level * 5; // Higher damage but costs mana
      this.mana -= 10;
      console.log(`โœจ ${this.name} casts Fireball!`);
      target.takeDamage(damage);
      console.log(`๐Ÿ”ฎ Mana: ${this.mana}/${this.maxMana}`);
    } else {
      console.log(`โŒ Not enough mana!`);
    }
  }

  useAbility(): void {
    if (!this.isAlive) return;

    if (this.mana >= 30) {
      console.log(`๐ŸŒŸ ${this.name} casts Teleport!`);
      this.mana -= 30;
      // Teleport to random location
      this.x = Math.random() * 100;
      this.y = Math.random() * 100;
      console.log(`โœจ Teleported to (${this.x.toFixed(1)}, ${this.y.toFixed(1)})`);
    } else {
      console.log(`โŒ Not enough mana for Teleport!`);
    }
  }

  render(): void {
    console.log(`๐Ÿง™โ€โ™‚๏ธ Rendering Mage ${this.name} at (${this.x}, ${this.y})`);
  }

  update(deltaTime: number): void {
    // Regenerate mana
    if (this.mana < this.maxMana) {
      this.mana = Math.min(this.maxMana, this.mana + deltaTime * 5);
    }
  }

  onCollision(other: GameEntity): void {
    console.log(`๐Ÿง™โ€โ™‚๏ธ ${this.name} collides with ${other.getType()} in a flash of magic!`);
  }
}

// ๐Ÿ‰ Monster implementation (directly extends GameEntity)
class Monster extends GameEntity {
  private monsterType: string;
  private attackPower: number;

  constructor(type: string, x: number, y: number, health: number, attackPower: number) {
    super(x, y, health);
    this.monsterType = type;
    this.attackPower = attackPower;
  }

  getType(): string {
    return this.monsterType;
  }

  render(): void {
    console.log(`๐Ÿ‰ Rendering ${this.monsterType} at (${this.x}, ${this.y})`);
  }

  update(deltaTime: number): void {
    // Simple AI - move randomly
    if (Math.random() < 0.1) {
      this.move(
        (Math.random() - 0.5) * 10,
        (Math.random() - 0.5) * 10
      );
    }
  }

  onCollision(other: GameEntity): void {
    if (other instanceof Character) {
      console.log(`๐Ÿ‰ ${this.monsterType} attacks ${other.getType()}!`);
      other.takeDamage(this.attackPower);
    }
  }

  protected onDeath(): void {
    super.onDeath();
    console.log(`๐Ÿ’ฐ ${this.monsterType} drops loot!`);
  }
}

// ๐ŸŽฎ Game simulation
const warrior = new Warrior('Thorin', 10, 10);
const mage = new Mage('Gandalf', 20, 20);
const dragon = new Monster('Fire Dragon', 50, 50, 200, 30);

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

warrior.attack(dragon);
mage.attack(dragon);
dragon.onCollision(warrior);

warrior.useAbility();
mage.useAbility();

// Check proximity
if (warrior.isNearby(mage, 20)) {
  console.log('\n๐Ÿค Warrior and Mage are fighting together!');
}

// Simulate game loop
console.log('\n๐Ÿ”„ Game Update...');
warrior.update(0.5);
mage.update(0.5);
dragon.update(0.5);

// Level up simulation
warrior.gainExperience(150);

๐Ÿ”„ Example 2: Data Processing Pipeline

Letโ€™s create an abstract data processing pipeline:

// ๐Ÿ“Š Abstract data processor
abstract class DataProcessor<TInput, TOutput> {
  protected name: string;
  protected processedCount: number = 0;
  protected errorCount: number = 0;

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

  // ๐ŸŽฏ Template Method Pattern - defines the algorithm structure
  async process(data: TInput[]): Promise<TOutput[]> {
    console.log(`๐Ÿ”„ Starting ${this.name} processing...`);
    
    // 1๏ธโƒฃ Validate input
    const validData = await this.validate(data);
    console.log(`โœ… Validated ${validData.length}/${data.length} items`);

    // 2๏ธโƒฃ Transform data
    const transformedData = await this.transform(validData);
    
    // 3๏ธโƒฃ Enrich data (optional)
    const enrichedData = await this.enrich(transformedData);
    
    // 4๏ธโƒฃ Format output
    const output = await this.format(enrichedData);
    
    this.processedCount += output.length;
    console.log(`โœ… ${this.name} completed! Processed: ${output.length} items`);
    
    // 5๏ธโƒฃ Generate report
    this.generateReport();
    
    return output;
  }

  // ๐ŸŽฏ Abstract methods that subclasses must implement
  protected abstract validate(data: TInput[]): Promise<TInput[]>;
  protected abstract transform(data: TInput[]): Promise<TOutput[]>;
  protected abstract format(data: TOutput[]): Promise<TOutput[]>;

  // โœ… Optional hook method - can be overridden
  protected async enrich(data: TOutput[]): Promise<TOutput[]> {
    // Default implementation - no enrichment
    return data;
  }

  // โœ… Concrete method
  protected generateReport(): void {
    console.log(`๐Ÿ“Š ${this.name} Report:`);
    console.log(`   โœ… Processed: ${this.processedCount}`);
    console.log(`   โŒ Errors: ${this.errorCount}`);
    console.log(`   ๐Ÿ“ˆ Success Rate: ${((this.processedCount / (this.processedCount + this.errorCount)) * 100).toFixed(1)}%`);
  }

  // โœ… Utility method for subclasses
  protected logError(message: string): void {
    this.errorCount++;
    console.error(`โŒ ${this.name} Error: ${message}`);
  }
}

// ๐Ÿ“ง Email data types
interface RawEmail {
  from: string;
  to: string;
  subject: string;
  body: string;
  timestamp: string;
}

interface ProcessedEmail {
  id: string;
  sender: string;
  recipient: string;
  subject: string;
  content: string;
  sentiment: 'positive' | 'neutral' | 'negative';
  priority: 'low' | 'medium' | 'high';
  processedAt: Date;
}

// ๐Ÿ“ง Email processor implementation
class EmailProcessor extends DataProcessor<RawEmail, ProcessedEmail> {
  constructor() {
    super('Email Processor');
  }

  protected async validate(emails: RawEmail[]): Promise<RawEmail[]> {
    return emails.filter(email => {
      // Validate email format
      const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
      
      if (!emailRegex.test(email.from) || !emailRegex.test(email.to)) {
        this.logError(`Invalid email address in ${email.subject}`);
        return false;
      }
      
      if (!email.subject || !email.body) {
        this.logError('Email missing subject or body');
        return false;
      }
      
      return true;
    });
  }

  protected async transform(emails: RawEmail[]): Promise<ProcessedEmail[]> {
    return emails.map(email => ({
      id: `email_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
      sender: email.from.toLowerCase(),
      recipient: email.to.toLowerCase(),
      subject: email.subject,
      content: email.body,
      sentiment: this.analyzeSentiment(email.body),
      priority: this.calculatePriority(email),
      processedAt: new Date()
    }));
  }

  protected async enrich(emails: ProcessedEmail[]): Promise<ProcessedEmail[]> {
    // Enrich with spam detection
    return emails.map(email => ({
      ...email,
      subject: this.detectSpam(email) ? `[SPAM] ${email.subject}` : email.subject
    }));
  }

  protected async format(emails: ProcessedEmail[]): Promise<ProcessedEmail[]> {
    // Format subject lines
    return emails.map(email => ({
      ...email,
      subject: email.subject.slice(0, 100) // Truncate long subjects
    }));
  }

  // ๐Ÿ“ง Helper methods
  private analyzeSentiment(text: string): 'positive' | 'neutral' | 'negative' {
    const positiveWords = ['great', 'excellent', 'happy', 'thanks', 'appreciate'];
    const negativeWords = ['bad', 'terrible', 'angry', 'disappointed', 'problem'];
    
    const lowerText = text.toLowerCase();
    const positiveCount = positiveWords.filter(word => lowerText.includes(word)).length;
    const negativeCount = negativeWords.filter(word => lowerText.includes(word)).length;
    
    if (positiveCount > negativeCount) return 'positive';
    if (negativeCount > positiveCount) return 'negative';
    return 'neutral';
  }

  private calculatePriority(email: RawEmail): 'low' | 'medium' | 'high' {
    const urgentKeywords = ['urgent', 'asap', 'immediately', 'critical'];
    const lowerSubject = email.subject.toLowerCase();
    
    if (urgentKeywords.some(keyword => lowerSubject.includes(keyword))) {
      return 'high';
    }
    
    if (email.to.includes('important@')) {
      return 'medium';
    }
    
    return 'low';
  }

  private detectSpam(email: ProcessedEmail): boolean {
    const spamKeywords = ['free money', 'click here', 'limited offer', 'act now'];
    const content = (email.subject + ' ' + email.content).toLowerCase();
    
    return spamKeywords.some(keyword => content.includes(keyword));
  }
}

// ๐Ÿ“Š Sales data types
interface RawSalesData {
  date: string;
  product: string;
  quantity: string;
  price: string;
  customer: string;
}

interface ProcessedSalesData {
  transactionId: string;
  date: Date;
  product: string;
  quantity: number;
  unitPrice: number;
  totalAmount: number;
  customer: string;
  category: string;
  profitMargin: number;
}

// ๐Ÿ’ฐ Sales data processor
class SalesDataProcessor extends DataProcessor<RawSalesData, ProcessedSalesData> {
  private productCategories = new Map<string, string>([
    ['laptop', 'Electronics'],
    ['phone', 'Electronics'],
    ['desk', 'Furniture'],
    ['chair', 'Furniture'],
    ['notebook', 'Stationery']
  ]);

  constructor() {
    super('Sales Data Processor');
  }

  protected async validate(sales: RawSalesData[]): Promise<RawSalesData[]> {
    return sales.filter(sale => {
      // Validate numeric values
      const quantity = parseInt(sale.quantity);
      const price = parseFloat(sale.price);
      
      if (isNaN(quantity) || quantity <= 0) {
        this.logError(`Invalid quantity: ${sale.quantity}`);
        return false;
      }
      
      if (isNaN(price) || price <= 0) {
        this.logError(`Invalid price: ${sale.price}`);
        return false;
      }
      
      // Validate date
      const date = new Date(sale.date);
      if (isNaN(date.getTime())) {
        this.logError(`Invalid date: ${sale.date}`);
        return false;
      }
      
      return true;
    });
  }

  protected async transform(sales: RawSalesData[]): Promise<ProcessedSalesData[]> {
    return sales.map(sale => {
      const quantity = parseInt(sale.quantity);
      const unitPrice = parseFloat(sale.price);
      const totalAmount = quantity * unitPrice;
      
      return {
        transactionId: `TXN_${Date.now()}_${Math.random().toString(36).substr(2, 5)}`,
        date: new Date(sale.date),
        product: sale.product,
        quantity,
        unitPrice,
        totalAmount,
        customer: sale.customer,
        category: this.getProductCategory(sale.product),
        profitMargin: this.calculateProfitMargin(sale.product, unitPrice)
      };
    });
  }

  protected async enrich(sales: ProcessedSalesData[]): Promise<ProcessedSalesData[]> {
    // Add loyalty tier based on total purchases
    const customerTotals = new Map<string, number>();
    
    sales.forEach(sale => {
      const current = customerTotals.get(sale.customer) || 0;
      customerTotals.set(sale.customer, current + sale.totalAmount);
    });
    
    return sales.map(sale => ({
      ...sale,
      customer: this.addLoyaltyTier(sale.customer, customerTotals.get(sale.customer) || 0)
    }));
  }

  protected async format(sales: ProcessedSalesData[]): Promise<ProcessedSalesData[]> {
    // Format currency values
    return sales.map(sale => ({
      ...sale,
      unitPrice: Math.round(sale.unitPrice * 100) / 100,
      totalAmount: Math.round(sale.totalAmount * 100) / 100,
      profitMargin: Math.round(sale.profitMargin * 100) / 100
    }));
  }

  // ๐Ÿ’ฐ Helper methods
  private getProductCategory(product: string): string {
    const lowerProduct = product.toLowerCase();
    
    for (const [keyword, category] of this.productCategories) {
      if (lowerProduct.includes(keyword)) {
        return category;
      }
    }
    
    return 'Other';
  }

  private calculateProfitMargin(product: string, price: number): number {
    // Simplified profit margin calculation
    const category = this.getProductCategory(product);
    
    switch (category) {
      case 'Electronics':
        return price * 0.15; // 15% margin
      case 'Furniture':
        return price * 0.25; // 25% margin
      case 'Stationery':
        return price * 0.40; // 40% margin
      default:
        return price * 0.20; // 20% default
    }
  }

  private addLoyaltyTier(customer: string, totalPurchases: number): string {
    if (totalPurchases >= 10000) {
      return `${customer} (๐Ÿ† Platinum)`;
    } else if (totalPurchases >= 5000) {
      return `${customer} (๐Ÿฅ‡ Gold)`;
    } else if (totalPurchases >= 1000) {
      return `${customer} (๐Ÿฅˆ Silver)`;
    }
    return customer;
  }
}

// ๐ŸŽฎ Test the processors
async function runDataProcessing() {
  // ๐Ÿ“ง Process emails
  const emailProcessor = new EmailProcessor();
  const rawEmails: RawEmail[] = [
    {
      from: '[email protected]',
      to: '[email protected]',
      subject: 'Urgent: System is down!',
      body: 'The system has been down for 2 hours. This is critical!',
      timestamp: '2023-11-20T10:00:00Z'
    },
    {
      from: '[email protected]',
      to: '[email protected]',
      subject: 'Great service',
      body: 'I really appreciate your excellent customer service. Thanks!',
      timestamp: '2023-11-20T11:00:00Z'
    },
    {
      from: 'spam@fake',  // Invalid email
      to: '[email protected]',
      subject: 'Free money! Click here!',
      body: 'You won! Act now for free money!',
      timestamp: '2023-11-20T12:00:00Z'
    }
  ];

  console.log('๐Ÿ“ง EMAIL PROCESSING ๐Ÿ“ง');
  const processedEmails = await emailProcessor.process(rawEmails);
  console.log('\nProcessed Emails:');
  processedEmails.forEach(email => {
    console.log(`  ๐Ÿ“ง ${email.subject} | ${email.priority} priority | ${email.sentiment} sentiment`);
  });

  // ๐Ÿ’ฐ Process sales data
  const salesProcessor = new SalesDataProcessor();
  const rawSales: RawSalesData[] = [
    {
      date: '2023-11-20',
      product: 'Gaming Laptop',
      quantity: '2',
      price: '1299.99',
      customer: 'Tech Corp'
    },
    {
      date: '2023-11-20',
      product: 'Office Chair',
      quantity: '10',
      price: '249.99',
      customer: 'Tech Corp'
    },
    {
      date: '2023-11-20',
      product: 'Notebook Set',
      quantity: 'invalid',  // Will be filtered
      price: '19.99',
      customer: 'School Supplies Inc'
    }
  ];

  console.log('\n\n๐Ÿ’ฐ SALES DATA PROCESSING ๐Ÿ’ฐ');
  const processedSales = await salesProcessor.process(rawSales);
  console.log('\nProcessed Sales:');
  processedSales.forEach(sale => {
    console.log(`  ๐Ÿ’ฐ ${sale.product} (${sale.category}) | ${sale.customer} | $${sale.totalAmount} | Profit: $${sale.profitMargin}`);
  });
}

// Run the demo
runDataProcessing();

๐Ÿš€ Advanced Concepts

๐Ÿง™โ€โ™‚๏ธ Advanced Topic 1: Abstract Class with Generics

Combine abstract classes with generics for maximum flexibility:

// ๐Ÿ—„๏ธ Abstract repository pattern
abstract class Repository<T, ID> {
  protected items: Map<ID, T> = new Map();
  
  // ๐ŸŽฏ Abstract methods for ID extraction
  protected abstract getId(item: T): ID;
  protected abstract validate(item: T): boolean;
  
  // โœ… Concrete CRUD operations
  create(item: T): T {
    if (!this.validate(item)) {
      throw new Error('Validation failed');
    }
    
    const id = this.getId(item);
    if (this.items.has(id)) {
      throw new Error(`Item with ID ${id} already exists`);
    }
    
    this.items.set(id, item);
    this.onItemCreated(item);
    return item;
  }
  
  read(id: ID): T | undefined {
    return this.items.get(id);
  }
  
  update(id: ID, updates: Partial<T>): T | undefined {
    const existing = this.items.get(id);
    if (!existing) return undefined;
    
    const updated = { ...existing, ...updates };
    if (!this.validate(updated)) {
      throw new Error('Update validation failed');
    }
    
    this.items.set(id, updated);
    this.onItemUpdated(updated, existing);
    return updated;
  }
  
  delete(id: ID): boolean {
    const item = this.items.get(id);
    if (!item) return false;
    
    this.items.delete(id);
    this.onItemDeleted(item);
    return true;
  }
  
  // ๐ŸŽฏ Hook methods (can be overridden)
  protected onItemCreated(item: T): void {
    console.log(`โž• Item created`);
  }
  
  protected onItemUpdated(newItem: T, oldItem: T): void {
    console.log(`๐Ÿ“ Item updated`);
  }
  
  protected onItemDeleted(item: T): void {
    console.log(`๐Ÿ—‘๏ธ Item deleted`);
  }
  
  // โœ… Query methods
  findAll(): T[] {
    return Array.from(this.items.values());
  }
  
  count(): number {
    return this.items.size;
  }
}

// ๐Ÿ‘ค User repository implementation
interface User {
  id: number;
  username: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

class UserRepository extends Repository<User, number> {
  private nextId = 1;
  
  protected getId(user: User): number {
    if (!user.id) {
      user.id = this.nextId++;
    }
    return user.id;
  }
  
  protected validate(user: User): boolean {
    // Username validation
    if (!user.username || user.username.length < 3) {
      console.log('โŒ Username must be at least 3 characters');
      return false;
    }
    
    // Email validation
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(user.email)) {
      console.log('โŒ Invalid email format');
      return false;
    }
    
    return true;
  }
  
  // Override hooks for user-specific logging
  protected onItemCreated(user: User): void {
    console.log(`โž• User created: ${user.username} (${user.role})`);
  }
  
  protected onItemUpdated(newUser: User, oldUser: User): void {
    console.log(`๐Ÿ“ User updated: ${oldUser.username} โ†’ ${newUser.username}`);
  }
  
  // User-specific methods
  findByEmail(email: string): User | undefined {
    return this.findAll().find(user => user.email === email);
  }
  
  findByRole(role: string): User[] {
    return this.findAll().filter(user => user.role === role);
  }
}

๐Ÿ—๏ธ Advanced Topic 2: Template Method Pattern

Using abstract classes to implement the Template Method design pattern:

// ๐Ÿณ Abstract recipe class demonstrating Template Method
abstract class Recipe {
  private name: string;
  protected servings: number;

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

  // ๐ŸŽฏ Template Method - defines the algorithm
  cook(): void {
    console.log(`\n๐Ÿณ Cooking ${this.name} for ${this.servings} servings\n`);
    
    this.gatherIngredients();
    this.prepareIngredients();
    
    if (this.needsMarinating()) {
      this.marinate();
    }
    
    this.performCooking();
    
    if (this.needsResting()) {
      this.rest();
    }
    
    this.plate();
    this.garnish();
    
    console.log(`\nโœ… ${this.name} is ready to serve! ๐Ÿฝ๏ธ\n`);
  }

  // ๐ŸŽฏ Abstract methods - must be implemented
  protected abstract gatherIngredients(): void;
  protected abstract prepareIngredients(): void;
  protected abstract performCooking(): void;
  protected abstract plate(): void;

  // ๐ŸŽฏ Hook methods - can be overridden
  protected needsMarinating(): boolean {
    return false; // Default: no marinating
  }

  protected marinate(): void {
    // Default empty implementation
  }

  protected needsResting(): boolean {
    return false; // Default: no resting
  }

  protected rest(): void {
    // Default empty implementation
  }

  protected garnish(): void {
    console.log('๐ŸŒฟ Adding final garnish');
  }
}

// ๐Ÿฅฉ Steak recipe implementation
class SteakRecipe extends Recipe {
  private steakType: string;
  private doneness: string;

  constructor(steakType: string = 'Ribeye', doneness: string = 'Medium-Rare') {
    super(`${doneness} ${steakType} Steak`);
    this.steakType = steakType;
    this.doneness = doneness;
  }

  protected gatherIngredients(): void {
    console.log('๐Ÿ“ฆ Gathering ingredients:');
    console.log('  - ๐Ÿฅฉ ' + this.steakType + ' steaks');
    console.log('  - ๐Ÿง‚ Salt and pepper');
    console.log('  - ๐Ÿงˆ Butter');
    console.log('  - ๐ŸŒฟ Fresh thyme');
    console.log('  - ๐Ÿง„ Garlic');
  }

  protected prepareIngredients(): void {
    console.log('๐Ÿ”ช Preparing ingredients:');
    console.log('  - Let steak reach room temperature');
    console.log('  - Season generously with salt and pepper');
    console.log('  - Crush garlic cloves');
  }

  protected needsMarinating(): boolean {
    return true; // Steaks benefit from marinating
  }

  protected marinate(): void {
    console.log('โฐ Marinating steak for 30 minutes...');
  }

  protected performCooking(): void {
    console.log('๐Ÿ”ฅ Cooking the steak:');
    console.log('  - Heat cast iron skillet to high heat');
    console.log('  - Sear steak 3-4 minutes per side');
    console.log('  - Add butter, thyme, and garlic');
    console.log('  - Baste continuously');
    console.log(`  - Cook to ${this.doneness} (internal temp: ${this.getTargetTemp()}ยฐF)`);
  }

  protected needsResting(): boolean {
    return true; // Steak must rest
  }

  protected rest(): void {
    console.log('โฑ๏ธ Resting steak for 5-10 minutes...');
  }

  protected plate(): void {
    console.log('๐Ÿฝ๏ธ Plating:');
    console.log('  - Slice against the grain');
    console.log('  - Arrange on warm plate');
    console.log('  - Drizzle with pan juices');
  }

  private getTargetTemp(): number {
    const temps: Record<string, number> = {
      'Rare': 125,
      'Medium-Rare': 135,
      'Medium': 145,
      'Medium-Well': 150,
      'Well-Done': 160
    };
    return temps[this.doneness] || 135;
  }
}

// ๐Ÿ Pasta recipe implementation
class PastaRecipe extends Recipe {
  private pastaType: string;
  private sauce: string;

  constructor(pastaType: string = 'Spaghetti', sauce: string = 'Carbonara') {
    super(`${pastaType} ${sauce}`);
    this.pastaType = pastaType;
    this.sauce = sauce;
  }

  protected gatherIngredients(): void {
    console.log('๐Ÿ“ฆ Gathering ingredients:');
    console.log(`  - ๐Ÿ ${this.pastaType}`);
    console.log('  - ๐Ÿฅ“ Pancetta or guanciale');
    console.log('  - ๐Ÿฅš Fresh eggs');
    console.log('  - ๐Ÿง€ Pecorino Romano');
    console.log('  - ๐Ÿง‚ Salt and black pepper');
  }

  protected prepareIngredients(): void {
    console.log('๐Ÿ”ช Preparing ingredients:');
    console.log('  - Dice pancetta into small cubes');
    console.log('  - Grate Pecorino Romano');
    console.log('  - Whisk eggs with cheese');
    console.log('  - Grind fresh black pepper');
  }

  protected performCooking(): void {
    console.log('๐Ÿ”ฅ Cooking:');
    console.log('  - Boil salted water for pasta');
    console.log('  - Cook pancetta until crispy');
    console.log(`  - Cook ${this.pastaType} al dente`);
    console.log('  - Reserve pasta water');
    console.log('  - Combine pasta with pancetta');
    console.log('  - Remove from heat, add egg mixture');
    console.log('  - Toss vigorously, adding pasta water');
  }

  protected plate(): void {
    console.log('๐Ÿฝ๏ธ Plating:');
    console.log('  - Twirl pasta onto warm plates');
    console.log('  - Extra grated cheese on top');
    console.log('  - Fresh cracked black pepper');
  }

  protected garnish(): void {
    console.log('๐ŸŒฟ Adding fresh parsley and extra cheese');
  }
}

// ๐Ÿณ Cooking demonstration
const steakDinner = new SteakRecipe('Filet Mignon', 'Medium');
const pastaLunch = new PastaRecipe('Fettuccine', 'Alfredo');

steakDinner.cook();
pastaLunch.cook();

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Trying to Instantiate Abstract Classes

abstract class Vehicle {
  abstract drive(): void;
}

// โŒ Wrong - Cannot instantiate abstract class!
const vehicle = new Vehicle(); // ๐Ÿ’ฅ Error!

// โœ… Correct - Create a concrete subclass
class Car extends Vehicle {
  drive(): void {
    console.log('๐Ÿš— Driving on the road');
  }
}

const car = new Car(); // โœ… This works!

๐Ÿคฏ Pitfall 2: Forgetting to Implement Abstract Methods

abstract class Animal {
  abstract makeSound(): void;
  abstract move(): void;
}

// โŒ Wrong - Missing implementation!
class Bird extends Animal {
  makeSound(): void {
    console.log('๐Ÿฆ Tweet tweet!');
  }
  // Missing move() implementation! ๐Ÿ’ฅ Error!
}

// โœ… Correct - Implement ALL abstract methods
class Parrot extends Animal {
  makeSound(): void {
    console.log('๐Ÿฆœ Squawk!');
  }
  
  move(): void {
    console.log('๐Ÿฆœ Flying through the air');
  }
}

๐Ÿ”„ Pitfall 3: Abstract Properties Without Proper Implementation

abstract class Product {
  abstract price: number; // Abstract property
  abstract readonly category: string; // Abstract readonly property
}

// โŒ Wrong - Properties not properly initialized
class Book extends Product {
  // Properties declared but not initialized! ๐Ÿ’ฅ
  price: number;
  readonly category: string;
}

// โœ… Correct - Initialize abstract properties
class Novel extends Product {
  price: number = 19.99; // โœ… Initialized
  readonly category: string = 'Fiction'; // โœ… Initialized
  
  // Or initialize in constructor
  constructor(price: number) {
    super();
    this.price = price;
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Keep Abstract Classes Focused: One clear purpose per abstract class
  2. ๐Ÿ“ Document Abstract Methods: Explain what implementations should do
  3. ๐Ÿ”„ Use Template Method Pattern: Define algorithms in abstract classes
  4. ๐Ÿ›ก๏ธ Validate in Abstract Classes: Put common validation in the base
  5. โœจ Provide Useful Defaults: Implement common behavior in base class

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Plugin System

Create an abstract plugin system for a text editor:

๐Ÿ“‹ Requirements:

  • โœ… Abstract Plugin class with lifecycle methods
  • ๐Ÿ”ง SpellCheckPlugin that checks spelling
  • ๐ŸŽจ FormatterPlugin that formats code
  • ๐Ÿ“Š StatsPlugin that tracks document statistics
  • ๐Ÿ”„ Plugin manager to handle all plugins

๐Ÿš€ Bonus Points:

  • Add plugin dependencies
  • Implement plugin configuration
  • Create a plugin marketplace simulation

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ”Œ Abstract plugin base class
abstract class Plugin {
  protected name: string;
  protected version: string;
  protected enabled: boolean = false;
  protected config: Map<string, any> = new Map();

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

  // ๐ŸŽฏ Plugin lifecycle - Template Method
  async initialize(): Promise<void> {
    console.log(`๐Ÿ”Œ Initializing ${this.name} v${this.version}...`);
    
    // 1๏ธโƒฃ Check dependencies
    if (!this.checkDependencies()) {
      throw new Error(`Dependencies not met for ${this.name}`);
    }
    
    // 2๏ธโƒฃ Load configuration
    await this.loadConfig();
    
    // 3๏ธโƒฃ Plugin-specific setup
    await this.setup();
    
    // 4๏ธโƒฃ Register hooks
    this.registerHooks();
    
    this.enabled = true;
    console.log(`โœ… ${this.name} initialized successfully!`);
  }

  // ๐ŸŽฏ Abstract methods
  protected abstract checkDependencies(): boolean;
  protected abstract setup(): Promise<void>;
  protected abstract registerHooks(): void;
  abstract processText(text: string): string;
  abstract getCommands(): Command[];

  // โœ… Concrete methods
  async loadConfig(): Promise<void> {
    // Default config loading
    console.log(`โš™๏ธ Loading config for ${this.name}`);
  }

  enable(): void {
    if (!this.enabled) {
      this.enabled = true;
      this.onEnabled();
      console.log(`โœ… ${this.name} enabled`);
    }
  }

  disable(): void {
    if (this.enabled) {
      this.enabled = false;
      this.onDisabled();
      console.log(`โธ๏ธ ${this.name} disabled`);
    }
  }

  // ๐ŸŽฏ Hook methods
  protected onEnabled(): void {
    // Override in subclasses if needed
  }

  protected onDisabled(): void {
    // Override in subclasses if needed
  }

  getInfo(): PluginInfo {
    return {
      name: this.name,
      version: this.version,
      enabled: this.enabled,
      commands: this.getCommands().length
    };
  }
}

// ๐Ÿ“ Supporting types
interface Command {
  name: string;
  shortcut?: string;
  execute: () => void;
}

interface PluginInfo {
  name: string;
  version: string;
  enabled: boolean;
  commands: number;
}

// ๐Ÿ” Spell Check Plugin
class SpellCheckPlugin extends Plugin {
  private dictionary: Set<string> = new Set();
  private customWords: Set<string> = new Set();

  constructor() {
    super('Spell Checker', '1.0.0');
  }

  protected checkDependencies(): boolean {
    // No external dependencies
    return true;
  }

  protected async setup(): Promise<void> {
    // Load dictionary
    this.dictionary = new Set([
      'the', 'quick', 'brown', 'fox', 'jumps', 'over', 'lazy', 'dog',
      'typescript', 'javascript', 'programming', 'code', 'function'
    ]);
    console.log(`๐Ÿ“š Loaded dictionary with ${this.dictionary.size} words`);
  }

  protected registerHooks(): void {
    console.log('๐Ÿช Registered spell check hooks');
  }

  processText(text: string): string {
    const words = text.split(/\s+/);
    const misspelled: string[] = [];

    words.forEach(word => {
      const cleanWord = word.toLowerCase().replace(/[^a-z]/g, '');
      if (cleanWord && !this.dictionary.has(cleanWord) && !this.customWords.has(cleanWord)) {
        misspelled.push(word);
      }
    });

    if (misspelled.length > 0) {
      console.log(`๐Ÿ” Found ${misspelled.length} misspelled words: ${misspelled.join(', ')}`);
      // In real implementation, would underline these words
      return text + `\n[Misspelled: ${misspelled.join(', ')}]`;
    }

    return text;
  }

  getCommands(): Command[] {
    return [
      {
        name: 'Check Spelling',
        shortcut: 'Ctrl+F7',
        execute: () => console.log('๐Ÿ” Checking spelling...')
      },
      {
        name: 'Add to Dictionary',
        shortcut: 'Ctrl+Shift+A',
        execute: () => console.log('๐Ÿ“š Adding word to dictionary...')
      }
    ];
  }

  addCustomWord(word: string): void {
    this.customWords.add(word.toLowerCase());
    console.log(`โž• Added "${word}" to custom dictionary`);
  }
}

// ๐ŸŽจ Code Formatter Plugin
class FormatterPlugin extends Plugin {
  private indentSize: number = 2;
  private useTabs: boolean = false;

  constructor() {
    super('Code Formatter', '2.0.0');
  }

  protected checkDependencies(): boolean {
    // Check if prettier is available (simulated)
    return true;
  }

  protected async setup(): Promise<void> {
    this.config.set('indentSize', this.indentSize);
    this.config.set('useTabs', this.useTabs);
    console.log(`๐ŸŽจ Formatter configured: ${this.indentSize} ${this.useTabs ? 'tabs' : 'spaces'}`);
  }

  protected registerHooks(): void {
    console.log('๐Ÿช Registered format on save hook');
  }

  processText(text: string): string {
    // Simple formatting simulation
    const lines = text.split('\n');
    let formattedLines: string[] = [];
    let indentLevel = 0;

    lines.forEach(line => {
      const trimmed = line.trim();
      
      // Decrease indent for closing braces
      if (trimmed.startsWith('}') || trimmed.startsWith(']') || trimmed.startsWith(')')) {
        indentLevel = Math.max(0, indentLevel - 1);
      }

      // Apply indentation
      const indent = this.useTabs 
        ? '\t'.repeat(indentLevel)
        : ' '.repeat(indentLevel * this.indentSize);
      
      formattedLines.push(indent + trimmed);

      // Increase indent for opening braces
      if (trimmed.endsWith('{') || trimmed.endsWith('[') || trimmed.endsWith('(')) {
        indentLevel++;
      }
    });

    console.log('๐ŸŽจ Code formatted successfully');
    return formattedLines.join('\n');
  }

  getCommands(): Command[] {
    return [
      {
        name: 'Format Document',
        shortcut: 'Shift+Alt+F',
        execute: () => console.log('๐ŸŽจ Formatting document...')
      },
      {
        name: 'Format Selection',
        shortcut: 'Ctrl+K Ctrl+F',
        execute: () => console.log('๐ŸŽจ Formatting selection...')
      }
    ];
  }

  setIndentSize(size: number): void {
    this.indentSize = size;
    this.config.set('indentSize', size);
    console.log(`โš™๏ธ Indent size set to ${size}`);
  }
}

// ๐Ÿ“Š Statistics Plugin
class StatsPlugin extends Plugin {
  private wordCount: number = 0;
  private charCount: number = 0;
  private lineCount: number = 0;
  private lastProcessed: Date | null = null;

  constructor() {
    super('Document Stats', '1.5.0');
  }

  protected checkDependencies(): boolean {
    return true;
  }

  protected async setup(): Promise<void> {
    console.log('๐Ÿ“Š Stats tracker ready');
  }

  protected registerHooks(): void {
    console.log('๐Ÿช Registered text change listener');
  }

  processText(text: string): string {
    // Calculate statistics
    this.charCount = text.length;
    this.wordCount = text.split(/\s+/).filter(word => word.length > 0).length;
    this.lineCount = text.split('\n').length;
    this.lastProcessed = new Date();

    // Don't modify the text, just analyze
    return text;
  }

  getCommands(): Command[] {
    return [
      {
        name: 'Show Statistics',
        shortcut: 'Ctrl+Shift+I',
        execute: () => this.showStats()
      },
      {
        name: 'Export Stats',
        execute: () => this.exportStats()
      }
    ];
  }

  showStats(): void {
    console.log('๐Ÿ“Š Document Statistics:');
    console.log(`   ๐Ÿ“ Words: ${this.wordCount}`);
    console.log(`   ๐Ÿ”ค Characters: ${this.charCount}`);
    console.log(`   ๐Ÿ“„ Lines: ${this.lineCount}`);
    console.log(`   โฐ Last updated: ${this.lastProcessed?.toLocaleTimeString() || 'Never'}`);
  }

  exportStats(): void {
    const stats = {
      wordCount: this.wordCount,
      charCount: this.charCount,
      lineCount: this.lineCount,
      lastProcessed: this.lastProcessed
    };
    console.log('๐Ÿ’พ Exporting stats:', JSON.stringify(stats, null, 2));
  }

  protected onEnabled(): void {
    console.log('๐Ÿ“Š Stats tracking started');
  }

  protected onDisabled(): void {
    console.log('๐Ÿ“Š Stats tracking paused');
  }
}

// ๐Ÿ”Œ Plugin Manager
class PluginManager {
  private plugins: Map<string, Plugin> = new Map();
  private loadOrder: string[] = [];

  async registerPlugin(plugin: Plugin): Promise<void> {
    const info = plugin.getInfo();
    
    if (this.plugins.has(info.name)) {
      console.log(`โš ๏ธ Plugin ${info.name} already registered`);
      return;
    }

    try {
      await plugin.initialize();
      this.plugins.set(info.name, plugin);
      this.loadOrder.push(info.name);
      console.log(`โœ… Registered plugin: ${info.name}`);
    } catch (error) {
      console.error(`โŒ Failed to register ${info.name}: ${error}`);
    }
  }

  getPlugin(name: string): Plugin | undefined {
    return this.plugins.get(name);
  }

  processAllPlugins(text: string): string {
    let processedText = text;

    for (const pluginName of this.loadOrder) {
      const plugin = this.plugins.get(pluginName);
      if (plugin && plugin.getInfo().enabled) {
        processedText = plugin.processText(processedText);
      }
    }

    return processedText;
  }

  listPlugins(): void {
    console.log('\n๐Ÿ”Œ Installed Plugins:');
    this.plugins.forEach(plugin => {
      const info = plugin.getInfo();
      const status = info.enabled ? 'โœ…' : 'โธ๏ธ';
      console.log(`${status} ${info.name} v${info.version} (${info.commands} commands)`);
    });
  }

  showCommands(): void {
    console.log('\nโŒจ๏ธ Available Commands:');
    this.plugins.forEach(plugin => {
      if (plugin.getInfo().enabled) {
        console.log(`\n${plugin.getInfo().name}:`);
        plugin.getCommands().forEach(cmd => {
          const shortcut = cmd.shortcut ? ` (${cmd.shortcut})` : '';
          console.log(`  โ€ข ${cmd.name}${shortcut}`);
        });
      }
    });
  }
}

// ๐ŸŽฎ Demo the plugin system
async function demoPluginSystem() {
  const manager = new PluginManager();

  // Register plugins
  const spellChecker = new SpellCheckPlugin();
  const formatter = new FormatterPlugin();
  const stats = new StatsPlugin();

  await manager.registerPlugin(spellChecker);
  await manager.registerPlugin(formatter);
  await manager.registerPlugin(stats);

  // Enable plugins
  spellChecker.enable();
  formatter.enable();
  stats.enable();

  // List plugins
  manager.listPlugins();
  manager.showCommands();

  // Process some text
  const sampleText = `function hello() {
console.log("Helo wrold!");
return true;
}`;

  console.log('\n๐Ÿ“ Original text:');
  console.log(sampleText);

  console.log('\n๐Ÿ”„ Processing text through all plugins...');
  const processed = manager.processAllPlugins(sampleText);
  
  console.log('\n๐Ÿ“ Processed text:');
  console.log(processed);

  // Show stats
  const statsPlugin = manager.getPlugin('Document Stats') as StatsPlugin;
  statsPlugin.showStats();

  // Add custom word to spell checker
  const spellPlugin = manager.getPlugin('Spell Checker') as SpellCheckPlugin;
  spellPlugin.addCustomWord('wrold'); // Add misspelling as custom word

  // Configure formatter
  const formatPlugin = manager.getPlugin('Code Formatter') as FormatterPlugin;
  formatPlugin.setIndentSize(4);
}

// Run the demo
demoPluginSystem();

๐ŸŽ“ Key Takeaways

Youโ€™ve mastered abstract classes! Hereโ€™s what you can now do:

  • โœ… Create abstract base classes that enforce contracts ๐Ÿ—๏ธ
  • โœ… Define abstract methods that subclasses must implement ๐ŸŽฏ
  • โœ… Share common functionality across class hierarchies โ™ป๏ธ
  • โœ… Use Template Method pattern for algorithm structures ๐ŸŽจ
  • โœ… Combine abstractions with generics for flexibility ๐Ÿš€

Remember: Abstract classes are blueprints - they define what must be built, not how! ๐Ÿ›๏ธ

๐Ÿค Next Steps

Congratulations! ๐ŸŽ‰ Youโ€™ve mastered abstract classes in TypeScript!

Hereโ€™s what to do next:

  1. ๐Ÿ’ป Practice with the plugin system exercise above
  2. ๐Ÿ—๏ธ Design your own abstract class hierarchies
  3. ๐Ÿ“š Move on to our next tutorial: Protected Constructor Pattern: Controlling Instantiation
  4. ๐ŸŒŸ Experiment with combining abstract classes, generics, and interfaces!

Remember: Every great framework starts with well-designed abstract classes. Keep building, keep abstracting, and create amazing architectures! ๐Ÿš€


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