Polymorphism in OOPS
Polymorphism
Polymorphism allows objects of different classes to be treated as objects of a common base class. This is achieved through virtual functions and function overriding. Polymorphism allows for dynamic binding at runtime and enables the use of a common interface for different derived classes.
Compile-Time Polymorphism (Function Overloading): Function overloading is a form of compile-time polymorphism where multiple functions can have the same name but different parameters. The appropriate function for a call is determined by the number and types of arguments passed during the function call.
class MathUtils {
public:
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
};
Static polymorphism, also known as compile-time polymorphism or early binding, is a concept in object-oriented programming where the method or function that will be executed is determined at compile-time rather than runtime. This means that the compiler decides which function to call based on the static (or fixed) type of the object involved, rather than waiting until runtime to determine the factual type and behaviour of the object.
There are two main mechanisms for achieving static polymorphism:
- Function Overloading: Function overloading allows multiple functions with the same name to coexist in the same scope as long as their parameter lists differ in terms of the number or types of parameters. During compilation, the compiler selects the applicable function based on the number and types of arguments used in the function call.
illustration in C++:
class mathOperations {
public:
int add(int a, int b ) {
return a + b;
}
double add( double a, double b ) {
return a + b;
}
};
int main() {
mathOperations math;
int result1 = math.add(2math.add(2.5math.add(2.5math.add(2.5, 3 );
double result2 = math.add(2.5, 3.7 );
return 0;
}
- Template Metaprogramming (in C++): Templates in C++ allow the creation of general functions or classes whose factual types are determined at compile time. This enables the compiler to generate specific code for each different type used with the template.
Example
template <typename T>
T add(T a, T b ) {
return a + b;
}
int main() {
int result1 = add( 2, 3 );
double result2 = add( 2.5, 3.7 );
return 0;
}
Static polymorphism is generally more effective than dynamic polymorphism( achieved through virtual functions and runtime type information) because the decision on which function to call is made at compile time, eliminating the overhead of dynamic dispatch. Still, it comes with the limitation that the factual type of the object must be known at compile time. However, dynamic polymorphism would be more suitable if you need the flexibility to determine the behaviour at runtime.
Runtime Polymorphism (Virtual Functions and Function Overriding): Runtime polymorphism is achieved using virtual functions and function overriding. A virtual function is a function declared in the base class with the ‘virtual’ keyword. Derived classes can provide their own implementation of the virtual function using the ‘override’ keyword.
class Shape {
public:
virtual void draw() {
cout << "Drawing a shape." << endl;
}
};
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a circle." << endl;
}
};
class Square : public Shape {
public:
void draw() override {
cout << "Drawing a square." << endl;
}
};
Example
class Shape {
public:
virtual double getArea() const = 0;
};
class Rectangle: public Shape {
public:
double getArea() const override {
return width * height;
}
};
class Circle: public Shape {
public:
double getArea() const override {
return 3.14159 * radius * radius;
}
};
Dynamic polymorphism, also known as runtime polymorphism or late binding, is a concept in object-oriented programming where the method or function that will be executed is determined at runtime based on the factual type and behaviour of the object involved. This allows for further flexibility in the behaviour of objects and enables the implementation of polymorphic behaviour.
Dynamic polymorphism is achieved through the use of virtual functions (also known as virtual methods) and is a fundamental point of object-oriented languages like C++ and Java. It enables a subclass to provide a specific implementation of a method defined in its superclass, allowing for different behaviours for objects of different types.
Then here is how dynamic polymorphism works in C++:
- Virtual Functions:In C++, you can declare a function as virtual in the base class. When a function is declared virtual, the compiler creates a virtual function table (VTable) that contains pointers to the most-derived implementations of the virtual functions. When a virtual function is called on a pointer or reference to the base class, the VTable is used to determine which factual implementation to invoke.
Example
class Shape {
public:
virtual void draw() {
cout << "Drawing a shape." << endl;
}
};
class Circle: public Shape {
public:
void draw() override {
cout << "Drawing a circle." << endl;
}
};
class Square: public Shape {
public:
void draw() override {
cout << "Drawing a square." << endl;
}
};
int main() {
Shape* shapePtr1 = new Circle();
Shape* shapePtr2 = new Square();
shapePtr1->draw();
shapePtr2->draw();
delete shapePtr1;
delete shapePtr2;
return 0;
}
In this illustration, the ‘draw()’ method is declared virtual in the ‘Shape’ base class, and its implementations are provided in the derived classes ‘Circle’ and ‘Square’. At runtime, when ‘draw()’ is called on ‘shapePtr1’, the ‘Circle’ class’s ‘draw()’ method is executed, and when it’s called on ‘shapePtr2’, the ‘Square’ class’s ‘draw()’ method is executed. This is dynamic polymorphism in action.
- Using Dynamic Polymorphism:
#include <iostream>
#include <vector>
#include "Item.h"
#include "Book.h"
#include "DVD.h"
int main() {
std::vector<Item*> items;
items.push_back(new Book("The Catcher in the Rye", "J.D. Salinger", 1951));
items.push_back(new DVD("Inception", 148));
for (const auto& item : items) {
item->DisplayInfo();
}
for (const auto& item : items) {
delete item;
}
return 0;
}
In this example, we have a base class ‘Item’ with a virtual function ‘DisplayInfo()’. The derived classes ‘Book’ and ‘DVD’ override this function to provide their specific implementations. When we create objects of the derived classes and store them in a vector of ‘Item*’ pointers, we can call the ‘DisplayInfo()’ function on each object, and the correct overridden version of the function will be called at runtime based on the actual type of the object.
Dynamic polymorphism allows for further flexible and extensible code, as new subclasses can be added without changing existing code and can participate in the polymorphic behaviour defined in the base class.