Site icon Afzal Badshah, PhD

Abstract Classes and Interfaces — Designing Extensible Frameworks

In our previous lecture, we explored virtual and pure virtual functions.
We saw how a virtual function allows different derived classes to perform the same action in their own way — like different employees performing their duties differently.

We also discovered that a pure virtual function acts as a promise — a function that must be implemented by every derived class.
This is where abstract classes emerge.

An abstract class can be imagined as a template or blueprint — it tells us what must exist, but not how it works.
It serves as a foundation for building extensible frameworks, where future developers can add new components without changing the existing code.

Why Abstraction Is Needed

In daily life, abstraction is everywhere.
Think of the employee structure in a large organization. Every employee — teacher, developer, or manager — follows the same base rules defined by the organization.
The organization acts as a standard interface, but what each device does with that electricity differs.

This is the heart of abstraction:

“Define what should be done, but let others decide how it should be done.”

When designing software frameworks — whether for a school management system or a smart city simulator — abstraction ensures that the system remains open for growth.
New modules can join the system as long as they follow the same contract.

Understanding Abstract Classes and Interfaces

Abstract Class

An abstract class is a class that cannot be instantiated directly.
It may have both normal and pure virtual functions.

“An abstract class defines a contract for derived classes — it says what they must do but may partially define how.”

Interface

An interface is a special type of abstract class that has only pure virtual functions — no data, no implementation.

“An interface is a promise — it defines behavior but leaves the complete implementation to others.”

So in C++:

Previously, we created an example where an Employee class had a virtual function performDuty() that was implemented differently by each employee type (like Teacher and Developer).

Now we will extend that same idea to understand abstract classes and interfaces.

Abstract Class Example

Let’s begin by making Employee an abstract class.
We’ll add a pure virtual function to define a common responsibility that each employee must perform.

#include <iostream>
using namespace std;

// Abstract Base Class
class Employee {
public:
    virtual void performDuty() = 0;   // Pure virtual function
    virtual void report() {           // Normal function
        cout << "Employee reporting to the office." << endl;
    }
};

The presence of = 0 makes this function pure virtual, and thus, Employee becomes an abstract class.
It cannot be instantiated directly.
Any class that inherits from it must define its own version of performDuty().

Let’s create two types of employees:

class Teacher : public Employee {
public:
    void performDuty() override {
        cout << "Teacher is delivering a lecture to students." << endl;
    }
};

class Developer : public Employee {
public:
    void performDuty() override {
        cout << "Developer is writing code for the new application." << endl;
    }
};

Now, we can use polymorphism to handle all employees through a single interface:

int main() {
    Employee* e1 = new Teacher();
    Employee* e2 = new Developer();

    e1->performDuty();
    e2->performDuty();

    e1->report();  // Normal function from abstract base class

    delete e1;
    delete e2;
    return 0;
}

🔍 Output:

Teacher is delivering a lecture to students.
Developer is writing code for the new application.
Employee reporting to the office.

In this setup:

This is extensibility — a fundamental goal in software architecture.

Interface: When We Need Only the Contract

Sometimes we don’t even need partial implementation — we just need to define behavior that different classes can “agree” to follow.

For instance, in a school system, some employees receive payments.
Others may not (like unpaid interns).
Let’s define an interface for that:

class Payable {
public:
    virtual void processPayment() = 0;
    virtual ~Payable() {} // Always define a virtual destructor in interfaces
};

Now, Teacher and Developer can “implement” this interface:

class Teacher : public Employee, public Payable {
public:
    void performDuty() override {
        cout << "Teacher is delivering a lecture." << endl;
    }
    void processPayment() override {
        cout << "Teacher receives monthly salary." << endl;
    }
};

class Developer : public Employee, public Payable {
public:
    void performDuty() override {
        cout << "Developer is writing project code." << endl;
    }
    void processPayment() override {
        cout << "Developer receives project-based payment." << endl;
    }
};

And in main():

int main() {
    Payable* p1 = new Teacher();
    Payable* p2 = new Developer();

    p1->processPayment();
    p2->processPayment();

    delete p1;
    delete p2;
    return 0;
}

🔍 Output:

Teacher receives monthly salary.
Developer receives project-based payment.

This structure allows one class to play multiple roles:

C++ supports multiple inheritance safely in such interface scenarios because interfaces typically have no data — only pure virtual functions.

This concept is used widely in real frameworks:

TypeCan Be InstantiatedContains Pure Virtual FunctionsMay Have ImplementationTypical Purpose
Concrete Class✅ Yes❌ No✅ YesFully defined behavior and data
Abstract Class❌ No✅ At least one✅ May have partial implementationBase class defining structure for derived classes
Interface❌ No✅ All❌ NoDefines contracts or roles that multiple classes can adopt

Self-Assessment

Conceptual:

  1. Can we create an object of an abstract class? Why not?
  2. How can we represent interfaces in C++?
  3. Why do interfaces often have virtual destructors?
  4. What’s the advantage of having multiple interfaces?

Coding Task:

Exit mobile version