Object Oriented ProgrammingUnit 512 min read
Polymorphism: Runtime Binding, Virtual Functions & Inheritance Flexibility
Unit 5 of Object Oriented Programming: Explores polymorphism—how one interface behaves differently based on object type—with runtime binding, virtual functions, pure virtual classes, and practical examples like employee salary calculations and payment processing systems.
TAKEAWAYS:
- Polymorphism enables one function call to invoke different implementations based on object type at runtime.
- Runtime binding (dynamic polymorphism) uses
virtualkeywords and resolves function calls during execution. - Pure virtual functions force derived classes to implement a function, enabling abstract base classes.
- Operator overloading and function overloading are unrelated to polymorphism but often confused.
- Real-world use: eSewa/Khalti use polymorphism to handle multiple payment methods (credit card, UPI, mobile wallet) via a unified
processPayment()interface. - NEPSE applies polymorphism to stock market orders (buy/sell) where each order type (limit, market) inherits from a base
Orderclass.
1. Definition and Core Concepts
Polymorphism (from Greek poly = many, morph = form) allows objects of different classes to be treated as objects of a common superclass while retaining their specific behaviors.
Key Types of Polymorphism
mindmap
root((Polymorphism))
Compile-time (Static)
Function Overloading
Operator Overloading
Run-time (Dynamic)
Method Overriding
Virtual Functions
Abstract ClassesWhy Polymorphism?
- Code Reusability: Write generic code that works for multiple derived classes.
- Extensibility: Add new classes without modifying existing code (Open/Closed Principle).
- Simplified Interface: Clients interact with base-class interfaces without knowing derived-class specifics.
2. Runtime Binding vs. Early Binding
| Feature | Early Binding (Static) | Runtime Binding (Dynamic) |
|---|---|---|
| Resolution Time | Compile-time | Runtime (execution-time) |
| Keyword | None (default) | virtual (C++) / @Override (Java) |
| Flexibility | Less flexible (fixed at compile) | Highly flexible (resolved dynamically) |
| Performance | Faster (no runtime lookup) | Slightly slower (vtable lookup) |
| Example | void print() in base class |
virtual void print() in base class |
Example Code (C++):
#include <iostream>
using namespace std;
class Animal {
public:
virtual void speak() { cout << "Animal sound\n"; } // Runtime binding
};
class Dog : public Animal {
public:
void speak() override { cout << "Bark!\n"; }
};
int main() {
Animal* a = new Dog(); // Base-class pointer to derived object
a->speak(); // Output: "Bark!" (resolved at runtime)
delete a;
}
Trace Table:
| Step | Action | Output |
|---|---|---|
| 1 | Animal* a = new Dog() |
Pointer a points to Dog object |
| 2 | a->speak() |
Calls Dog::speak() (runtime) |
3. Virtual Functions and Method Overriding
How It Works
- Base class declares a function as
virtual. - Derived class overrides it with its own implementation.
- The vtable (virtual table) stores function pointers for dynamic resolution.
Figure: Virtual Function Mechanism
figure:
Base Class (Animal)
|-- vtable: [speak() → Animal::speak]
Derived Class (Dog)
|-- vtable: [speak() → Dog::speak]
When a->speak() is called (where a is a Dog object), the vtable lookup directs to Dog::speak().
4. Pure Virtual Functions and Abstract Classes
Pure Virtual Functions
- Declared with
= 0(C++) orabstract(Java). - Forces derived classes to implement the function.
- Cannot instantiate an abstract class.
Example:
class Shape {
public:
virtual double area() const = 0; // Pure virtual
};
class Circle : public Shape {
public:
double area() const override { return 3.14 * r * r; }
};
Abstract Classes vs. Interfaces
| Feature | Abstract Class (C++) | Interface (Java/Python) |
|---|---|---|
| State | Can have data members | No data members (pure abstraction) |
| Inheritance | Single inheritance only (C++) | Multiple inheritance allowed |
| Example | Shape (with color field) |
Comparable (no fields) |
5. Worked Example: Employee Salary Calculation
Scenario: A company (like Ncell) pays employees differently—salaried employees get a fixed wage, while hourly employees earn per hour. Use polymorphism to calculate salaries dynamically.
classDiagram
class Employee {
<<abstract>>
+calculateSalary()
}
class SalariedEmployee {
+calculateSalary()
}
class HourlyEmployee {
+calculateSalary()
}
Employee <|-- SalariedEmployee
Employee <|-- HourlyEmployee
note for Employee
Base class with virtual method
end note
note for SalariedEmployee
Overrides calculateSalary()
end note
note for HourlyEmployee
Overrides calculateSalary()
end noteClass hierarchy for Ncell-like payroll system with polymorphismBase Class (Employee)
class Employee {
public:
virtual double calculateSalary() const = 0; // Pure virtual
virtual ~Employee() {} // Virtual destructor for safe deletion
};
Derived Classes
class SalariedEmployee : public Employee {
double monthlySalary;
public:
SalariedEmployee(double salary) : monthlySalary(salary) {}
double calculateSalary() const override { return monthlySalary; }
};
class HourlyEmployee : public Employee {
double hoursWorked;
double rate;
public:
HourlyEmployee(double h, double r) : hoursWorked(h), rate(r) {}
double calculateSalary() const override { return hoursWorked * rate; }
};
Client Code (Payroll System)
int main() {
Employee* emp1 = new SalariedEmployee(5000);
Employee* emp2 = new HourlyEmployee(40, 25);
cout << "Salaried: $" << emp1->calculateSalary() << endl; // $5000
cout << "Hourly: $" << emp2->calculateSalary() << endl; // $1000
delete emp1; delete emp2;
}
Trace Table:
| Employee Type | calculateSalary() Call |
Output |
|---|---|---|
SalariedEmployee |
Calls SalariedEmployee::calculateSalary() |
$5000 |
HourlyEmployee |
Calls HourlyEmployee::calculateSalary() |
$1000 |
Real-World Tie: This mirrors how Ncell processes employee payrolls—salaried staff (e.g., managers) vs. hourly workers (e.g., customer support).
6. Operator Overloading and Polymorphism
Misconception: Operator overloading is not polymorphism. It changes how operators behave for user-defined types (e.g., + for Fraction objects), but it doesn’t enable dynamic behavior like method overriding.
Example:
class Fraction {
int numerator, denominator;
public:
Fraction(int n, int d) : numerator(n), denominator(d) {}
Fraction operator+(const Fraction& other) {
return Fraction(numerator * other.denominator + other.numerator * denominator,
denominator * other.denominator);
}
};
This is not polymorphism—it’s a compile-time feature.
7. Advantages and Disadvantages
Advantages
- Extensibility: Add new derived classes without modifying existing code.
- Maintainability: Changes in derived classes don’t affect base-class clients.
- Flexibility: Treat unrelated objects uniformly (e.g.,
Shapehierarchy).
Disadvantages
- Performance Overhead: Virtual functions use vtables, adding slight runtime cost.
- Complexity: Overuse can make code harder to debug (e.g., "slicing" issues).
- Design Risk: Deep inheritance hierarchies can become unwieldy.
8. In the Real World
- eSewa/Khalti Payment Gateway
- Idea: Polymorphism handles multiple payment methods (credit card, UPI, mobile wallet) via a unified
processPayment()interface. - How: Each payment type (e.g.,
CreditCardPayment,UPIPayment) inherits fromPaymentMethodand overridesprocessPayment(). - Example: When a user selects "Khalti," the system calls
KhaltiPayment::processPayment(), which routes to Khalti’s API.
- Idea: Polymorphism handles multiple payment methods (credit card, UPI, mobile wallet) via a unified
NEPSE Stock Market Orders
- Idea: Different order types (limit, market, stop-loss) share a base
Orderclass with a pure virtualexecute()function. - How: Each derived class (e.g.,
LimitOrder,MarketOrder) implementsexecute()differently. - Worked Example:
When NEPSE processes orders, it callsclass Order { public: virtual void execute() const = 0; }; class LimitOrder : public Order { public: void execute() const override { cout << "Execute at limit price\n"; } };order->execute(), and the correct logic runs based on the order type.
- Idea: Different order types (limit, market, stop-loss) share a base
Pathao Ride Dispatching
- Idea: Pathao uses polymorphism to dispatch drivers based on vehicle type (bike, car, van).
- How: A
Vehiclebase class withdrive()is overridden byBike,Car, etc. - Real Scenario: If a rider requests a "car," Pathao’s system calls
Car::drive(), which checks availability and routes the request to available car drivers.
9. Exam Tip
Focus on:
- Difference between early and runtime binding (table above).
- Virtual functions: When to use
virtual,override, and pure virtual (= 0). - Abstract classes: Why they cannot be instantiated and how they enforce implementation.
- Code implementation: Always include a
virtualdestructor in base classes with dynamic memory. - Real-world mapping: Relate polymorphism to systems like payment gateways or order processing (e.g., NEPSE, Pathao).
Common Pitfalls:
- Forgetting the
virtualkeyword → early binding (wrong answer). - Not overriding
virtualfunctions correctly → compile-time errors. - Using polymorphism where overloading would suffice (e.g., operator overloading for
Fraction).
- Forgetting the
Question Patterns:
- "Explain runtime binding with an example." → Use the
Animal/Dogexample. - "Implement a class hierarchy with polymorphism." → Follow the
Employee/SalariedEmployeestructure. - "Why is a pure virtual function needed?" → To enforce implementation in derived classes (e.g.,
Shape::area()).
- "Explain runtime binding with an example." → Use the
Final Visual: Polymorphism in Action (eSewa Payment Flow)
sequenceDiagram
participant User
participant eSewa
participant PaymentMethod
participant CreditCardPayment
participant UPIPayment
User->>eSewa: Select "Khalti"
eSewa->>PaymentMethod: processPayment()
alt Credit Card
PaymentMethod->>CreditCardPayment: processPayment()
CreditCardPayment-->>eSewa: Redirect to card gateway
else UPI
PaymentMethod->>UPIPayment: processPayment()
UPIPayment-->>eSewa: Redirect to UPI app
endIn the real world
- eSewa/Khalti: Uses polymorphism via a unified
processPayment()interface in their backend systems. Each payment method (credit card, UPI, mobile wallet) inherits from a basePaymentMethodclass, allowing dynamic resolution of payment logic at runtime. - NEPSE (Nepal Stock Exchange): Applies runtime binding for order processing. A base
Orderclass with virtualexecute()method is overridden byBuyOrderandSellOrderclasses. The exchange system callsorder.execute()without knowing the concrete order type. - Ncell Payroll System: Implements abstract classes for employee types. The
Employeebase class declares a pure virtualcalculateSalary(), forcing derived classes (SalariedEmployee,HourlyEmployee) to implement their own logic, ensuring consistent salary calculation across all employee types.
Based on the TU BIT syllabus for Object Oriented Programming (BIT153), unit 5.
Discussion
Loading…