Skip to main content

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

  1. Compile-time Polymorphism (Static Polymorphism)
    • Method Overloading: Multiple methods in the same class with the same name but different parameters.
  2. 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

  1. Flexibility: Write code that can work with objects of multiple types.
  2. Extensibility: Easily add new classes without changing existing code.
  3. Simplification: Reduce complex conditionals by treating objects uniformly.
  4. 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:

  1. Common Interface: The Shape base class defines a common interface with area(), perimeter(), and describe() methods.
  2. Method Overriding: Each subclass (Circle, Rectangle, Triangle) provides its own implementation of these methods, overriding the base class methods.
  3. Polymorphic Behavior: The print_shape_info() function takes a Shape object as an argument. It can work with any subclass of Shape without knowing the specific type of the object.
  4. Runtime Polymorphism: When we iterate through the shapes list and call print_shape_info(), the correct version of each method is called based on the actual type of the object, demonstrating runtime polymorphism.

Advanced Polymorphism Concepts

  1. 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 abc module for this:
from abc import ABC, abstractmethod

class AbstractShape(ABC):
@abstractmethod
def area(self):
pass

@abstractmethod
def perimeter(self):
pass

  1. Interfaces: Some languages (like Java) have explicit interface constructs. In Python, we can achieve similar functionality using abstract base classes.
  2. 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

  1. Operator Overloading: In many languages, you can define how operators work with your custom objects, which is another form of polymorphism.

Best Practices

  1. Liskov Substitution Principle: Subclasses should be substitutable for their base classes without affecting the correctness of the program.
  2. Dependency Inversion: Depend on abstractions (interfaces or abstract base classes) rather than concrete implementations.
  3. Open/Closed Principle: Classes should be open for extension but closed for modification. Polymorphism helps achieve this by allowing new functionality through new subclasses.
  4. 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.