Abstract Classes and Inheritance
Abstract classes define templates that subclasses must complete. They're essential for patterns like Template Method, Factory Method, and Strategy.
What is an Abstract Class?
An abstract class:
- Cannot be instantiated directly
- May contain abstract methods (no implementation)
- May contain concrete methods (with implementation)
- Forces subclasses to implement abstract methods
typescript1abstract class ShippingCalculator {2 // Abstract method - no implementation3 abstract calculateRate(weight: number): number;45 // Concrete method - has implementation6 getFormattedRate(weight: number): string {7 const rate = this.calculateRate(weight);8 return `$${rate.toFixed(2)}`;9 }10}1112// const calc = new ShippingCalculator(); // Error: cannot instantiate abstract1314class FedExCalculator extends ShippingCalculator {15 calculateRate(weight: number): number {16 return weight * 3.50;17 }18}1920const fedex = new FedExCalculator();21console.log(fedex.getFormattedRate(10)); // "$35.00"
Abstract vs Interface
| Feature | Abstract Class | Interface |
|---|---|---|
| Implementation | Can have | Cannot have |
| Properties | Can have values | Only declarations |
| Constructor | Yes | No |
| Multiple inheritance | No | Yes |
| Access modifiers | Yes | No (all public) |
When to Use Each
typescript1// Use INTERFACE when you only need a contract2interface Trackable {3 trackingNumber: string;4 getStatus(): string;5}67// Use ABSTRACT CLASS when you need shared implementation8abstract class NotificationSender {9 protected attempts: number = 0;1011 abstract send(message: string): Promise<boolean>;1213 async sendWithRetry(message: string, maxRetries: number = 3): Promise<boolean> {14 while (this.attempts < maxRetries) {15 this.attempts++;16 if (await this.send(message)) {17 return true;18 }19 }20 return false;21 }22}
Template Method Pattern Preview
Abstract classes are perfect for Template Method:
typescript1abstract class ReportGenerator {2 // Template method - defines the algorithm3 generate(): string {4 const header = this.createHeader();5 const body = this.createBody();6 const footer = this.createFooter();7 return this.format(header, body, footer);8 }910 // Abstract methods - subclasses must implement11 protected abstract createHeader(): string;12 protected abstract createBody(): string;1314 // Concrete method with default - can be overridden15 protected createFooter(): string {16 return `Generated at ${new Date().toISOString()}`;17 }1819 // Hook method - optional override20 protected format(header: string, body: string, footer: string): string {21 return `${header}\n${body}\n${footer}`;22 }23}2425class ShippingReport extends ReportGenerator {26 constructor(private shipments: Shipment[]) {27 super();28 }2930 protected createHeader(): string {31 return '=== Shipping Report ===';32 }3334 protected createBody(): string {35 return this.shipments36 .map(s => `${s.id}: ${s.status}`)37 .join('\n');38 }3940 // Override footer41 protected createFooter(): string {42 return `Total: ${this.shipments.length} shipments`;43 }44}
Inheritance Hierarchy
typescript1abstract class Vehicle {2 constructor(protected speed: number = 0) {}34 abstract getType(): string;56 accelerate(amount: number): void {7 this.speed += amount;8 }9}1011abstract class GroundVehicle extends Vehicle {12 abstract getWheelCount(): number;13}1415class DeliveryTruck extends GroundVehicle {16 getType(): string {17 return 'Delivery Truck';18 }1920 getWheelCount(): number {21 return 18;22 }23}2425class DeliveryBike extends GroundVehicle {26 getType(): string {27 return 'Delivery Bike';28 }2930 getWheelCount(): number {31 return 2;32 }33}
Protected Abstract Members
Abstract classes can have protected members:
typescript1abstract class AddressFormatter {2 protected abstract countryCode: string;3 protected abstract formatStreet(address: Address): string;45 format(address: Address): string {6 return [7 this.formatStreet(address),8 `${address.city}, ${this.countryCode}`9 ].join('\n');10 }11}1213class USFormatter extends AddressFormatter {14 protected countryCode = 'USA';1516 protected formatStreet(address: Address): string {17 return `${address.street}`;18 }19}
Inheritance vs Composition
Prefer composition over inheritance when:
- You need to combine behaviors from multiple sources
- The "is-a" relationship doesn't hold
- You want more flexibility at runtime
typescript1// INHERITANCE - tight coupling2class ExpressDeliveryTruck extends DeliveryTruck {3 calculatePriorityCost(): number {4 return this.speed * 1.5;5 }6}78// COMPOSITION - loose coupling (often preferred)9class DeliveryService {10 constructor(11 private vehicle: Vehicle,12 private calculator: CostCalculator,13 private tracker: Tracker14 ) {}1516 deliver(package: Package): void {17 this.tracker.start(package);18 // Use composed objects19 }20}
Factory Method Pattern Preview
Abstract classes enable Factory Method:
typescript1abstract class ShippingLabelFactory {2 // Factory method3 abstract createLabel(shipment: Shipment): ShippingLabel;45 // Uses factory method6 printLabel(shipment: Shipment): void {7 const label = this.createLabel(shipment);8 label.print();9 }10}1112class FedExLabelFactory extends ShippingLabelFactory {13 createLabel(shipment: Shipment): ShippingLabel {14 return new FedExLabel(shipment);15 }16}1718class USPSLabelFactory extends ShippingLabelFactory {19 createLabel(shipment: Shipment): ShippingLabel {20 return new USPSLabel(shipment);21 }22}
Best Practices
- Keep hierarchies shallow - Deep inheritance is hard to understand
- Use abstract for shared behavior - Not just for type constraints
- Consider composition first - More flexible than inheritance
- Make abstract members protected - Unless they're part of public API
- Document template methods - Explain the algorithm and extension points
Summary
| Concept | Use Case |
|---|---|
| Abstract class | Partial implementation with template |
| Abstract method | Contract subclass must fulfill |
| Concrete method | Shared implementation |
| Protected | Extension points for subclasses |
| Inheritance | "Is-a" relationships |
| Composition | "Has-a" relationships, more flexible |
Next: Build a shipping entity class hierarchy!