Skip to main content

Chapter 4: Inheritance

What is Inheritance?

Inheritance is a fundamental concept in Object-Oriented Programming that allows a new class to be based on an existing class. The new class inherits attributes and methods from the existing class, enabling code reuse and the creation of hierarchical relationships between classes.

Key concepts in inheritance:

  • Base class (or Parent class): The existing class that is being inherited from.
  • Derived class (or Child class): The new class that inherits from the base class.
  • Superclass: Another term for the base class.
  • Subclass: Another term for the derived class.

Why Use Inheritance?

  1. Code Reusability: Inherit common attributes and methods from a base class, reducing redundant code.
  2. Extensibility: Easily add new features to existing classes without modifying them.
  3. Hierarchical Classification: Represent relationships between classes in a logical, hierarchical structure.
  4. Polymorphism: Enable polymorphic behavior (which we'll cover in the next chapter).

Method Overriding

Method overriding occurs when a derived class provides a specific implementation for a method that is already defined in its base class. This allows the derived class to change or extend the behavior of inherited methods.

Real-life Example: An Animal Hierarchy

Let's create an Animal hierarchy to demonstrate inheritance. We'll have a base Animal class and derived classes for Dog, Cat, and Bird.

class Animal:
def __init__(self, name, age):
self.name = name
self.age = age

def speak(self):
pass # This will be overridden by subclasses

def describe(self):
return f"{self.name} is {self.age} years old"

class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age)
self.breed = breed

def speak(self):
return "Woof!"

def describe(self):
return f"{super().describe()} and is a {self.breed}"

class Cat(Animal):
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color

def speak(self):
return "Meow!"

def describe(self):
return f"{super().describe()} and has {self.color} fur"

class Bird(Animal):
def __init__(self, name, age, species):
super().__init__(name, age)
self.species = species

def speak(self):
return "Chirp!"

def describe(self):
return f"{super().describe()} and is a {self.species}"

def fly(self):
return f"{self.name} is flying high!"

# Using the classes
dog = Dog("Buddy", 5, "Golden Retriever")
cat = Cat("Whiskers", 3, "tabby")
bird = Bird("Tweety", 2, "Canary")

animals = [dog, cat, bird]

for animal in animals:
print(animal.describe())
print(f"{animal.name} says: {animal.speak()}")
if isinstance(animal, Bird):
print(animal.fly())
print() # Empty line for readability

# Output:
# Buddy is 5 years old and is a Golden Retriever
# Buddy says: Woof!
#
# Whiskers is 3 years old and has tabby fur
# Whiskers says: Meow!
#
# Tweety is 2 years old and is a Canary
# Tweety says: Chirp!
# Tweety is flying high!

This example demonstrates several key aspects of inheritance:

  1. Base Class: The Animal class serves as the base class, defining common attributes (name and age) and methods (speak and describe) for all animals.
  2. Derived Classes: Dog, Cat, and Bird are derived classes that inherit from Animal. They all have access to the attributes and methods of the Animal class.
  3. Extending Functionality: Each derived class adds its own unique attribute (breed for Dog, color for Cat, species for Bird).
  4. Method Overriding: All derived classes override the speak method to provide their own specific implementation. They also override the describe method to include their unique attributes.
  5. Super() Function: The super() function is used in the derived classes to call methods from the base class. This allows us to extend the functionality of the base class methods rather than completely replacing them.
  6. Unique Methods: The Bird class has a unique fly method, demonstrating that derived classes can have methods not present in the base class.
  7. Polymorphism: In the loop where we iterate through the animals list, we can call describe and speak on each animal regardless of its specific type. This is an example of polymorphism, which we'll explore more in the next chapter.

Benefits of This Design

  1. Code Reuse: Common attributes and methods are defined once in the Animal class and reused in all derived classes.
  2. Extensibility: We can easily add new animal types (e.g., Fish) without modifying existing code.
  3. Flexibility: Each animal type can have its own unique behaviors while still sharing common traits.
  4. Maintainability: If we need to change something common to all animals, we only need to modify the Animal class.

Considerations When Using Inheritance

  1. Single Inheritance vs. Multiple Inheritance: Some languages (like Python) support multiple inheritance, where a class can inherit from multiple base classes. This can be powerful but also introduces complexity.
  2. Depth of Inheritance: Be cautious about creating too deep of an inheritance hierarchy, as it can make the code harder to understand and maintain.
  3. Liskov Substitution Principle: Derived classes should be substitutable for their base classes without affecting the correctness of the program.
  4. Favor Composition Over Inheritance: In some cases, it might be better to use composition (has-a relationship) instead of inheritance (is-a relationship).

Inheritance is a powerful tool in OOP, allowing for the creation of well-structured, reusable, and extensible code. However, it should be used judiciously and in conjunction with other OOP principles to create the most effective and maintainable software designs.