3.2. Inheritance and Polymorphism#
Inheritance and polymorphism are powerful concepts in object-oriented programming that enable code reuse, extensibility, and flexibility. They enhance the structure and functionality of classes by allowing you to create hierarchies of classes and interact with different classes in a unified way. Let’s dive into the intricacies of inheritance and polymorphism [Lin et al., 2022, Wilson, 2022]:
3.2.1. Introduction to Inheritance#
Inheritance is a fundamental concept that enables the creation of new classes (subclasses or derived classes) based on existing ones (superclasses or base classes). This process allows the new class to inherit attributes and methods from the existing class, promoting code reuse and efficient development [Lin et al., 2022, Wilson, 2022].
3.2.2. Superclass and Subclass Relationships#
In the context of inheritance, the existing class is referred to as the superclass or base class. The new class being created is called the subclass or derived class. The subclass inherits attributes and methods from the superclass, and it can also have additional attributes and methods specific to its own purpose. The subclass extends and refines the functionality of the superclass [Lin et al., 2022, Wilson, 2022].
Example - Inheritance and Subclass Definition:
# Define a base class called Vehicle
class Vehicle:
def __init__(self, brand):
self.brand = brand
def start_engine(self):
# Method to start the vehicle's engine
print(f"The {self.brand} vehicle's engine is running.")
# Define a subclass Car that inherits from Vehicle
class Car(Vehicle):
def __init__(self, brand, model):
# Initialize the Car with a brand and model
super().__init__(brand)
self.model = model
def start_engine(self):
# Method to start the car's engine, overriding the parent class method
print(f"The {self.brand} {self.model} is revving up.")
# Creating instances of Vehicle and Car
vehicle = Vehicle("Generic")
car = Car("Toyota", "Camry")
# Using the start_engine method
vehicle.start_engine() # Output: The Generic vehicle's engine is running.
car.start_engine() # Output: The Toyota Camry is revving up.
The Generic vehicle's engine is running.
The Toyota Camry is revving up.
In this example:
The
Vehicleclass is defined with an__init__method to initialize thebrandattribute and astart_enginemethod to print a message indicating that the engine of the vehicle is running.The
Carclass is defined as a subclass ofVehicle. It has its own__init__method that takes bothbrandandmodelas parameters. It uses thesuper()function to call the parent class’s__init__method and initializes themodelattribute. It also overrides thestart_enginemethod to print a message indicating that the specific car model is revving up.Instances of both
VehicleandCarare created:vehicleandcar.The
start_enginemethod is called on both instances. For thevehicleinstance, the message is generic, whereas for thecarinstance, the specific car model is mentioned.
This example showcases the concept of inheritance, where the Car class inherits attributes and methods from its parent class Vehicle. It also demonstrates how method overriding works, as the Car class provides its own implementation of the start_engine method that’s specific to cars.
Note
In the provided example, the super() function is used within the Car class’s __init__ method to call the constructor of its superclass, which is the Vehicle class. The purpose of using super() is to ensure that the initialization code from the parent class is executed before adding any additional attributes or behavior specific to the subclass.
class Vehicle:
def __init__(self, brand):
self.brand = brand
# Other methods...
class Car(Vehicle):
def __init__(self, brand, model):
super().__init__(brand) # Calls the parent class constructor
self.model = model
# Other methods...
In this code, when you create an instance of the Car class, the __init__ method of the Car class is executed. Within this method, the super().__init__(brand) line is responsible for calling the __init__ method of the parent class (Vehicle). By doing so, it initializes the brand attribute inherited from the Vehicle class.
Using super() is particularly useful when working with inheritance, as it ensures that the common attributes and behavior from the parent class are properly initialized before adding any specific attributes or behavior to the subclass. It helps maintain a consistent object initialization process across the inheritance hierarchy.
Note that the start_engine method in the Car subclass overrides the start_engine method of the parent class Vehicle. This is a common feature in object-oriented programming called method overriding. Method overriding allows a subclass to provide a specific implementation for a method that is already defined in its parent class. When you define a method with the same name in a subclass, it “overrides” the method with the same name in the parent class. This means that when you call start_engine on an instance of the Car class, the overridden method in Car will be executed instead of the one in Vehicle.
3.2.3. Polymorphism#
Polymorphism is the ability of different classes to be treated as instances of a common class or interface. This enables a unified approach to interacting with objects of different types. By implementing the same method names in multiple classes, you can call these methods on different objects and achieve different behaviors based on the object type [Lin et al., 2022, Wilson, 2022].
Example - Polymorphism:
# Define an abstract class called Shape
class Shape:
def area(self):
# This method is meant to be overridden by subclasses
pass
# Define a Circle class that inherits from Shape
class Circle(Shape):
def __init__(self, radius):
# Initialize the Circle with a radius
self.radius = radius
def area(self):
# Calculate and return the area of the Circle
return 3.14 * self.radius * self.radius
# Define a Rectangle class that inherits from Shape
class Rectangle(Shape):
def __init__(self, length, width):
# Initialize the Rectangle with a length and width
self.length = length
self.width = width
def area(self):
# Calculate and return the area of the Rectangle
return self.length * self.width
# Using polymorphism with different shapes
shapes = [Circle(5), Rectangle(4, 6)]
for shape in shapes:
# Calculate and print the area of each shape
print(f"Area: {shape.area()}")
Area: 78.5
Area: 24
Here’s a breakdown of the example and how it illustrates polymorphism:
Base Class and Method Definition:
The
Shapeclass serves as the base class with a single method namedarea. This method is not implemented in theShapeclass (it contains apassstatement), as the actual implementation will be provided by its subclasses.
Subclasses and Method Implementation:
The
CircleandRectangleclasses inherit from theShapeclass.Each subclass (
CircleandRectangle) defines its own implementation of theareamethod to calculate the area based on their specific attributes (radiusforCircleandlengthandwidthforRectangle).
Using Polymorphism:
In the last part of the code, a list named
shapesis created, containing instances of both theCircleandRectangleclasses.The
forloop iterates through each shape in theshapeslist.Regardless of whether the shape is a
Circleor aRectangle, the loop treats each shape as an instance of the common base classShape.The
areamethod is called on each shape, and the appropriate implementation from the respective subclass is invoked.
Output:
The loop prints the calculated area for each shape, even though they have different implementations for the
areamethod.This demonstrates that different classes (subclasses) can be treated uniformly through a common interface (
Shape’sareamethod), showcasing polymorphism.
Inheritance and Polymorphism Summary
Inheritance facilitates the creation of class hierarchies, promoting code reuse and extension. It establishes relationships between superclasses and subclasses, allowing the subclass to inherit and customize behavior. Polymorphism, on the other hand, enables a unified interaction with diverse objects, offering flexibility and ease of use. These concepts collectively enhance the modularity, reusability, and maintainability of object-oriented programs.