SOLUTION: Challenge 1 - Abstract Class with Multiple Subclasses ================================================================ Challenge: Create an abstract Vehicle class with abstract methods. Implement two subclasses (Car, Bike) that satisfy the contract. --- SOLUTION: // Abstract base class abstract class Vehicle { abstract make: string; abstract model: string; abstract year: number; abstract start(): void; abstract stop(): void; abstract getInfo(): string; // Concrete method (shared) honk() { console.log(`${this.make} ${this.model} says: Honk!`); } } // Car implementation class Car extends Vehicle { make: string; model: string; year: number; doors: number; constructor(make: string, model: string, year: number, doors: number) { super(); this.make = make; this.model = model; this.year = year; this.doors = doors; } start() { console.log(`🚗 ${this.make} ${this.model} engine starts (vroom vroom)`); } stop() { console.log(`🚗 ${this.make} ${this.model} engine stops`); } getInfo(): string { return `${this.year} ${this.make} ${this.model} (${this.doors}-door car)`; } } // Bike implementation class Bike extends Vehicle { make: string; model: string; year: number; type: "road" | "mountain" | "cruiser"; constructor(make: string, model: string, year: number, type: "road" | "mountain" | "cruiser") { super(); this.make = make; this.model = model; this.year = year; this.type = type; } start() { console.log(`🏍️ ${this.make} ${this.model} engine roars to life`); } stop() { console.log(`🏍️ ${this.make} ${this.model} engine shuts down`); } getInfo(): string { return `${this.year} ${this.make} ${this.model} (${this.type} bike)`; } } // Usage const myCar = new Car("Toyota", "Camry", 2023, 4); myCar.start(); // 🚗 Toyota Camry engine starts (vroom vroom) console.log(myCar.getInfo()); // 2023 Toyota Camry (4-door car) myCar.honk(); // Toyota Camry says: Honk! (inherited method) myCar.stop(); // 🚗 Toyota Camry engine stops console.log("---"); const myBike = new Bike("Harley-Davidson", "Street 750", 2022, "cruiser"); myBike.start(); // 🏍️ Harley-Davidson Street 750 engine roars to life console.log(myBike.getInfo()); // 2022 Harley-Davidson Street 750 (cruiser bike) myBike.honk(); // Harley-Davidson Street 750 says: Honk! (inherited method) myBike.stop(); // 🏍️ Harley-Davidson Street 750 engine shuts down // Can't instantiate abstract class: // const vehicle = new Vehicle(); // ❌ Error: can't instantiate abstract --- EXPLANATION: ABSTRACT CLASS: - Can't be instantiated (new Vehicle() fails) - Defines a contract: subclasses must implement all abstract members - Can have concrete methods (honk()) that subclasses inherit ABSTRACT PROPERTIES: - make, model, year — subclasses must define these - Similar to interfaces but with a class body ABSTRACT METHODS: - start(), stop(), getInfo() — subclasses MUST implement - No body, just the signature SUBCLASS IMPLEMENTATION: - Car extends Vehicle — must implement all abstract members - Bike extends Vehicle — must implement all abstract members - Each has its own specific properties (doors, type) POLYMORPHISM: Both Car and Bike are Vehicles, so you can treat them as such: function inspectVehicle(vehicle: Vehicle) { console.log(vehicle.getInfo()); vehicle.start(); vehicle.honk(); vehicle.stop(); } inspectVehicle(myCar); // Works with Car inspectVehicle(myBike); // Works with Bike --- BENEFITS: 1. Type Safety: TypeScript ensures subclasses implement the contract 2. Flexibility: Different subclasses implement start/stop differently 3. Shared Logic: honk() is written once, inherited by all 4. Clear Intent: Abstract class signals "this is a template" --- INTERFACE VS ABSTRACT CLASS: When to use abstract class: - You want to share implementation (concrete methods) - You have private/protected members - You need constructor logic When to use interface: - Pure contract (no implementation) - Multiple inheritance is desired - Simpler, more minimal contract In this case, abstract class is right because: - honk() is shared implementation - Subclasses are closely related (all Vehicles)