Skip to main content

Pillars of OOPs

Object-Oriented Programming (OOP) is built on four main pillars: Abstraction, Encapsulation, Inheritance, and Polymorphism. Each of these concepts plays a crucial role in creating modular, reusable, and maintainable code.


1. Abstraction​

Abstraction is the process of hiding the complex implementation details and showing only the essential features of an object. It allows developers to reduce complexity by providing a simplified interface.

Key Points​

  • Focuses on what an object does rather than how it does it.
  • Achieved through abstract classes and interfaces.

Example of Abstraction​

C++ Code
#include <iostream>
using namespace std;

class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};

class Circle : public Shape {
public:
void draw() {
cout << "Drawing Circle" << endl;
}
};

int main() {
Circle circle;
circle.draw(); // Calls the draw method
return 0;
}
Java Code
abstract class Shape {
abstract void draw(); // Abstract method
}

class Circle extends Shape {
void draw() {
System.out.println("Drawing Circle");
}
}

public class Main {
public static void main(String[] args) {
Circle circle = new Circle();
circle.draw(); // Calls the draw method
}
}
JavaScript Code
// In JavaScript, abstract methods are not natively supported, but we can simulate abstract behavior 
// by throwing an error in the base class method and forcing subclasses to implement it.
class Shape {
// Simulate an abstract method
draw() {
throw new Error("Method 'draw()' must be implemented");
}
}

class Circle extends Shape {
// Implementing the abstract method
draw() {
console.log("Drawing Circle");
}
}

// Main code
const circle = new Circle();
circle.draw(); // Output: Drawing Circle


2. Encapsulation​

Encapsulation is the bundling of data (attributes) and methods (functions) that operate on the data into a single unit called a class. It restricts direct access to some of the object's components, which is a means of preventing unintended interference and misuse.

Key Points​

  • Protects an object's state by restricting access to its internal data.
  • Achieved using access modifiers (private, protected, public).

Example of Encapsulation​

C++ Code
#include <iostream>
using namespace std;

class BankAccount {
private:
double balance; // Private data member

public:
BankAccount() : balance(0) {} // Constructor

void deposit(double amount) {
balance += amount;
}

void displayBalance() {
cout << "Balance: " << balance << endl;
}
};

int main() {
BankAccount account;
account.deposit(1000);
account.displayBalance(); // Displays the balance
return 0;
}
Java Code
class BankAccount {
private double balance; // Private data member

public BankAccount() {
balance = 0; // Constructor
}

public void deposit(double amount) {
balance += amount;
}

public void displayBalance() {
System.out.println("Balance: " + balance);
}
}

public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(1000);
account.displayBalance(); // Displays the balance
}
}
JavaScript Code
class BankAccount {
#balance; // Private field

constructor() {
this.#balance = 0; // Initialize balance in constructor
}

// Method to deposit money
deposit(amount) {
this.#balance += amount;
}

// Method to display balance
displayBalance() {
console.log(`Balance: ${this.#balance}`);
}
}

// Main code
const account = new BankAccount();
account.deposit(1000);
account.displayBalance(); // Output: Balance: 1000


3. Inheritance​

Inheritance is a mechanism that allows one class to inherit the properties and behaviors (methods) of another class. It promotes code reusability and establishes a hierarchical relationship between classes.

Key Points​

  • The class that inherits is called the derived class or child class, and the class being inherited from is called the base class or parent class.
  • Supports "is-a" relationship.

Example of Inheritance​

C++ Code
#include <iostream>
using namespace std;

class Animal {
public:
void eat() {
cout << "Eating..." << endl;
}
};

class Dog : public Animal { // Dog inherits from Animal
public:
void bark() {
cout << "Woof!" << endl;
}
};

int main() {
Dog dog;
dog.eat(); // Inherited method
dog.bark(); // Dog's own method
return 0;
}
Java Code
class Animal {
void eat() {
System.out.println("Eating...");
}
}

class Dog extends Animal { // Dog inherits from Animal
void bark() {
System.out.println("Woof!");
}
}

public class Main {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat(); // Inherited method
dog.bark(); // Dog's own method
}
}
JavaScript Code
class Animal {
eat() {
console.log("Eating...");
}
}

class Dog extends Animal { // Dog inherits from Animal
bark() {
console.log("Woof!");
}
}

// Main code
const dog = new Dog();
dog.eat(); // Inherited method
dog.bark(); // Dog's own method


4. Polymorphism​

Polymorphism allows methods to do different things based on the object it is acting upon. It means "many forms" and can be classified into two types: Compile-time polymorphism and Runtime polymorphism.

4.1 Compile-time Polymorphism​

Also known as method overloading, it occurs when multiple methods in the same class have the same name but different parameters.

Example of Compile-time Polymorphism​

C++ Code
#include <iostream>
using namespace std;

class Math {
public:
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}
};

int main() {
Math math;
cout << "Int Addition: " << math.add(5, 10) << endl; // Calls int version
cout << "Double Addition: " << math.add(5.5, 10.5) << endl; // Calls double version
return 0;
}
Java Code
class Math {
int add(int a, int b) {
return a + b;
}

double add(double a, double b) {
return a + b;
}
}

public class Main {
public static void main(String[] args) {
Math math = new Math();
System.out.println("Int Addition: " + math.add(5, 10)); // Calls int version
System.out.println("Double Addition: " + math.add(5.5, 10.5)); // Calls double version
}
}
JavaScript Code
// JavaScript does not support method overloading in the same way that Java does. 
// Instead, you can achieve similar functionality by using a single method that checks
// the types of its arguments and performs the appropriate operation.

class Math {
add(a, b) {
// Check types of a and b
if (typeof a === 'number' && typeof b === 'number') {
return a + b; // Addition
} else {
throw new Error("Invalid arguments: Both arguments must be numbers");
}
}
}

// Main code
const math = new Math();
console.log("Int Addition: " + math.add(5, 10)); // Output: Int Addition: 15
console.log("Double Addition: " + math.add(5.5, 10.5)); // Output: Double Addition: 16

4.2 Runtime Polymorphism​

Also known as method overriding, it occurs when a derived class provides a specific implementation of a method that is already defined in its base class. The decision about which method to call is made at runtime.

Example of Runtime Polymorphism​

C++ Code
#include <iostream>
using namespace std;

class Animal {
public:
virtual void sound() { // Virtual method
cout << "Animal sound" << endl;
}
};

class Dog : public Animal {
public:
void sound() override { // Override method
cout << "Woof!" << endl;
}
};

int main() {
Animal* animal = new Dog(); // Pointer to base class
animal->sound(); // Calls Dog's sound method
delete animal;
return 0;
}
Java Code
class Animal {
void sound() { // Base class method
System.out.println("Animal sound");
}
}

class Dog extends Animal {
void sound() { // Override method
System.out.println("Woof!");
}
}

public class Main {
public static void main(String[] args) {
Animal animal = new Dog(); // Reference to base class
animal.sound(); // Calls Dog's sound method
}
}
JavaScript Code
class Animal {
sound() { // Base class method
console.log("Animal sound");
}
}

class Dog extends Animal {
sound() { // Override method
console.log("Woof!");
}
}

// Main code
const animal = new Dog(); // Reference to base class
animal.sound(); // Calls Dog's sound method, Output: Woof!


Differences between Compile-time and Runtime Polymorphism​

FeatureCompile-time PolymorphismRuntime Polymorphism
DefinitionAchieved through method overloading.Achieved through method overriding.
Binding TimeResolved during compilation.Resolved during runtime.
Method ResolutionThe compiler determines which method to call.The JVM determines which method to call.
PerformanceGenerally faster due to early binding.Slightly slower due to late binding.
FlexibilityLess flexible as the decision is made at compile time.More flexible as the decision is made at runtime.
ExampleOverloading methods with different parameters.Overriding a method in a derived class.