Skip to main content

Concept & Theory

1. Theory

1. Theoretical Background

1.1 Definition of OOP

Object-Oriented Programming (OOP) is a programming paradigm that uses the concept of objects.
An object combines data (attributes) and behavior (methods) into a single unit.

The four main pillars of OOP:

  • Encapsulation → Hiding data implementation using restricted access through setter/getter.
  • Abstraction → Hiding implementation details while exposing only relevant interfaces.
  • Inheritance → Derived classes can inherit attributes and methods from a base class.
  • Polymorphism → The ability of objects to take many forms (e.g., method overriding).

1.2 Encapsulation

Encapsulation is one of the four fundamental principles of Object-Oriented Programming (OOP).
It refers to the process of wrapping attributes (data members) and behaviors (methods) inside a class and restricting direct access to the data from outside.

Instead of accessing attributes directly, developers use getter and setter functions.
This provides better data security, controlled access, and modularity in software design.

class Device {
private:
    int batteryLevel; // accessible only via setter/getter
public:
    void setBattery(int b) { batteryLevel = b; }
    int getBattery() { return batteryLevel; }
};

image

Access Specifiers in Encapsulation

C++ provides three main access specifiers that control how members of a class can be accessed:

  1. Public Members declared as public can be accessed from anywhere in the program (inside and outside the class). Commonly used for interfaces, such as getters, setters, or functions that need to be available to other classes.
class Phone {
public:
    void call() {
        cout << "Calling..." << endl;
    }
};
  1. Protected Members declared as protected are only accessible within the class itself and its derived (child) classes. They are not accessible from outside the class. Commonly used in inheritance when you want child classes to access attributes, but still prevent external direct access.
class Gadget {
protected:
    int warrantyYears;
};

class Laptop : public Gadget {
public:
    void setWarranty(int w) {
        warrantyYears = w; // accessible here
    }
};
  1. Private Members declared as private are only accessible within the class itself. They are completely hidden from outside access, including derived classes. Commonly used for sensitive data that must be fully encapsulated.
class BankAccount {
private:
    double balance;
public:
    void deposit(double amount) {
        balance += amount;
    }
    double getBalance() {
        return balance;
    }
};

1.3 Abstraction

Abstraction allows a programmer to display only the essential features without revealing implementation details. In C++, this is often done with abstract classes and pure virtual functions.

class Vehicle {
public:
    virtual void move() = 0; // pure virtual → must be overridden by derived class
};

1.4 Pure Virtual Function

  • Normal virtual function → has a default implementation, can be overridden.
  • Pure virtual function → has no implementation and makes a class abstract.

Objects cannot be created from an abstract class; only from its derived classes.

#include <iostream>
using namespace std;

// Abstract base class
class Device {
public:
    // Pure virtual function → no implementation here
    virtual void start() = 0; 
};

// Derived class Smartphone
class Smartphone : public Device {
public:
    void start() override {
        cout << "Smartphone is starting with touch screen..." << endl;
    }
};

// Derived class Laptop
class Laptop : public Device {
public:
    void start() override {
        cout << "Laptop is starting with power button..." << endl;
    }
};

int main() {
    Device* d1 = new Smartphone();
    Device* d2 = new Laptop();

    d1->start(); // Output: Smartphone is starting with touch screen...
    d2->start(); // Output: Laptop is starting with power button...

    delete d1;
    delete d2;
    return 0;
}


1.5 Relevant SOLID Principles

  • Single Responsibility Principle (SRP) The Single Responsibility Principle (SRP) states that a class should only have one reason to change, meaning it should focus on a single responsibility. This avoids bloated classes that handle too many tasks at once. For example, instead of having one class that manages both appliance details and logging, these should be separated into two different classes.

    #include <iostream>
    #include <string>
    using namespace std;
    
    // SRP: Appliance handles only appliance-related data
    class Appliance {
    private:
        string name;
    public:
        Appliance(string n) : name(n) {}
        string getName() { return name; }
    };
    
    // Separate Logger class (not mixed inside Appliance)
    class Logger {
    public:
        void log(string message) {
            cout << "[LOG]: " << message << endl;
        }
    };
    
    int main() {
        Appliance fridge("Refrigerator");
        Logger logger;
    
        logger.log(fridge.getName() + " is running...");
        return 0;
    }
    
    
    Note : In this SRP example, the Appliance class is responsible only for appliance data, while the Logger class is responsible for logging activities. Each class has one clear responsibility.
    
  • Liskov Substitution Principle (LSP) The Liskov Substitution Principle (LSP) states that objects of a derived class should be substitutable for objects of their base class without breaking program behavior. In practice, this means that if a base class reference is used to call a function, it should work correctly with any derived class.

    #include <iostream>
    using namespace std;
    
    // Base class
    class Appliance {
    public:
        virtual void start() {
            cout << "Starting a generic appliance..." << endl;
        }
    };
    
    // Derived class WashingMachine
    class WashingMachine : public Appliance {
    public:
        void start() override {
            cout << "Washing machine is starting the wash cycle..." << endl;
        }
    };
    
    // Derived class Refrigerator
    class Refrigerator : public Appliance {
    public:
        void start() override {
            cout << "Refrigerator is cooling..." << endl;
        }
    };
    
    int main() {
        Appliance* a1 = new WashingMachine();
        Appliance* a2 = new Refrigerator();
    
        a1->start();  // Works as a WashingMachine
        a2->start();  // Works as a Refrigerator
    
        delete a1;
        delete a2;
        return 0;
    }
    
    Note : Here, both WashingMachine and Refrigerator can be substituted for Appliance without issues. This shows LSP because the derived classes behave correctly when used in place of the base class.