Introduction to Inheritance and Polymorphism

In the previous chapter, we looked at how Classes and Objects allow us to bundle data and behavior together. But what happens when we want to create many different types of objects that share similar characteristics? Instead of writing the same code over and over again, we use two powerful "superpowers" of Object-Oriented Programming (OOP): Inheritance and Polymorphism.

These concepts are all about making our code more efficient, organized, and easy to scale. Don't worry if these terms sound like big words—by the end of these notes, you'll see they are just fancy names for very common-sense ideas!


1. Inheritance: Promoting Software Reuse

Inheritance is a mechanism where a new class (called a Child Class or Subclass) is based on an existing class (called a Parent Class or Superclass). The Child Class automatically "inherits" all the attributes and methods of the Parent Class.

Why use Inheritance?

The primary goal of inheritance is Software Reuse. Why write the same code for a "Car" and a "Truck" if they both have an engine and a method to drive? You can simply create a parent class called Vehicle and let the others inherit from it.

The "Is-A" Relationship

A simple way to check if inheritance makes sense is the "Is-A" test:
1. A Dog is-a Mammal. (Inheritance works!)
2. A Smartphone is-a Electronic Device. (Inheritance works!)
3. A Library is-a Book. (No! A library has books, but it isn't a book. Inheritance wouldn't be used here.)

Python Implementation and Method Overriding

In Python, we put the Parent Class name in parentheses when defining the Child Class. If the child needs to change how a specific method works, it can Override that method.

Example:
class Animal:
  def speak(self):
    print("Some generic sound")

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

In this example, Dog inherits from Animal. However, it overrides the speak method to provide a more specific behavior.

The super() Function

Sometimes, we want to use the Parent's original code and then add a little bit more to it. We use the super() function to call the Parent's methods. This is most commonly used in the __init__ constructor to ensure the Parent's attributes are set up correctly.

Key Takeaway: Inheritance allows us to create a hierarchy of classes, reducing redundancy by sharing common code in a Parent class.


2. Polymorphism: Enabling Code Generalisation

Polymorphism comes from Greek, meaning "many forms." In OOP, it refers to the ability of different classes to be treated as instances of the same parent class through the same interface (method names).

Why use Polymorphism?

The goal is Code Generalisation. It allows you to write a single piece of code that can work with many different types of objects, as long as they share the same method name.

An Everyday Analogy

Think of a "Play" button on a universal remote. Whether you are using a Music Player, a Video Streaming App, or a DVD Player, the "Play" button exists for all of them. You don't need a different finger to press "Play" for each device. The action (pressing the button) is the same, but the result (playing music vs. playing a movie) depends on the object you are using. That is polymorphism!

How it works in practice

Imagine you have a list of different shapes: Circles, Squares, and Triangles. If they all inherit from a parent class Shape and all have a method called draw(), you can do this:

for s in shape_list:
  s.draw()

The computer doesn't need to know if \( s \) is a circle or a square at that exact moment; it just knows that every Shape has a draw() method. This makes your program very flexible!

Syllabus Note: For your H2 Computing syllabus, you only need to focus on polymorphism through method overriding. You do not need to learn "Method Overloading" (having multiple methods with the same name but different parameters) or "Multiple Inheritance" (inheriting from more than one parent).

Key Takeaway: Polymorphism allows us to write general code that works with a variety of objects, provided they share the same method interface.


3. Class Diagrams and Relationships

To plan out inheritance, we use Class Diagrams. In these diagrams:
1. Each class is a box with its attributes and methods.
2. Inheritance is shown with a solid line and an arrow pointing from the Child Class to the Parent Class.
3. This visualizes how attributes flow down the chain and how different objects are related.

(Note: For more details on drawing these, refer to the "Class Diagrams" chapter.)


4. Common Mistakes and Quick Review

Common Pitfalls to Avoid

1. Forgetting super(): When creating a Child Class constructor (__init__), if you don't call super().__init__(), the Parent's attributes might not be initialized, leading to errors.
2. Over-inheriting: Only use inheritance if the "Is-A" relationship is true. Don't inherit just to get one or two variables; that's better handled by encapsulation.
3. Confusing the Arrow Direction: In class diagrams, the arrow always points up to the Parent. Think of it as the Child pointing to its origin.

Quick Review Box

Inheritance: Creating a hierarchy. Use it for Software Reuse. Think "Parent and Child."
Polymorphism: One interface, many implementations. Use it for Code Generalisation. Think "One button, different actions."
Method Overriding: When a Child Class replaces a Parent's method with its own version.
Exclusions: You do not need to know Multiple Inheritance or Method Overloading for this syllabus.


Summary: The Big Picture

Inheritance and Polymorphism are the tools that make OOP truly powerful. Inheritance lets us build on top of what we've already created, while Polymorphism lets us treat different objects with a common "language." Together, they make our software modular, reusable, and much easier to maintain as it grows in complexity.