Inheritance and Polymorphism — Building Hierarchical and Flexible Systems

Inheritance and polymorphism are two of the most powerful principles in Object-Oriented Programming (OOP).

Chapter 5: Object-Oriented Programming (OOP)

Sub-chapter: Inheritance and Polymorphism — Building Hierarchical and Flexible Systems

Inheritance and polymorphism are two of the most powerful principles in Object-Oriented Programming (OOP).
They allow you to reuse, extend, and customize code while maintaining a clear and scalable design.


🧱 What Is Inheritance?

Inheritance allows one class (the child or subclass) to derive from another class (the parent or superclass).
This enables the child class to inherit all attributes and methods of the parent, and optionally override or extend them.

Example: Basic Inheritance

class Animal:
    def speak(self):
        return "Some sound"

class Dog(Animal):
    def speak(self):
        return "Woof!"

class Cat(Animal):
    def speak(self):
        return "Meow!"

dog = Dog()
cat = Cat()

print(dog.speak())  # Woof!
print(cat.speak())  # Meow!

🧩 Why Use Inheritance?

✅ Avoids code duplication.
✅ Encourages code reuse and organization.
✅ Allows hierarchical design and extension.
✅ Enables polymorphism — treating different objects uniformly.


🧠 Types of Inheritance

TypeDescriptionExample
SingleA subclass inherits from one superclass.Dog → Animal
MultilevelA subclass acts as a parent for another subclass.A → B → C
MultipleA class inherits from multiple parent classes.C(A, B)
HierarchicalMultiple subclasses inherit from the same parent.Dog, Cat → Animal
HybridA combination of multiple inheritance patterns.Complex real-world systems

🧮 Method Overriding and super()

When a subclass redefines a method from its parent, the new version overrides the original.

super() is used to call the parent’s version of a method.

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Square(Rectangle):
    def __init__(self, side):
        super().__init__(side, side)

super() ensures proper method chaining, especially in multi-level or multiple inheritance.


🔄 Polymorphism: One Interface, Multiple Implementations

Polymorphism allows different objects to respond to the same method call in different ways.

def make_sound(animal):
    print(animal.speak())

animals = [Dog(), Cat()]
for a in animals:
    make_sound(a)

Output:

Woof!
Meow!

Each subclass provides its own speak() implementation — same interface, different behavior.


🧩 Abstract Base Classes (ABC)

To enforce a structure across subclasses, Python provides the abc module.

from abc import ABC, abstractmethod

class Shape(ABC):
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14 * self.radius ** 2

You cannot instantiate Shape directly — it defines an interface that all subclasses must implement.


🧱 Real-World Example — Employee Management System

This example demonstrates inheritance, polymorphism, and abstraction in a realistic scenario.

from abc import ABC, abstractmethod

class Employee(ABC):
    def __init__(self, name, base_salary):
        self.name = name
        self.base_salary = base_salary

    @abstractmethod
    def calculate_bonus(self):
        pass

    def get_info(self):
        return f"{self.name}: ${self.base_salary}"


class Developer(Employee):
    def __init__(self, name, base_salary, programming_language):
        super().__init__(name, base_salary)
        self.programming_language = programming_language

    def calculate_bonus(self):
        return self.base_salary * 0.10


class Manager(Employee):
    def __init__(self, name, base_salary, team_size):
        super().__init__(name, base_salary)
        self.team_size = team_size

    def calculate_bonus(self):
        return self.base_salary * (0.05 * self.team_size)


employees = [
    Developer("Alice", 80000, "Python"),
    Manager("Bob", 100000, 3),
]

for emp in employees:
    print(f"{emp.get_info()} | Bonus: ${emp.calculate_bonus():.2f}")

Output:

Alice: $80000 | Bonus: $8000.00
Bob: $100000 | Bonus: $15000.00

📊 UML-Style Class Diagram

+-----------------+
|   Employee (ABC) |
+-----------------+
| - name          |
| - base_salary   |
+-----------------+
| + calculate_bonus() (abstract) |
| + get_info()    |
+-----------------+
        ^
        |
  --------------------
  |                  |
+-----------+    +-----------+
| Developer |    |  Manager  |
+-----------+    +-----------+
| - language |   | - team_size |
| + calculate_bonus() | + calculate_bonus() |
+-----------+    +-----------+

🧭 Both Developer and Manager share the same interface from Employee, but implement behavior differently.


🧬 Multiple Inheritance and Method Resolution Order (MRO)

Python supports multiple inheritance — a class can inherit from multiple parent classes.

class A:
    def show(self):
        print("A")

class B(A):
    def show(self):
        print("B")

class C(A):
    def show(self):
        print("C")

class D(B, C):
    pass

d = D()
d.show()
print(D.mro())

Output:

B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

Python uses the C3 linearization algorithm to determine the MRO — the order in which classes are searched for methods.


⚖️ Composition vs Inheritance

ConceptDefinitionWhen to Use
Inheritance“Is a” relationship — reuse structure from a base class.Shared behavior, polymorphism.
Composition“Has a” relationship — reuse functionality by including other objects.Modular design, flexible architecture.

Example:

class Engine:
    def start(self):
        print("Engine started")

class Car:
    def __init__(self):
        self.engine = Engine()  # Composition

    def drive(self):
        self.engine.start()
        print("Car is moving!")

🧩 Best Practices

✅ Favor composition over deep inheritance trees.
✅ Keep class hierarchies shallow and meaningful.
✅ Use super() for clean method chaining.
✅ Abstract shared behavior into base classes.
✅ Avoid inheriting from multiple classes unnecessarily — use mixins if needed.
✅ Document overridden methods clearly.


🧾 Summary


By mastering inheritance and polymorphism, you can design systems that are modular, extensible, and maintainable — key qualities in advanced software engineering.