4.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]:
4.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].
4.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
Vehicle
class is defined with an__init__
method to initialize thebrand
attribute and astart_engine
method to print a message indicating that the engine of the vehicle is running.The
Car
class is defined as a subclass ofVehicle
. It has its own__init__
method that takes bothbrand
andmodel
as parameters. It uses thesuper()
function to call the parent class’s__init__
method and initializes themodel
attribute. It also overrides thestart_engine
method to print a message indicating that the specific car model is revving up.Instances of both
Vehicle
andCar
are created:vehicle
andcar
.The
start_engine
method is called on both instances. For thevehicle
instance, the message is generic, whereas for thecar
instance, 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
.
4.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
Shape
class serves as the base class with a single method namedarea
. This method is not implemented in theShape
class (it contains apass
statement), as the actual implementation will be provided by its subclasses.
Subclasses and Method Implementation:
The
Circle
andRectangle
classes inherit from theShape
class.Each subclass (
Circle
andRectangle
) defines its own implementation of thearea
method to calculate the area based on their specific attributes (radius
forCircle
andlength
andwidth
forRectangle
).
Using Polymorphism:
In the last part of the code, a list named
shapes
is created, containing instances of both theCircle
andRectangle
classes.The
for
loop iterates through each shape in theshapes
list.Regardless of whether the shape is a
Circle
or aRectangle
, the loop treats each shape as an instance of the common base classShape
.The
area
method 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
area
method.This demonstrates that different classes (subclasses) can be treated uniformly through a common interface (
Shape
’sarea
method), 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.