BIT153 Object Oriented Programming

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 virtual keywords 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 Order class.

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 Classes

Why 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

AnimalDog
Virtual function table (vtable) showing Animal's virtual speak() and Dog's override

How It Works

  1. Base class declares a function as virtual.
  2. Derived class overrides it with its own implementation.
  3. 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++) or abstract (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 note
Class hierarchy for Ncell-like payroll system with polymorphism

Base 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., Shape hierarchy).

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

  1. 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 from PaymentMethod and overrides processPayment().
    • Example: When a user selects "Khalti," the system calls KhaltiPayment::processPayment(), which routes to Khalti’s API.
inheritsinheritsinheritsGUIComponentButtonTextBoxCheckBox
Polymorphism in GUI frameworks (e.g., Qt, Java Swing)
  1. NEPSE Stock Market Orders

    • Idea: Different order types (limit, market, stop-loss) share a base Order class with a pure virtual execute() function.
    • How: Each derived class (e.g., LimitOrder, MarketOrder) implements execute() differently.
    • Worked Example:
      class Order {
      public:
          virtual void execute() const = 0;
      };
      class LimitOrder : public Order {
      public:
          void execute() const override { cout << "Execute at limit price\n"; }
      };
      
      When NEPSE processes orders, it calls order->execute(), and the correct logic runs based on the order type.
  2. Pathao Ride Dispatching

    • Idea: Pathao uses polymorphism to dispatch drivers based on vehicle type (bike, car, van).
    • How: A Vehicle base class with drive() is overridden by Bike, 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:

    1. Difference between early and runtime binding (table above).
    2. Virtual functions: When to use virtual, override, and pure virtual (= 0).
    3. Abstract classes: Why they cannot be instantiated and how they enforce implementation.
    4. Code implementation: Always include a virtual destructor in base classes with dynamic memory.
    5. Real-world mapping: Relate polymorphism to systems like payment gateways or order processing (e.g., NEPSE, Pathao).
  • Common Pitfalls:

    • Forgetting the virtual keyword → early binding (wrong answer).
    • Not overriding virtual functions correctly → compile-time errors.
    • Using polymorphism where overloading would suffice (e.g., operator overloading for Fraction).
  • Question Patterns:

    • "Explain runtime binding with an example." → Use the Animal/Dog example.
    • "Implement a class hierarchy with polymorphism." → Follow the Employee/SalariedEmployee structure.
    • "Why is a pure virtual function needed?" → To enforce implementation in derived classes (e.g., Shape::area()).

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
    end

In 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 base PaymentMethod class, allowing dynamic resolution of payment logic at runtime.
  • NEPSE (Nepal Stock Exchange): Applies runtime binding for order processing. A base Order class with virtual execute() method is overridden by BuyOrder and SellOrder classes. The exchange system calls order.execute() without knowing the concrete order type.
  • Ncell Payroll System: Implements abstract classes for employee types. The Employee base class declares a pure virtual calculateSalary(), 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…