15 minlesson

Abstract Classes and Inheritance

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
typescript
1abstract class ShippingCalculator {
2 // Abstract method - no implementation
3 abstract calculateRate(weight: number): number;
4
5 // Concrete method - has implementation
6 getFormattedRate(weight: number): string {
7 const rate = this.calculateRate(weight);
8 return `$${rate.toFixed(2)}`;
9 }
10}
11
12// const calc = new ShippingCalculator(); // Error: cannot instantiate abstract
13
14class FedExCalculator extends ShippingCalculator {
15 calculateRate(weight: number): number {
16 return weight * 3.50;
17 }
18}
19
20const fedex = new FedExCalculator();
21console.log(fedex.getFormattedRate(10)); // "$35.00"

Abstract vs Interface

FeatureAbstract ClassInterface
ImplementationCan haveCannot have
PropertiesCan have valuesOnly declarations
ConstructorYesNo
Multiple inheritanceNoYes
Access modifiersYesNo (all public)

When to Use Each

typescript
1// Use INTERFACE when you only need a contract
2interface Trackable {
3 trackingNumber: string;
4 getStatus(): string;
5}
6
7// Use ABSTRACT CLASS when you need shared implementation
8abstract class NotificationSender {
9 protected attempts: number = 0;
10
11 abstract send(message: string): Promise<boolean>;
12
13 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:

typescript
1abstract class ReportGenerator {
2 // Template method - defines the algorithm
3 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 }
9
10 // Abstract methods - subclasses must implement
11 protected abstract createHeader(): string;
12 protected abstract createBody(): string;
13
14 // Concrete method with default - can be overridden
15 protected createFooter(): string {
16 return `Generated at ${new Date().toISOString()}`;
17 }
18
19 // Hook method - optional override
20 protected format(header: string, body: string, footer: string): string {
21 return `${header}\n${body}\n${footer}`;
22 }
23}
24
25class ShippingReport extends ReportGenerator {
26 constructor(private shipments: Shipment[]) {
27 super();
28 }
29
30 protected createHeader(): string {
31 return '=== Shipping Report ===';
32 }
33
34 protected createBody(): string {
35 return this.shipments
36 .map(s => `${s.id}: ${s.status}`)
37 .join('\n');
38 }
39
40 // Override footer
41 protected createFooter(): string {
42 return `Total: ${this.shipments.length} shipments`;
43 }
44}

Inheritance Hierarchy

typescript
1abstract class Vehicle {
2 constructor(protected speed: number = 0) {}
3
4 abstract getType(): string;
5
6 accelerate(amount: number): void {
7 this.speed += amount;
8 }
9}
10
11abstract class GroundVehicle extends Vehicle {
12 abstract getWheelCount(): number;
13}
14
15class DeliveryTruck extends GroundVehicle {
16 getType(): string {
17 return 'Delivery Truck';
18 }
19
20 getWheelCount(): number {
21 return 18;
22 }
23}
24
25class DeliveryBike extends GroundVehicle {
26 getType(): string {
27 return 'Delivery Bike';
28 }
29
30 getWheelCount(): number {
31 return 2;
32 }
33}

Protected Abstract Members

Abstract classes can have protected members:

typescript
1abstract class AddressFormatter {
2 protected abstract countryCode: string;
3 protected abstract formatStreet(address: Address): string;
4
5 format(address: Address): string {
6 return [
7 this.formatStreet(address),
8 `${address.city}, ${this.countryCode}`
9 ].join('\n');
10 }
11}
12
13class USFormatter extends AddressFormatter {
14 protected countryCode = 'USA';
15
16 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
typescript
1// INHERITANCE - tight coupling
2class ExpressDeliveryTruck extends DeliveryTruck {
3 calculatePriorityCost(): number {
4 return this.speed * 1.5;
5 }
6}
7
8// COMPOSITION - loose coupling (often preferred)
9class DeliveryService {
10 constructor(
11 private vehicle: Vehicle,
12 private calculator: CostCalculator,
13 private tracker: Tracker
14 ) {}
15
16 deliver(package: Package): void {
17 this.tracker.start(package);
18 // Use composed objects
19 }
20}

Factory Method Pattern Preview

Abstract classes enable Factory Method:

typescript
1abstract class ShippingLabelFactory {
2 // Factory method
3 abstract createLabel(shipment: Shipment): ShippingLabel;
4
5 // Uses factory method
6 printLabel(shipment: Shipment): void {
7 const label = this.createLabel(shipment);
8 label.print();
9 }
10}
11
12class FedExLabelFactory extends ShippingLabelFactory {
13 createLabel(shipment: Shipment): ShippingLabel {
14 return new FedExLabel(shipment);
15 }
16}
17
18class USPSLabelFactory extends ShippingLabelFactory {
19 createLabel(shipment: Shipment): ShippingLabel {
20 return new USPSLabel(shipment);
21 }
22}

Best Practices

  1. Keep hierarchies shallow - Deep inheritance is hard to understand
  2. Use abstract for shared behavior - Not just for type constraints
  3. Consider composition first - More flexible than inheritance
  4. Make abstract members protected - Unless they're part of public API
  5. Document template methods - Explain the algorithm and extension points

Summary

ConceptUse Case
Abstract classPartial implementation with template
Abstract methodContract subclass must fulfill
Concrete methodShared implementation
ProtectedExtension points for subclasses
Inheritance"Is-a" relationships
Composition"Has-a" relationships, more flexible

Next: Build a shipping entity class hierarchy!