OOP Concepts for System Design
Last Updated :
16 Aug, 2025
Object-Oriented Programming (OOP) is a programming paradigm that helps you write modular, reusable, and scalable code by organizing it around classes (blueprints) and objects (instances of those blueprints).
In simple terms, OOP is about designing a system as a collection of objects each with its own data (state) and methods (behaviors) that interact to solve problems.
OOPs ConceptsWhy OOP is Needed:
Before OOP, when the code size grows and multiple people work on a project, there problems arise
- Changing in one team's code causing other codes to break. Hence difficult to maintain.
- Large number of parameters during function calls.
- Difficult to divide and maintain code across teams.
- Limited Code Reusability
- Not scalable as the code is not modular.
Real-world Examples of OOPs in System Design:
- Banking System
- E-commerce Platform
- Hospital Management System
Classes and Objects in OOP
Classes and objects are fundamental concepts in object-oriented programming (OOP), which is a system design approach used to simulate real-world items and their interactions
1. Classes
A class is a template or blueprint used to create objects. It specifies the characteristics (properties) and actions (methods) that objects of that class will have.
Attributes: Data an object holds (e.g., make, model, year in a Car class).
Methods: Actions an object can perform (e.g., startEngine(), accelerate()).
Classes and Objects2. Objects
An object is an instance of a class. It is created based on the blueprint defined by the class.
- Attributes: These are the values represent the state of the object. For example, an object of the "Car" class might have attributes like "make" = "Toyota," "model" = "Camry," and "year" = 2022.
- Methods: To carry out certain tasks or processes, objects can call methods defined by their class. These methods can change the object's state and work with its data (attributes). The "start_engine," "accelerate," and "turn_off_engine" methods, for instance, can be used to manage the behavior of an object of the "Car" class.
Example:
C++
#include <iostream>
using namespace std;
class Student {
public:
int roll;
string name;
void takeLeave() {
cout << "on leave" << endl;
}
void bunkClass() {
cout << "Go out and play: " << endl;
}
};
int main() {
Student sid;
sid.bunkClass();
sid.name = "Siddhartha Hazra";
cout << sid.name << endl;
return 0;
}
C
#include <stdio.h>
#include <string.h>
typedef struct Student {
int roll;
char name[50];
} Student;
void takeLeave(Student *s) {
printf("on leave\n");
}
void bunkClass(Student *s) {
printf("Go out and play: \n");
}
int main() {
Student sid;
bunkClass(&sid);
strcpy(sid.name, "Siddhartha Hazra");
printf("%s\n", sid.name);
return 0;
}
Java
class Student {
int roll;
String name;
void takeLeave() {
System.out.println("on leave");
}
void bunkClass() {
System.out.println("Go out and play: ");
}
}
public class Main {
public static void main(String args[]) {
Student sid = new Student();
sid.bunkClass();
sid.name = "Siddhartha Hazra";
System.out.println(sid.name);
}
}
Python
class Student:
def __init__(self):
self.roll = None
self.name = None
def takeLeave(self):
print("on leave")
def bunkClass(self):
print("Go out and play: ")
sid = Student()
sid.bunkClass()
sid.name = "Siddhartha Hazra"
print(sid.name)
JavaScript
class Student {
constructor() {
this.roll = null;
this.name = null;
}
takeLeave() {
console.log('on leave');
}
bunkClass() {
console.log('Go out and play: ');
}
}
const sid = new Student();
sid.bunkClass();
sid.name = 'Siddhartha Hazra';
console.log(sid.name);
Encapsulation in OOP
Encapsulation is the process of combining data and methods for working with the data into a single unit called a class. It makes it possible to hide a class's implementation details from outside users who engage with the class via its public interface.
Encapsulation- Class as a Unit of Encapsulation: Classes include information (attributes) and actions (methods) associated with a particular entity or concept. The class's public methods allow users to interact with it without having to understand the inner workings of those methods.
- Access Modifiers: Access modifiers that regulate the visibility of class members (attributes and methods), such as public, private and protected, are used to enforce encapsulation. Private members can only be reached from within the classroom, whilst public members can be reached from outside.
Example:
C++
#include <iostream>
#include <string>
class Employee {
private:
int id;
std::string name;
public:
void setId(int id) {
this->id = id;
}
void setName(std::string name) {
this->name = name;
}
int getId() {
return id;
}
std::string getName() {
return name;
}
};
int main() {
Employee emp;
// Using setters
emp.setId(101);
emp.setName("Geek");
// Using getters
std::cout << "Employee ID: " << emp.getId() << std::endl;
std::cout << "Employee Name: " << emp.getName() << std::endl;
return 0;
}
C
#include <stdio.h>
#include <string.h>
typedef struct Employee {
int id;
char name[50];
} Employee;
void setId(Employee* emp, int id) {
emp->id = id;
}
void setName(Employee* emp, const char* name) {
strcpy(emp->name, name);
}
int getId(Employee* emp) {
return emp->id;
}
const char* getName(Employee* emp) {
return emp->name;
}
int main() {
Employee emp;
// Using setters
setId(&emp, 101);
setName(&emp, "Geek");
// Using getters
printf("Employee ID: %d\n", getId(&emp));
printf("Employee Name: %s\n", getName(&emp));
return 0;
}
Java
class Employee {
// Private fields (encapsulated data)
private int id;
private String name;
// Setter methods
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
// Getter methods
public int getId() {
return id;
}
public String getName() {
return name;
}
}
public class Main {
public static void main(String[] args) {
Employee emp = new Employee();
// Using setters
emp.setId(101);
emp.setName("Geek");
// Using getters
System.out.println("Employee ID: " + emp.getId());
System.out.println("Employee Name: " + emp.getName());
}
}
Python
class Employee:
def __init__(self):
self.__id = None
self.__name = None
def set_id(self, id):
self.__id = id
def set_name(self, name):
self.__name = name
def get_id(self):
return self.__id
def get_name(self):
return self.__name
emp = Employee()
# Using setters
emp.set_id(101)
emp.set_name("Geek")
# Using getters
print(f'Employee ID: {emp.get_id()}')
print(f'Employee Name: {emp.get_name()}')
JavaScript
class Employee {
constructor() {
this.id = null;
this.name = null;
}
setId(id) {
this.id = id;
}
setName(name) {
this.name = name;
}
getId() {
return this.id;
}
getName() {
return this.name;
}
}
const emp = new Employee();
// Using setters
emp.setId(101);
emp.setName('Geek');
// Using getters
console.log('Employee ID: ' + emp.getId());
console.log('Employee Name: ' + emp.getName());
Abstraction In OOP
Abstraction is the process of concentrating on an object's or system's key features while disregarding unimportant elements. It enables programmers to produce models that simply and easily convey the core of real-world objects and ideas. We can achieve abstraction in two ways:
- Abstract Class: Abstract classes provide a way to create blueprints for objects without providing complete implementations. They serve as templates for other classes to inherit from, defining common behaviors and attributes that subclasses can extend and customize.
- Using Interface: Interfaces serve as blueprints for classes, defining a set of method signatures without specifying their implementations. Unlike classes, interfaces cannot contain instance fields but can include constants. They provide a way to achieve abstraction.
Example:
C++
#include <iostream>
// Abstract class
class Vehicle {
public:
// Pure virtual functions
virtual void accelerate() = 0;
virtual void brake() = 0;
// Concrete method
void startEngine() {
std::cout << "Engine started!" << std::endl;
}
};
// Concrete class
class Car : public Vehicle {
public:
void accelerate() override {
std::cout << "Car: Pressing gas pedal..." << std::endl;
// Hidden complex logic: fuel injection, gear shifting, etc.
}
void brake() override {
std::cout << "Car: Applying brakes..." << std::endl;
// Hidden logic: hydraulic pressure, brake pads, etc.
}
};
int main() {
Vehicle* myCar = new Car();
myCar->startEngine();
myCar->accelerate();
myCar->brake();
delete myCar;
return 0;
}
C
#include <stdio.h>
// Abstract class equivalent using struct and function pointers
typedef struct {
void (*accelerate)(struct Vehicle*);
void (*brake)(struct Vehicle*);
void (*startEngine)(struct Vehicle*);
} Vehicle;
// Concrete methods
void startEngine(Vehicle* self) {
printf("Engine started!\n");
}
void carAccelerate(Vehicle* self) {
printf("Car: Pressing gas pedal...\n");
// Hidden complex logic: fuel injection, gear shifting, etc.
}
void carBrake(Vehicle* self) {
printf("Car: Applying brakes...\n");
// Hidden logic: hydraulic pressure, brake pads, etc.
}
int main() {
Vehicle myCar = {
.accelerate = carAccelerate,
.brake = carBrake,
.startEngine = startEngine
};
myCar.startEngine(&myCar);
myCar.accelerate(&myCar);
myCar.brake(&myCar);
return 0;
}
Java
abstract class Vehicle {
// Abstract methods (what it can do)
abstract void accelerate();
abstract void brake();
// Concrete method (common to all vehicles)
void startEngine() {
System.out.println("Engine started!");
}
}
// Concrete implementation (hidden details)
class Car extends Vehicle {
@Override
void accelerate() {
System.out.println("Car: Pressing gas pedal...");
// Hidden complex logic: fuel injection, gear shifting, etc.
}
@Override
void brake() {
System.out.println("Car: Applying brakes...");
// Hidden logic: hydraulic pressure, brake pads, etc.
}
}
public class Main {
public static void main(String[] args) {
Vehicle myCar = new Car();
myCar.startEngine();
myCar.accelerate();
myCar.brake();
}
}
Python
from abc import ABC, abstractmethod
# Abstract class
class Vehicle(ABC):
@abstractmethod
def accelerate(self):
pass
@abstractmethod
def brake(self):
pass
def startEngine(self):
print("Engine started!")
# Concrete class
class Car(Vehicle):
def accelerate(self):
print("Car: Pressing gas pedal...")
# Hidden complex logic: fuel injection, gear shifting, etc.
def brake(self):
print("Car: Applying brakes...")
# Hidden logic: hydraulic pressure, brake pads, etc.
# Main execution
if __name__ == "__main__":
myCar = Car()
myCar.startEngine()
myCar.accelerate()
myCar.brake()
JavaScript
// Abstract class
class Vehicle {
constructor() {
if (this.constructor === Vehicle) {
throw new Error("Abstract classes can't be instantiated.");
}
}
// Abstract methods
accelerate() {
throw new Error("Method 'accelerate()' must be implemented.");
}
brake() {
throw new Error("Method 'brake()' must be implemented.");
}
// Concrete method
startEngine() {
console.log('Engine started!');
}
}
// Concrete class
class Car extends Vehicle {
accelerate() {
console.log('Car: Pressing gas pedal...');
// Hidden complex logic: fuel injection, gear shifting, etc.
}
brake() {
console.log('Car: Applying brakes...');
// Hidden logic: hydraulic pressure, brake pads, etc.
}
}
// Main execution
const myCar = new Car();
myCar.startEngine();
myCar.accelerate();
myCar.brake();
Inheritance in OOP
A class (subclass or derived class) can inherit properties and methods from another class (superclass or base class) through inheritance. While retaining its common characteristics, the subclass has the ability to add or change the superclass's functionality.
InheritanceSyntax:
// Superclass
class Superclass {
// Superclass members
}
// Subclass
class Subclass extends Superclass {
// Subclass members
}
Example:
C++
// Superclass (Parent)
#include<iostream>
class Animal {
public:
void eat() {
std::cout << "Animal is eating..." << std::endl;
}
void sleep() {
std::cout << "Animal is sleeping..." << std::endl;
}
};
// Subclass (Child) - Inherits from Animal
class Dog : public Animal {
public:
void bark() {
std::cout << "Dog is barking!" << std::endl;
}
};
int main() {
Dog myDog;
// Inherited methods (from Animal)
myDog.eat();
myDog.sleep();
// Child class method
myDog.bark();
return 0;
}
C
// Superclass (Parent)
#include <stdio.h>
void eat() {
printf("Animal is eating...\n");
}
void sleep() {
printf("Animal is sleeping...\n");
}
void bark() {
printf("Dog is barking!\n");
}
int main() {
// Inherited methods (from Animal)
eat();
sleep();
// Child class method
bark();
return 0;
}
Java
// Superclass (Parent)
class Animal {
void eat() {
System.out.println("Animal is eating...");
}
void sleep() {
System.out.println("Animal is sleeping...");
}
}
// Subclass (Child) - Inherits from Animal
class Dog extends Animal {
void bark() {
System.out.println("Dog is barking!");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
// Inherited methods (from Animal)
myDog.eat();
myDog.sleep();
// Child class method
myDog.bark();
}
}
Python
# Superclass (Parent)
class Animal:
def eat(self):
print("Animal is eating...")
def sleep(self):
print("Animal is sleeping...")
# Subclass (Child) - Inherits from Animal
class Dog(Animal):
def bark(self):
print("Dog is barking!")
# Main
if __name__ == '__main__':
myDog = Dog()
# Inherited methods (from Animal)
myDog.eat()
myDog.sleep()
# Child class method
myDog.bark()
JavaScript
// Superclass (Parent)
class Animal {
eat() {
console.log('Animal is eating...');
}
sleep() {
console.log('Animal is sleeping...');
}
}
// Subclass (Child) - Inherits from Animal
class Dog extends Animal {
bark() {
console.log('Dog is barking!');
}
}
// Main
const myDog = new Dog();
// Inherited methods (from Animal)
myDog.eat();
myDog.sleep();
// Child class method
myDog.bark();
Polymorphism in OOP
It makes code reuse, extension and flexibility possible by treating objects of different classes as belonging to the same superclass. Through a standard interface, it enables uniform treatment of objects of various categories. It allows the same code to work with several kinds of objects.
- Method Overriding: Method overriding, in which subclasses offer their own implementation of a method defined in their superclass, is a common way to establish polymorphism. Depending on the object's real type, the runtime environment chooses which implementation to call when a method is called on it.
- Interface-Based Polymorphism: Another way to accomplish polymorphism is by using interfaces or abstract classes, in which case several classes extend the same abstract class or implement the same interface.
- Method Overloading: This is a feature that allows a class to have multiple methods with the same name but different parameters. This enables developers to create methods that perform similar tasks but operate on different types of input or different numbers of parameters.
Example:
C++
#include <iostream>
class Calculator {
public:
// Method for adding two integers
int add(int a, int b) {
return a + b;
}
// Overloaded method for adding three integers
int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method for adding two doubles
double add(double a, double b) {
return a + b;
}
};
int main() {
Calculator myCalculator;
// Example usage of the overloaded methods
int sum1 = myCalculator.add(5, 3);
int sum2 = myCalculator.add(4, 6, 2);
double sum3 = myCalculator.add(3.5, 2.7);
// Output the results
std::cout << "Sum of 5 and 3: " << sum1 << std::endl;
std::cout << "Sum of 4, 6, and 2: " << sum2 << std::endl;
std::cout << "Sum of 3.5 and 2.7: " << sum3 << std::endl;
return 0;
}
C
#include <stdio.h>
int add_int(int a, int b) {
return a + b;
}
int add_int_three(int a, int b, int c) {
return a + b + c;
}
double add_double(double a, double b) {
return a + b;
}
int main() {
// Example usage of the functions
int sum1 = add_int(5, 3);
int sum2 = add_int_three(4, 6, 2);
double sum3 = add_double(3.5, 2.7);
// Output the results
printf("Sum of 5 and 3: %d\n", sum1);
printf("Sum of 4, 6, and 2: %d\n", sum2);
printf("Sum of 3.5 and 2.7: %.1f\n", sum3);
return 0;
}
Java
public class Calculator {
// Method for adding two integers
public int add(int a, int b) {
return a + b;
}
// Overloaded method for adding three integers
public int add(int a, int b, int c) {
return a + b + c;
}
// Overloaded method for adding two doubles
public double add(double a, double b) {
return a + b;
}
public static void main(String[] args) {
Calculator myCalculator = new Calculator();
// Example usage of the overloaded methods
int sum1 = myCalculator.add(5, 3);
int sum2 = myCalculator.add(4, 6, 2);
double sum3 = myCalculator.add(3.5, 2.7);
// Output the results
System.out.println("Sum of 5 and 3: " + sum1);
System.out.println("Sum of 4, 6, and 2: " + sum2);
System.out.println("Sum of 3.5 and 2.7: " + sum3);
}
}
JavaScript
class Calculator {
// Method for adding two integers
add(a, b) {
return a + b;
}
// Overloaded method for adding three integers
add(a, b, c) {
if (arguments.length === 3) {
return a + b + c;
}
return this.add(a, b);
}
// Overloaded method for adding two doubles
add(a, b) {
if (typeof a === 'number' && typeof b === 'number') {
return a + b;
}
return this.add(a, b);
}
}
const myCalculator = new Calculator();
// Example usage of the overloaded methods
const sum1 = myCalculator.add(5, 3);
const sum2 = myCalculator.add(4, 6, 2);
const sum3 = myCalculator.add(3.5, 2.7);
// Output the results
console.log('Sum of 5 and 3:', sum1);
console.log('Sum of 4, 6, and 2:', sum2);
console.log('Sum of 3.5 and 2.7:', sum3);
Note: Python does not support method overloading in the same direct way that languages like Java or C++ do. Here, only the last definition will be accessible and callable as it "override" any previously defined methods with the same name.
Next Reads :
Introduction to OOP (Object Oriented Programming)
Explore
What is System Design
System Design Fundamentals
Scalability in System Design
Databases in Designing Systems
High Level Design(HLD)
Low Level Design(LLD)
Design Patterns
Interview Guide for System Design
System Design Interview Questions & Answers