Chapter 6: Polymorphism in Object Oriented Programming

Chapter 6: Polymorphism

Abstract:
Polymorphism, derived from the Greek words "poly" (many) and "morph" (form), describes the ability of objects of different classes to respond to the same method call in their own specific ways. It enables a single interface to represent different underlying forms, making code more flexible and reusable. Polymorphism is achieved through function overloading and method overriding, and it relies on dynamic dispatch to determine the appropriate method implementation at runtime.
Function Overloading
Function overloading, also known as compile-time polymorphism or static binding, allows multiple methods within the same class to share the same name but differ in their parameter lists (number, types, or order of parameters). The compiler determines which overloaded method to call based on the arguments provided during the method call.
Java
class Calculator {    int add(int a, int b) {        return a + b;    }    double add(double a, double b) {        return a + b;    }    int add(int a, int b, int c) {        return a + b + c;    }}public class Main {    public static void main(String[] args) {        Calculator calc = new Calculator();        System.out.println(calc.add(2, 3));        // Output: 5        System.out.println(calc.add(2.5, 3.5));    // Output: 6.0        System.out.println(calc.add(1, 2, 3));     // Output: 6    }}
Method Overriding
Method overriding, also known as runtime polymorphism or dynamic binding, occurs when a subclass provides its own implementation for a method that is already defined in its superclass. The method signature (name and parameters) remains the same in both the superclass and the subclass. The specific method to be executed is determined at runtime based on the actual object type. 
Java
class Animal {    void makeSound() {        System.out.println("Generic animal sound");    }}class Dog extends Animal {    @Override    void makeSound() {        System.out.println("Woof!");    }}class Cat extends Animal {    @Override    void makeSound() {        System.out.println("Meow!");    }}public class Main {    public static void main(String[] args) {        Animal animal1 = new Dog();        Animal animal2 = new Cat();        animal1.makeSound(); // Output: Woof!        animal2.makeSound(); // Output: Meow!    }}
Dynamic Dispatch
Dynamic dispatch is the mechanism that enables runtime polymorphism. When a method is called on an object, the runtime environment determines the actual type of the object and invokes the appropriate method implementation based on that type. This allows for flexibility and extensibility, as new subclasses can be added without modifying existing code. In the Animal example above, dynamic dispatch ensures that the correct makeSound method is called for each object, regardless of whether it is a Dog or a Cat.

So let's explore the Chapter 6 in detail

Chapter 6: Polymorphism

6.1 Introduction to Polymorphism

Polymorphism is a fundamental concept in object-oriented programming (OOP) that allows objects of different classes to respond to the same method call in different ways. This enhances code reusability, flexibility, and maintainability by enabling a single interface to represent multiple behaviors.

The term "polymorphism" is derived from the Greek words poly (many) and morph (forms), meaning "many forms." In programming, it refers to the ability of a single function, method, or operator to operate on different types of data or perform different tasks based on the context.

Polymorphism can be classified into two main types:

  1. Compile-time polymorphism (Static polymorphism) – Achieved using function overloading and operator overloading.
  2. Runtime polymorphism (Dynamic polymorphism) – Achieved using method overriding in inheritance and dynamic method dispatch.

In this chapter, we will explore these types of polymorphism, focusing on function overloading and method overriding.


6.2 Objects of Different Classes Responding to the Same Method Call

One of the key aspects of polymorphism is that different classes can define the same method but implement it in unique ways. This allows objects of different classes to respond differently to the same function call.

6.2.1 Example of Polymorphism Using Method Overriding

Method overriding is a feature where a subclass provides a specific implementation of a method that is already defined in its superclass. The overridden method in the subclass must have the same name, return type, and parameters as the method in the superclass.

Example: Using Method Overriding in C++

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void makeSound() { // Virtual function
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override { // Overriding the method
        cout << "Dog barks" << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override { // Overriding the method
        cout << "Cat meows" << endl;
    }
};

int main() {
    Animal* a1 = new Dog(); // Polymorphic behavior
    Animal* a2 = new Cat();

    a1->makeSound(); // Calls Dog's makeSound()
    a2->makeSound(); // Calls Cat's makeSound()

    delete a1;
    delete a2;
    return 0;
}

Output:

Dog barks  
Cat meows  

In this example, we use a base class pointer (Animal*) to call the makeSound() method on different objects (Dog and Cat). Since the makeSound() function is declared as virtual in the base class, the appropriate subclass method is called dynamically at runtime.


6.3 Function Overloading

Function overloading is a type of compile-time polymorphism where multiple functions have the same name but different parameters. The compiler determines which function to invoke based on the number and types of arguments passed.

6.3.1 Rules for Function Overloading

  • Functions must have the same name.
  • Functions must differ in the number or type of parameters.
  • The return type alone cannot be used to differentiate overloaded functions.

Example: Function Overloading in C++

#include <iostream>
using namespace std;

// Overloaded functions
void display(int num) {
    cout << "Integer: " << num << endl;
}

void display(double num) {
    cout << "Double: " << num << endl;
}

void display(string text) {
    cout << "String: " << text << endl;
}

int main() {
    display(10);      // Calls display(int)
    display(3.14);    // Calls display(double)
    display("Hello"); // Calls display(string)
    return 0;
}

Output:

Integer: 10  
Double: 3.14  
String: Hello  

Here, the display() function is overloaded with three different parameter types (integer, double, and string). The compiler selects the appropriate function based on the argument type.


6.4 Method Overriding

Method overriding is a feature of runtime polymorphism where a subclass provides a different implementation of a method that is already defined in its superclass. The overridden method in the subclass must have the same function signature (name, return type, and parameters) as the method in the superclass.

6.4.1 Example: Method Overriding in Java

class Vehicle {
    void move() {
        System.out.println("The vehicle is moving");
    }
}

class Car extends Vehicle {
    @Override
    void move() {
        System.out.println("The car is driving");
    }
}

class Bicycle extends Vehicle {
    @Override
    void move() {
        System.out.println("The bicycle is pedaling");
    }
}

public class Main {
    public static void main(String[] args) {
        Vehicle v1 = new Car();
        Vehicle v2 = new Bicycle();

        v1.move(); // Calls Car's move() method
        v2.move(); // Calls Bicycle's move() method
    }
}

Output:

The car is driving  
The bicycle is pedaling  

This demonstrates runtime polymorphism through method overriding, where different objects respond differently to the same method call.


6.5 Function Overloading vs. Method Overriding

Feature Function Overloading Method Overriding
Definition Multiple functions with the same name but different parameters. Redefining a method in a subclass that exists in a superclass.
Type Compile-time polymorphism Runtime polymorphism
Parameters Must differ in number or type Must have the same parameters
Return Type Can be different, but does not differentiate functions Must be the same or a subtype
Inheritance Not required Required (between base and derived class)
Access Modifier Can be different Cannot reduce visibility (e.g., public method in superclass cannot become private in subclass)

6.6 Advantages of Polymorphism

  • Code Reusability: Reduces code duplication by allowing methods with the same name to be used for different data types or behaviors.
  • Flexibility and Scalability: Enables extending and modifying code without affecting existing functionality.
  • Improved Readability and Maintainability: Reduces complexity by allowing uniform method names for similar operations across different classes.
  • Supports Loose Coupling: Helps in designing loosely coupled systems, making them easier to manage and extend.

6.7 Conclusion

Polymorphism is a powerful OOP concept that enables objects of different classes to respond to the same method call differently. It is implemented through function overloading (compile-time polymorphism) and method overriding (runtime polymorphism). Function overloading allows multiple functions with the same name but different parameters, while method overriding enables dynamic method dispatch, allowing a subclass to provide a specific implementation of a superclass method.

By leveraging polymorphism, developers can write more flexible, reusable, and maintainable code, making it a cornerstone of modern software development.

Comments