Part 3: Inheritance and Polymorphism - Code Reuse and Flexibility

Introduction

I built an e-commerce system with separate classes: PhysicalProduct, DigitalProduct, and SubscriptionProduct. Each had duplicate code for pricing, discounts, and inventory tracking. When I needed to add tax calculation, I updated PhysicalProduct but forgot the others. Production bugs followed.

I refactored using inheritance. Created a base Product class with shared logic. Extended it for specific product types. Tax calculation went in the base class once. All products inherited it automatically. That's when I understood inheritance's power.

This article shows you how to build class hierarchies, extend behavior, override methods, and leverage polymorphism for maintainable, flexible code.

The Problem: Code Duplication

class PhysicalProduct {
    constructor(
        public id: string,
        public name: string,
        public price: number,
        public weight: number
    ) {}
    
    calculatePrice(): number {
        return this.price;
    }
    
    applyDiscount(percent: number): void {
        this.price -= this.price * (percent / 100);
    }
    
    getShippingCost(): number {
        return this.weight * 0.5;
    }
}

class DigitalProduct {
    constructor(
        public id: string,
        public name: string,
        public price: number,
        public fileSize: number  // MB
    ) {}
    
    // Duplicate code!
    calculatePrice(): number {
        return this.price;
    }
    
    // Duplicate code!
    applyDiscount(percent: number): void {
        this.price -= this.price * (percent / 100);
    }
    
    getDownloadUrl(): string {
        return `https://downloads.example.com/${this.id}`;
    }
}

Problems:

  • Duplicate calculatePrice() and applyDiscount() logic

  • Bug fixes need updates in multiple places

  • Adding features requires changing every class

  • No unified way to handle different product types

The Solution: Inheritance

Inheritance Basics

Extends Keyword

Method Overriding

Using super

Polymorphism

Different classes responding to same method call differently:

Abstract Classes

Classes that cannot be instantiated directly - only extended:

Real Example: Notification System

Real Example: User Roles and Permissions

Protected vs Private

Method Override Rules

Best Practices

1. Favor Composition Over Deep Inheritance

2. Use Abstract Classes for Shared Behavior

3. Keep Inheritance Hierarchies Shallow

4. Call super() First in Constructor

What's Next

You now understand:

  • Class inheritance with extends

  • Method overriding and super

  • Abstract classes and methods

  • Polymorphism for flexible code

  • Protected vs private members

  • Building class hierarchies

In Part 4: Interfaces and Composition, you'll learn how to use TypeScript interfaces for contracts, composition patterns for flexibility, and dependency injection for testable, maintainable code.


Based on building scalable TypeScript applications with clean inheritance hierarchies, from payment systems to notification services.

Last updated