+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 26 of 355

๐Ÿ— ๏ธ Classes in TypeScript: Object-Oriented Programming Basics

Master TypeScript classes and object-oriented programming with practical examples, inheritance patterns, and best practices ๐Ÿš€

๐ŸŒฑBeginner
25 min read

Prerequisites

  • Basic TypeScript types and interfaces ๐Ÿ“
  • Understanding of functions and objects ๐Ÿ”ง
  • Familiarity with ES6 syntax ๐Ÿ’ก

What you'll learn

  • Create and instantiate TypeScript classes ๐ŸŽฏ
  • Understand constructors and class methods ๐Ÿ› ๏ธ
  • Apply access modifiers for encapsulation ๐Ÿ”’
  • Build real-world applications with classes ๐Ÿ—๏ธ

๐ŸŽฏ Introduction

Welcome to the exciting world of Object-Oriented Programming with TypeScript classes! ๐ŸŽ‰ Think of classes as blueprints for creating objects - like a cookie cutter that shapes delicious cookies! ๐Ÿช

Classes help you organize your code into reusable, structured components that model real-world entities. Whether youโ€™re building a game ๐ŸŽฎ, an e-commerce app ๐Ÿ›’, or a social media platform ๐Ÿ“ฑ, classes are your best friend for creating maintainable code.

By the end of this tutorial, youโ€™ll be confidently creating classes that make your fellow developers say โ€œWow, this code is so clean and organized!โ€ ๐ŸŒŸ

Letโ€™s dive into the wonderful world of TypeScript classes! ๐ŸŠโ€โ™‚๏ธ

๐Ÿ“š Understanding Classes

๐Ÿค” What are Classes?

Classes are like templates or blueprints for creating objects ๐Ÿ—๏ธ. Think of them as:

  • ๐Ÿช Cookie cutters: The class is the cutter, objects are the cookies
  • ๐Ÿ  House blueprints: The class is the blueprint, instances are actual houses
  • ๐Ÿš— Car factory: The class defines how to make cars, instances are individual cars

In TypeScript, classes combine:

  • ๐Ÿ“Š Data (properties) - what the object has
  • โšก Behavior (methods) - what the object can do
  • ๐Ÿ”’ Encapsulation - controlling access to data

๐Ÿ’ก Why Use Classes?

Hereโ€™s why developers love classes:

  1. Code Organization ๐Ÿ“: Group related data and functions together
  2. Reusability โ™ป๏ธ: Create multiple instances from one blueprint
  3. Encapsulation ๐Ÿ”’: Hide internal details, expose clean interfaces
  4. Inheritance ๐Ÿ‘ฅ: Share common functionality between related classes
  5. Type Safety ๐Ÿ›ก๏ธ: TypeScript ensures your classes are used correctly

Real-world example: A User class can represent users in your app, with properties like name and email, and methods like login() and updateProfile() ๐Ÿ‘ค.

๐Ÿ”ง Basic Class Syntax

๐Ÿ“ Your First Class

Letโ€™s create a simple class to understand the basics:

// ๐ŸŽฎ A simple Player class for a game
class Player {
  // ๐Ÿ“Š Properties (what the player has)
  name: string;
  level: number;
  health: number;
  
  // ๐Ÿ—๏ธ Constructor (how to create a player)
  constructor(name: string) {
    this.name = name;
    this.level = 1;        // ๐ŸŒŸ Starting level
    this.health = 100;     // โค๏ธ Full health to start
  }
  
  // โšก Methods (what the player can do)
  levelUp(): void {
    this.level++;
    this.health = 100;     // ๐Ÿฉน Full heal on level up!
    console.log(`๐ŸŽ‰ ${this.name} reached level ${this.level}!`);
  }
  
  takeDamage(damage: number): void {
    this.health -= damage;
    console.log(`๐Ÿ’ฅ ${this.name} took ${damage} damage! Health: ${this.health}`);
    
    if (this.health <= 0) {
      console.log(`๐Ÿ’€ ${this.name} has been defeated!`);
    }
  }
  
  // ๐Ÿ“Š Getter method
  getStatus(): string {
    return `๐Ÿ‘ค ${this.name} - Level ${this.level} (${this.health}โค๏ธ)`;
  }
}

// ๐Ÿš€ Creating instances (actual players)
const hero = new Player("Aragorn");
const mage = new Player("Gandalf");

console.log(hero.getStatus());  // ๐Ÿ‘ค Aragorn - Level 1 (100โค๏ธ)
hero.levelUp();                 // ๐ŸŽ‰ Aragorn reached level 2!
hero.takeDamage(30);           // ๐Ÿ’ฅ Aragorn took 30 damage! Health: 70

๐Ÿ’ก Key Concepts:

  • class keyword defines the blueprint
  • constructor() runs when creating new instances
  • this refers to the current instance
  • Properties store data, methods define behavior

๐Ÿ’ก Practical Examples

๐Ÿ›’ Example 1: Shopping Cart System

Letโ€™s build a real-world shopping cart:

// ๐Ÿ›๏ธ Product interface for type safety
interface Product {
  id: string;
  name: string;
  price: number;
  emoji: string;
}

// ๐Ÿ“ Cart item with quantity
interface CartItem {
  product: Product;
  quantity: number;
}

// ๐Ÿ›’ Shopping Cart class
class ShoppingCart {
  // ๐Ÿ“Š Private properties (internal data)
  private items: CartItem[] = [];
  private customerName: string;
  
  // ๐Ÿ—๏ธ Constructor
  constructor(customerName: string) {
    this.customerName = customerName;
    console.log(`๐Ÿ›’ New cart created for ${customerName}!`);
  }
  
  // โž• Add item to cart
  addItem(product: Product, quantity: number = 1): void {
    // ๐Ÿ” Check if item already exists
    const existingItem = this.items.find(item => item.product.id === product.id);
    
    if (existingItem) {
      existingItem.quantity += quantity;
      console.log(`๐Ÿ“ˆ Updated ${product.emoji} ${product.name} quantity to ${existingItem.quantity}`);
    } else {
      this.items.push({ product, quantity });
      console.log(`โœ… Added ${quantity}x ${product.emoji} ${product.name} to cart`);
    }
  }
  
  // โž– Remove item from cart
  removeItem(productId: string): void {
    const index = this.items.findIndex(item => item.product.id === productId);
    
    if (index !== -1) {
      const removedItem = this.items.splice(index, 1)[0];
      console.log(`๐Ÿ—‘๏ธ Removed ${removedItem.product.emoji} ${removedItem.product.name} from cart`);
    } else {
      console.log(`โŒ Product not found in cart`);
    }
  }
  
  // ๐Ÿ’ฐ Calculate total price
  getTotal(): number {
    return this.items.reduce((total, item) => {
      return total + (item.product.price * item.quantity);
    }, 0);
  }
  
  // ๐Ÿ“‹ Display cart contents
  displayCart(): void {
    console.log(`\n๐Ÿ›’ ${this.customerName}'s Cart:`);
    console.log('='.repeat(30));
    
    if (this.items.length === 0) {
      console.log('๐Ÿ“ญ Your cart is empty');
      return;
    }
    
    this.items.forEach(item => {
      const total = item.product.price * item.quantity;
      console.log(`${item.product.emoji} ${item.product.name} x${item.quantity} - $${total.toFixed(2)}`);
    });
    
    console.log('โ”€'.repeat(30));
    console.log(`๐Ÿ’ฐ Total: $${this.getTotal().toFixed(2)}`);
  }
  
  // ๐Ÿงน Clear cart
  clearCart(): void {
    this.items = [];
    console.log(`๐Ÿงน Cart cleared for ${this.customerName}`);
  }
  
  // ๐Ÿš€ Checkout process
  checkout(): void {
    if (this.items.length === 0) {
      console.log('โŒ Cannot checkout empty cart');
      return;
    }
    
    console.log(`\n๐ŸŽ‰ Processing checkout for ${this.customerName}...`);
    this.displayCart();
    console.log(`โœ… Order placed successfully! Thank you, ${this.customerName}! ๐Ÿ™`);
    this.clearCart();
  }
}

// ๐Ÿ›๏ธ Sample products
const products: Product[] = [
  { id: '1', name: 'TypeScript Book', price: 29.99, emoji: '๐Ÿ“˜' },
  { id: '2', name: 'Mechanical Keyboard', price: 149.99, emoji: 'โŒจ๏ธ' },
  { id: '3', name: 'Coffee Mug', price: 12.99, emoji: 'โ˜•' },
  { id: '4', name: 'Stress Ball', price: 7.99, emoji: '๐ŸŸก' }
];

// ๐Ÿš€ Let's go shopping!
const cart = new ShoppingCart("Alice");

cart.addItem(products[0], 2);     // 2 books
cart.addItem(products[1]);        // 1 keyboard
cart.addItem(products[2], 3);     // 3 mugs

cart.displayCart();
cart.removeItem('2');             // Remove keyboard
cart.displayCart();
cart.checkout();

๐ŸŽต Example 2: Music Player Class

Letโ€™s create a feature-rich music player:

// ๐ŸŽต Song interface
interface Song {
  id: string;
  title: string;
  artist: string;
  duration: number; // in seconds
  genre: string;
  emoji: string;
}

// ๐ŸŽถ Music Player class
class MusicPlayer {
  private playlist: Song[] = [];
  private currentSongIndex: number = -1;
  private isPlaying: boolean = false;
  private volume: number = 50; // 0-100
  private shuffleMode: boolean = false;
  private repeatMode: 'none' | 'one' | 'all' = 'none';
  
  // ๐Ÿ“Š Player stats
  private totalPlayTime: number = 0;
  
  constructor() {
    console.log('๐ŸŽต Music Player initialized! Ready to rock! ๐ŸŽธ');
  }
  
  // โž• Add song to playlist
  addSong(song: Song): void {
    this.playlist.push(song);
    console.log(`โœ… Added ${song.emoji} "${song.title}" by ${song.artist}`);
  }
  
  // โ–ถ๏ธ Play current song
  play(): void {
    if (this.playlist.length === 0) {
      console.log('โŒ No songs in playlist! Add some music first ๐ŸŽต');
      return;
    }
    
    if (this.currentSongIndex === -1) {
      this.currentSongIndex = 0;
    }
    
    this.isPlaying = true;
    const song = this.playlist[this.currentSongIndex];
    console.log(`โ–ถ๏ธ Now playing: ${song.emoji} "${song.title}" by ${song.artist}`);
    this.displayProgress();
  }
  
  // โธ๏ธ Pause playback
  pause(): void {
    if (this.isPlaying) {
      this.isPlaying = false;
      console.log('โธ๏ธ Playback paused');
    } else {
      console.log('โŒ Music is not playing');
    }
  }
  
  // โน๏ธ Stop playback
  stop(): void {
    this.isPlaying = false;
    console.log('โน๏ธ Playback stopped');
  }
  
  // โญ๏ธ Next song
  next(): void {
    if (this.playlist.length === 0) return;
    
    if (this.shuffleMode) {
      this.currentSongIndex = Math.floor(Math.random() * this.playlist.length);
    } else {
      this.currentSongIndex = (this.currentSongIndex + 1) % this.playlist.length;
    }
    
    if (this.isPlaying) {
      this.play();
    }
  }
  
  // โฎ๏ธ Previous song
  previous(): void {
    if (this.playlist.length === 0) return;
    
    this.currentSongIndex = this.currentSongIndex <= 0 
      ? this.playlist.length - 1 
      : this.currentSongIndex - 1;
    
    if (this.isPlaying) {
      this.play();
    }
  }
  
  // ๐Ÿ”€ Toggle shuffle
  toggleShuffle(): void {
    this.shuffleMode = !this.shuffleMode;
    const status = this.shuffleMode ? 'ON' : 'OFF';
    console.log(`๐Ÿ”€ Shuffle mode: ${status}`);
  }
  
  // ๐Ÿ” Cycle repeat mode
  cycleRepeat(): void {
    const modes: ('none' | 'one' | 'all')[] = ['none', 'one', 'all'];
    const currentIndex = modes.indexOf(this.repeatMode);
    const nextIndex = (currentIndex + 1) % modes.length;
    this.repeatMode = modes[nextIndex];
    
    const emojis = { none: 'โžก๏ธ', one: '๐Ÿ”‚', all: '๐Ÿ”' };
    console.log(`${emojis[this.repeatMode]} Repeat mode: ${this.repeatMode}`);
  }
  
  // ๐Ÿ”Š Set volume
  setVolume(level: number): void {
    this.volume = Math.max(0, Math.min(100, level));
    const volumeBar = 'โ–ˆ'.repeat(Math.floor(this.volume / 10));
    console.log(`๐Ÿ”Š Volume: ${this.volume}% [${volumeBar}]`);
  }
  
  // ๐Ÿ“Š Display current status
  displayStatus(): void {
    console.log('\n๐ŸŽต Music Player Status');
    console.log('='.repeat(25));
    
    if (this.currentSongIndex >= 0) {
      const song = this.playlist[this.currentSongIndex];
      const status = this.isPlaying ? 'โ–ถ๏ธ Playing' : 'โธ๏ธ Paused';
      console.log(`${status}: ${song.emoji} "${song.title}"`);
      console.log(`๐ŸŽค Artist: ${song.artist}`);
      console.log(`๐ŸŽญ Genre: ${song.genre}`);
    }
    
    console.log(`๐Ÿ“š Playlist: ${this.playlist.length} songs`);
    console.log(`๐Ÿ”Š Volume: ${this.volume}%`);
    console.log(`๐Ÿ”€ Shuffle: ${this.shuffleMode ? 'ON' : 'OFF'}`);
    console.log(`๐Ÿ” Repeat: ${this.repeatMode}`);
  }
  
  // ๐Ÿ“‹ Show playlist
  showPlaylist(): void {
    console.log('\n๐Ÿ“š Current Playlist:');
    console.log('='.repeat(30));
    
    this.playlist.forEach((song, index) => {
      const current = index === this.currentSongIndex ? 'โ–ถ๏ธ ' : '   ';
      const duration = `${Math.floor(song.duration / 60)}:${(song.duration % 60).toString().padStart(2, '0')}`;
      console.log(`${current}${song.emoji} "${song.title}" - ${song.artist} (${duration})`);
    });
  }
  
  // ๐Ÿ“Š Private helper method
  private displayProgress(): void {
    console.log('๐ŸŽต โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ 100%');
  }
}

// ๐ŸŽต Sample songs
const songs: Song[] = [
  { id: '1', title: 'Bohemian Rhapsody', artist: 'Queen', duration: 355, genre: 'Rock', emoji: '๐Ÿ‘‘' },
  { id: '2', title: 'Billie Jean', artist: 'Michael Jackson', duration: 294, genre: 'Pop', emoji: '๐Ÿ•บ' },
  { id: '3', title: 'Hotel California', artist: 'Eagles', duration: 391, genre: 'Rock', emoji: '๐Ÿจ' },
  { id: '4', title: 'Shape of You', artist: 'Ed Sheeran', duration: 234, genre: 'Pop', emoji: '๐Ÿ’ƒ' }
];

// ๐Ÿš€ Let's play some music!
const player = new MusicPlayer();

// Add songs to playlist
songs.forEach(song => player.addSong(song));

// Control playback
player.showPlaylist();
player.play();
player.setVolume(75);
player.toggleShuffle();
player.next();
player.displayStatus();

๐Ÿš€ Advanced Class Features

๐Ÿ”’ Access Modifiers

Control who can access your class members:

class BankAccount {
  public accountHolder: string;     // โœ… Accessible everywhere
  private balance: number;          // ๐Ÿ”’ Only within this class
  protected accountNumber: string;  // ๐Ÿ‘ฅ This class and subclasses
  
  constructor(holder: string, initialBalance: number) {
    this.accountHolder = holder;
    this.balance = initialBalance;
    this.accountNumber = this.generateAccountNumber();
  }
  
  // ๐Ÿ”’ Private method - internal use only
  private generateAccountNumber(): string {
    return 'ACC-' + Math.random().toString(36).substr(2, 9).toUpperCase();
  }
  
  // โœ… Public method - safe interface
  public deposit(amount: number): void {
    if (amount > 0) {
      this.balance += amount;
      console.log(`๐Ÿ’ฐ Deposited $${amount}. New balance: $${this.balance}`);
    }
  }
  
  // โœ… Public method with validation
  public withdraw(amount: number): boolean {
    if (amount > 0 && amount <= this.balance) {
      this.balance -= amount;
      console.log(`๐Ÿ’ธ Withdrew $${amount}. Remaining balance: $${this.balance}`);
      return true;
    } else {
      console.log('โŒ Insufficient funds or invalid amount');
      return false;
    }
  }
  
  // โœ… Safe way to check balance
  public getBalance(): number {
    return this.balance;
  }
}

const account = new BankAccount("John Doe", 1000);
account.deposit(500);           // โœ… Works
console.log(account.getBalance()); // โœ… Works
// console.log(account.balance);   // โŒ Error: private property

๐Ÿ—๏ธ Static Members

Class-level properties and methods:

class MathUtils {
  // ๐Ÿ“Š Static properties - shared by all instances
  static readonly PI = 3.14159;
  static readonly E = 2.71828;
  
  // ๐Ÿ”ง Static methods - utility functions
  static add(a: number, b: number): number {
    return a + b;
  }
  
  static multiply(a: number, b: number): number {
    return a * b;
  }
  
  static circleArea(radius: number): number {
    return MathUtils.PI * radius * radius;
  }
  
  // ๐Ÿ“ˆ Statistics helper
  static average(numbers: number[]): number {
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((total, num) => total + num, 0);
    return sum / numbers.length;
  }
}

// ๐Ÿš€ Use static methods without creating instances
console.log(`๐Ÿ“ Circle area: ${MathUtils.circleArea(5)}`);
console.log(`๐Ÿ“Š Average: ${MathUtils.average([1, 2, 3, 4, 5])}`);
console.log(`๐Ÿฅง Pi value: ${MathUtils.PI}`);

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Forgetting โ€˜thisโ€™ keyword

class Counter {
  private count: number = 0;
  
  // โŒ Wrong - missing 'this'
  incrementWrong(): void {
    count++; // โŒ Error: 'count' is not defined
  }
  
  // โœ… Correct - using 'this'
  increment(): void {
    this.count++; // โœ… Works!
    console.log(`Count: ${this.count}`);
  }
}

๐Ÿคฏ Pitfall 2: Not initializing properties

class Student {
  name: string;      // โŒ Not initialized
  age: number;       // โŒ Not initialized
  grades: number[] = []; // โœ… Initialized
  
  constructor(name: string, age: number) {
    this.name = name;  // โœ… Initialize in constructor
    this.age = age;    // โœ… Initialize in constructor
  }
}

๐Ÿ”’ Pitfall 3: Exposing internal state

// โŒ Bad - exposes internal array
class BadTodoList {
  public items: string[] = [];
}

// โœ… Good - controls access
class GoodTodoList {
  private items: string[] = [];
  
  addItem(item: string): void {
    this.items.push(item);
  }
  
  getItems(): readonly string[] {
    return [...this.items]; // Return copy
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Single Responsibility: Each class should have one clear purpose
  2. ๐Ÿ”’ Encapsulation: Keep internal details private
  3. ๐Ÿ“ Clear Naming: Use descriptive class and method names
  4. ๐Ÿ—๏ธ Constructor Logic: Keep constructors simple and focused
  5. โšก Method Size: Keep methods small and focused
  6. ๐Ÿ“Š Immutability: Consider readonly properties where appropriate

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Library Management System

Create a comprehensive library system using classes:

๐Ÿ“‹ Requirements:

  • ๐Ÿ“š Book class with title, author, ISBN, and availability
  • ๐Ÿ‘ค Member class with name, ID, and borrowed books
  • ๐Ÿ›๏ธ Library class to manage books and members
  • ๐Ÿ“Š Track borrowing history and due dates
  • ๐Ÿ” Search functionality for books and members

๐Ÿš€ Bonus Points:

  • Add late fee calculation
  • Implement reservation system
  • Create different member types (student, adult, premium)

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ“š Book class
class Book {
  readonly isbn: string;
  readonly title: string;
  readonly author: string;
  readonly genre: string;
  private isAvailable: boolean = true;
  private borrowedBy: string | null = null;
  private dueDate: Date | null = null;
  
  constructor(isbn: string, title: string, author: string, genre: string) {
    this.isbn = isbn;
    this.title = title;
    this.author = author;
    this.genre = genre;
  }
  
  // ๐Ÿ“Š Get book status
  getStatus(): 'available' | 'borrowed' {
    return this.isAvailable ? 'available' : 'borrowed';
  }
  
  // ๐Ÿ“… Get due date if borrowed
  getDueDate(): Date | null {
    return this.dueDate;
  }
  
  // ๐Ÿ‘ค Get who borrowed it
  getBorrower(): string | null {
    return this.borrowedBy;
  }
  
  // ๐Ÿ“– Borrow book
  borrow(memberId: string, dueDate: Date): boolean {
    if (this.isAvailable) {
      this.isAvailable = false;
      this.borrowedBy = memberId;
      this.dueDate = dueDate;
      return true;
    }
    return false;
  }
  
  // ๐Ÿ”„ Return book
  return(): void {
    this.isAvailable = true;
    this.borrowedBy = null;
    this.dueDate = null;
  }
  
  // ๐Ÿ“‹ Book info
  getInfo(): string {
    const status = this.isAvailable ? 'โœ… Available' : 'โŒ Borrowed';
    return `๐Ÿ“š "${this.title}" by ${this.author} (${this.genre}) - ${status}`;
  }
}

// ๐Ÿ‘ค Library Member class
class LibraryMember {
  readonly memberId: string;
  readonly name: string;
  readonly email: string;
  private borrowedBooks: string[] = []; // ISBNs
  private joinDate: Date;
  
  constructor(memberId: string, name: string, email: string) {
    this.memberId = memberId;
    this.name = name;
    this.email = email;
    this.joinDate = new Date();
  }
  
  // ๐Ÿ“š Get borrowed books
  getBorrowedBooks(): readonly string[] {
    return [...this.borrowedBooks];
  }
  
  // ๐Ÿ“– Borrow a book
  borrowBook(isbn: string): void {
    this.borrowedBooks.push(isbn);
  }
  
  // ๐Ÿ”„ Return a book
  returnBook(isbn: string): void {
    const index = this.borrowedBooks.indexOf(isbn);
    if (index !== -1) {
      this.borrowedBooks.splice(index, 1);
    }
  }
  
  // ๐Ÿ“Š Member info
  getInfo(): string {
    return `๐Ÿ‘ค ${this.name} (ID: ${this.memberId}) - ${this.borrowedBooks.length} books borrowed`;
  }
}

// ๐Ÿ›๏ธ Library Management System
class Library {
  private books: Map<string, Book> = new Map();
  private members: Map<string, LibraryMember> = new Map();
  private borrowingPeriodDays: number = 14;
  
  // ๐Ÿ“š Add book to library
  addBook(book: Book): void {
    this.books.set(book.isbn, book);
    console.log(`โœ… Added book: ${book.getInfo()}`);
  }
  
  // ๐Ÿ‘ค Register new member
  registerMember(member: LibraryMember): void {
    this.members.set(member.memberId, member);
    console.log(`โœ… Registered member: ${member.getInfo()}`);
  }
  
  // ๐Ÿ“– Borrow book
  borrowBook(memberId: string, isbn: string): boolean {
    const member = this.members.get(memberId);
    const book = this.books.get(isbn);
    
    if (!member) {
      console.log(`โŒ Member ${memberId} not found`);
      return false;
    }
    
    if (!book) {
      console.log(`โŒ Book ${isbn} not found`);
      return false;
    }
    
    if (book.getStatus() === 'borrowed') {
      console.log(`โŒ Book "${book.title}" is already borrowed`);
      return false;
    }
    
    // Calculate due date
    const dueDate = new Date();
    dueDate.setDate(dueDate.getDate() + this.borrowingPeriodDays);
    
    // Process borrowing
    book.borrow(memberId, dueDate);
    member.borrowBook(isbn);
    
    console.log(`๐Ÿ“– ${member.name} borrowed "${book.title}"`);
    console.log(`๐Ÿ“… Due date: ${dueDate.toDateString()}`);
    return true;
  }
  
  // ๐Ÿ”„ Return book
  returnBook(memberId: string, isbn: string): boolean {
    const member = this.members.get(memberId);
    const book = this.books.get(isbn);
    
    if (!member || !book) {
      console.log('โŒ Member or book not found');
      return false;
    }
    
    if (book.getBorrower() !== memberId) {
      console.log('โŒ This book was not borrowed by this member');
      return false;
    }
    
    // Process return
    book.return();
    member.returnBook(isbn);
    
    console.log(`โœ… ${member.name} returned "${book.title}"`);
    return true;
  }
  
  // ๐Ÿ” Search books
  searchBooks(query: string): Book[] {
    const results: Book[] = [];
    
    for (const book of this.books.values()) {
      if (book.title.toLowerCase().includes(query.toLowerCase()) ||
          book.author.toLowerCase().includes(query.toLowerCase()) ||
          book.genre.toLowerCase().includes(query.toLowerCase())) {
        results.push(book);
      }
    }
    
    return results;
  }
  
  // ๐Ÿ“Š Display library stats
  displayStats(): void {
    console.log('\n๐Ÿ›๏ธ Library Statistics');
    console.log('='.repeat(25));
    console.log(`๐Ÿ“š Total books: ${this.books.size}`);
    console.log(`๐Ÿ‘ฅ Total members: ${this.members.size}`);
    
    let availableBooks = 0;
    let borrowedBooks = 0;
    
    for (const book of this.books.values()) {
      if (book.getStatus() === 'available') {
        availableBooks++;
      } else {
        borrowedBooks++;
      }
    }
    
    console.log(`โœ… Available books: ${availableBooks}`);
    console.log(`๐Ÿ“– Borrowed books: ${borrowedBooks}`);
  }
  
  // ๐Ÿ“‹ List all books
  listBooks(): void {
    console.log('\n๐Ÿ“š Library Catalog:');
    console.log('='.repeat(30));
    
    for (const book of this.books.values()) {
      console.log(book.getInfo());
      if (book.getStatus() === 'borrowed') {
        const member = this.members.get(book.getBorrower()!);
        const dueDate = book.getDueDate()!;
        console.log(`   ๐Ÿ“… Due: ${dueDate.toDateString()} (${member?.name})`);
      }
    }
  }
}

// ๐Ÿš€ Let's build our library!
const library = new Library();

// ๐Ÿ“š Add some books
const books = [
  new Book('978-0134685991', 'Effective TypeScript', 'Dan Vanderkam', 'Programming'),
  new Book('978-1491927632', 'Programming TypeScript', 'Boris Cherny', 'Programming'),
  new Book('978-0596517748', 'JavaScript: The Good Parts', 'Douglas Crockford', 'Programming'),
  new Book('978-0321125217', 'Domain-Driven Design', 'Eric Evans', 'Software Design')
];

books.forEach(book => library.addBook(book));

// ๐Ÿ‘ฅ Register members
const members = [
  new LibraryMember('MEM001', 'Alice Johnson', '[email protected]'),
  new LibraryMember('MEM002', 'Bob Smith', '[email protected]'),
  new LibraryMember('MEM003', 'Carol Davis', '[email protected]')
];

members.forEach(member => library.registerMember(member));

// ๐Ÿ“– Test borrowing
library.borrowBook('MEM001', '978-0134685991');
library.borrowBook('MEM002', '978-1491927632');

// ๐Ÿ“Š Display current state
library.displayStats();
library.listBooks();

// ๐Ÿ” Search functionality
console.log('\n๐Ÿ” Search results for "TypeScript":');
const searchResults = library.searchBooks('TypeScript');
searchResults.forEach(book => console.log(book.getInfo()));

๐ŸŽ“ Key Takeaways

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

  • โœ… Create classes with properties and methods ๐Ÿ—๏ธ
  • โœ… Use constructors to initialize objects properly ๐Ÿ”ง
  • โœ… Apply access modifiers for proper encapsulation ๐Ÿ”’
  • โœ… Build real-world applications with OOP principles ๐ŸŒŸ
  • โœ… Organize code into reusable, maintainable structures ๐Ÿ“

Remember: Classes are your building blocks for creating amazing applications! ๐Ÿš€

๐Ÿค Next Steps

Amazing work! ๐ŸŽ‰ Youโ€™ve unlocked the power of TypeScript classes!

Hereโ€™s your learning path forward:

  1. ๐Ÿ’ป Practice with the library management exercise above
  2. ๐Ÿ—๏ธ Try building your own class-based project
  3. ๐Ÿ“š Next tutorial: Class Properties and Methods - Building Class Members
  4. ๐ŸŒŸ Share your class designs with the community!

Remember: Great applications start with well-designed classes. Keep building, keep learning! ๐Ÿš€


Happy coding! ๐ŸŽ‰๐Ÿ—๏ธโœจ