🏗️

Inheritance

Object-Oriented Programming Intermediate 2 min read 300 words

Create new classes based on existing ones to promote code reuse and establish hierarchies

Inheritance

Inheritance is a mechanism where a new class (derived/child) is created from an existing class (base/parent), inheriting its properties and methods. It establishes an “is-a” relationship between classes.

Why Inheritance Matters

  • Code Reuse: Share common functionality across related classes
  • Hierarchy: Model real-world relationships
  • Polymorphism: Enable treating derived objects as their base type
  • Extensibility: Add new behavior without modifying existing code

Basic Inheritance

// Base class
public class Animal
{
    public string Name { get; set; }

    public virtual void Speak()
    {
        Console.WriteLine($"{Name} makes a sound");
    }

    public abstract void Move();  // Must be implemented
}

// Derived class - inherits from Animal
public class Dog : Animal
{
    public string Breed { get; set; }

    public override void Speak()
    {
        Console.WriteLine($"{Name} barks: Woof!");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} runs on four legs");
    }

    // New method specific to Dog
    public void Fetch()
    {
        Console.WriteLine($"{Name} fetches the ball");
    }
}

// Multi-level inheritance
public class Terrier : Dog
{
    public override void Speak()
    {
        Console.WriteLine($"{Name} yaps: Yip yip!");
    }
}

// Usage
Animal myDog = new Dog { Name = "Rex", Breed = "German Shepherd" };
myDog.Speak();  // "Rex barks: Woof!"
myDog.Move();   // "Rex runs on four legs"

Keywords: virtual, override, abstract, sealed

public abstract class Shape
{
    // Abstract method - MUST be overridden, no implementation
    public abstract double GetArea();

    // Virtual method - CAN be overridden, has default implementation
    public virtual void Draw()
    {
        Console.WriteLine("Drawing shape");
    }

    // Regular method - cannot be overridden
    public void LogInfo()
    {
        Console.WriteLine($"Shape with area: {GetArea()}");
    }
}

public class Circle : Shape
{
    public double Radius { get; set; }

    // Required: implementing abstract method
    public override double GetArea() => Math.PI * Radius * Radius;

    // Optional: overriding virtual method
    public override void Draw()
    {
        Console.WriteLine($"Drawing circle with radius {Radius}");
    }
}

// Sealed class - cannot be inherited
public sealed class Square : Shape
{
    public double Side { get; set; }
    public override double GetArea() => Side * Side;
}

// Error: Cannot derive from sealed class
// public class SpecialSquare : Square { }

// Sealed method - cannot be overridden in further derived classes
public class Rectangle : Shape
{
    public double Width { get; set; }
    public double Height { get; set; }

    public sealed override double GetArea() => Width * Height;
}

Calling Base Class Members

public class Employee
{
    public string Name { get; set; }
    public decimal BaseSalary { get; protected set; }

    public Employee(string name, decimal baseSalary)
    {
        Name = name;
        BaseSalary = baseSalary;
    }

    public virtual decimal CalculatePay()
    {
        return BaseSalary;
    }
}

public class SalesEmployee : Employee
{
    public decimal Commission { get; set; }
    public decimal SalesAmount { get; set; }

    // Call base constructor
    public SalesEmployee(string name, decimal baseSalary, decimal commission)
        : base(name, baseSalary)
    {
        Commission = commission;
    }

    public override decimal CalculatePay()
    {
        // Call base implementation and add to it
        return base.CalculatePay() + (SalesAmount * Commission);
    }
}

When NOT to Use Inheritance

Inheritance is powerful but often overused. Here are anti-patterns to avoid:

Anti-Pattern 1: Deep Inheritance Hierarchies

// BAD: Too many levels (fragile base class problem)
public class BasePersistence { }
public class DataAccessLayer : BasePersistence { }
public class RepositoryBase : DataAccessLayer { }
public class ProductRepository : RepositoryBase { }
public class CachedProductRepository : ProductRepository { }
// 5 levels deep! Changes at any level can break everything below

Anti-Pattern 2: Inheriting Just for Code Reuse

// BAD: Using inheritance just to reuse methods
public class Logger
{
    protected void Log(string message) { /* ... */ }
}

public class OrderService : Logger  // OrderService is NOT a Logger!
{
    public void ProcessOrder(Order order)
    {
        Log("Processing order");  // Works, but wrong design!
    }
}

The Right Way: Composition Over Inheritance

// GOOD: Use composition instead
public class OrderService
{
    private readonly ILogger _logger;

    public OrderService(ILogger logger)
    {
        _logger = logger;
    }

    public void ProcessOrder(Order order)
    {
        _logger.Log("Processing order");
    }
}

// GOOD: Decorators instead of deep inheritance
public class CachedProductRepository : IProductRepository
{
    private readonly IProductRepository _inner;
    private readonly ICache _cache;

    public CachedProductRepository(IProductRepository inner, ICache cache)
    {
        _inner = inner;
        _cache = cache;
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        var cached = await _cache.GetAsync<Product>($"product_{id}");
        if (cached != null) return cached;

        var product = await _inner.GetByIdAsync(id);
        await _cache.SetAsync($"product_{id}", product);
        return product;
    }
}

Inheritance Rules for Senior Developers

Rule Description
Use for “is-a” only A Dog IS an Animal, not “Dog uses Animal”
Keep hierarchies shallow Maximum 3 levels recommended
Prefer composition “Has-a” relationships are more flexible
Prefer interfaces For contracts without shared implementation
Follow LSP Derived types must be substitutable for base

Interview Tips

Common Questions:

  • “What is inheritance? Give an example.”
  • “Explain the difference between virtual and abstract
  • “When would you use inheritance vs. composition?”
  • “What is the Liskov Substitution Principle?”

Key Points:

  1. Inheritance creates “is-a” relationships
  2. Use virtual for methods that CAN be overridden
  3. Use abstract for methods that MUST be overridden
  4. Use sealed to prevent further inheritance
  5. Prefer composition over inheritance for flexibility
  6. Keep hierarchies flat (max 3 levels)

Red Flags in Code Reviews:

  • Deep inheritance hierarchies (4+ levels)
  • Inheriting just to reuse code
  • Base classes that are too large or do too much
  • Violations of Liskov Substitution Principle

📚 Related Articles