Chapter 5: Polymorphism
What is Polymorphism?
Polymorphism is a core concept in Object-Oriented Programming that allows objects of different classes to be treated as objects of a common base class. The term "polymorphism" comes from Greek, meaning "many forms." In OOP, it refers to the ability of a single interface to represent different underlying forms (data types or classes).
Types of Polymorphism
- Compile-time Polymorphism (Static Polymorphism)
- Method Overloading: Multiple methods in the same class with the same name but different parameters.
- Runtime Polymorphism (Dynamic Polymorphism)
- Method Overriding: A subclass provides a specific implementation for a method that is already defined in its superclass.
Benefits of Polymorphism
- Flexibility: Write code that can work with objects of multiple types.
- Extensibility: Easily add new classes without changing existing code.
- Simplification: Reduce complex conditionals by treating objects uniformly.
- Code Reuse: Implement shared behavior in base classes.
Real-life Example: A Shape Class Hierarchy
Let's create a Shape hierarchy to demonstrate polymorphism. We'll have a base Shape class and derived classes for Circle, Rectangle, and Triangle.
import math
class Shape:
def __init__(self, color):
self.color = color
def area(self):
pass # This will be overridden by subclasses
def perimeter(self):
pass # This will be overridden by subclasses
def describe(self):
return f"This is a {self.color} shape."
class Circle(Shape):
def __init__(self, color, radius):
super().__init__(color)
self.radius = radius
def area(self):
return math.pi * self.radius ** 2
def perimeter(self):
return 2 * math.pi * self.radius
def describe(self):
return f"This is a {self.color} circle with radius {self.radius}."
class Rectangle(Shape):
def __init__(self, color, length, width):
super().__init__(color)
self.length = length
self.width = width
def area(self):
return self.length * self.width
def perimeter(self):
return 2 * (self.length + self.width)
def describe(self):
return f"This is a {self.color} rectangle with length {self.length} and width {self.width}."
class Triangle(Shape):
def __init__(self, color, a, b, c):
super().__init__(color)
self.a = a
self.b = b
self.c = c
def area(self):
# Using Heron's formula
s = (self.a + self.b + self.c) / 2
return math.sqrt(s * (s - self.a) * (s - self.b) * (s - self.c))
def perimeter(self):
return self.a + self.b + self.c
def describe(self):
return f"This is a {self.color} triangle with sides {self.a}, {self.b}, and {self.c}."
# Function demonstrating polymorphism
def print_shape_info(shape):
print(shape.describe())
print(f"Area: {shape.area():.2f}")
print(f"Perimeter: {shape.perimeter():.2f}")
print()
# Creating shapes
circle = Circle("red", 5)
rectangle = Rectangle("blue", 4, 6)
triangle = Triangle("green", 3, 4, 5)
# Using polymorphism
shapes = [circle, rectangle, triangle]
for shape in shapes:
print_shape_info(shape)
# Output:
# This is a red circle with radius 5.
# Area: 78.54
# Perimeter: 31.42
#
# This is a blue rectangle with length 4 and width 6.
# Area: 24.00
# Perimeter: 20.00
#
# This is a green triangle with sides 3, 4, and 5.
# Area: 6.00
# Perimeter: 12.00
This example demonstrates several key aspects of polymorphism:
- Common Interface: The
Shapebase class defines a common interface witharea(),perimeter(), anddescribe()methods. - Method Overriding: Each subclass (
Circle,Rectangle,Triangle) provides its own implementation of these methods, overriding the base class methods. - Polymorphic Behavior: The
print_shape_info()function takes aShapeobject as an argument. It can work with any subclass ofShapewithout knowing the specific type of the object. - Runtime Polymorphism: When we iterate through the
shapeslist and callprint_shape_info(), the correct version of each method is called based on the actual type of the object, demonstrating runtime polymorphism.
Advanced Polymorphism Concepts
- Abstract Base Classes: In some languages, you can create abstract base classes that cannot be instantiated and may contain abstract methods (methods without implementation). In Python, you can use the
abcmodule for this:
from abc import ABC, abstractmethod
class AbstractShape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
- Interfaces: Some languages (like Java) have explicit interface constructs. In Python, we can achieve similar functionality using abstract base classes.
- Duck Typing: In dynamically typed languages like Python, polymorphism can be achieved through duck typing. If it walks like a duck and quacks like a duck, it's a duck!
def calculate_total_area(shapes):
return sum(shape.area() for shape in shapes)
# This will work for any object that has an area() method,
# not just those inheriting from Shape
- Operator Overloading: In many languages, you can define how operators work with your custom objects, which is another form of polymorphism.
Best Practices
- Liskov Substitution Principle: Subclasses should be substitutable for their base classes without affecting the correctness of the program.
- Dependency Inversion: Depend on abstractions (interfaces or abstract base classes) rather than concrete implementations.
- Open/Closed Principle: Classes should be open for extension but closed for modification. Polymorphism helps achieve this by allowing new functionality through new subclasses.
- Avoid isinstance() Checks: If you find yourself using many
isinstance()checks, you might not be taking full advantage of polymorphism.
Polymorphism is a powerful concept in OOP that allows for flexible and extensible code design. By using polymorphism effectively, you can create systems that are easier to maintain, extend, and understand.