Object-Oriented Programming (OOP) is one of the most widely used paradigms in modern software development.
Instead of organizing code around functions, OOP structures programs
around objects.
Each object contains data and the behavior that operates on that
data.
This approach helps developers build applications that are easier to maintain, reuse, and extend.
In this article we will explore the four core concepts of OOP:
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Each concept includes practical examples.
What is Object-Oriented Programming?
OOP is a programming paradigm that organizes code around objects.
An object usually contains:
- attributes (data)
- methods (functions that operate on the data)
Example in Python:
class Dog:
def __init__(self, name, breed):
self.name = name
self.breed = breed
def bark(self):
return f"{self.name} says Woof!"
my_dog = Dog("Buddy", "Golden Retriever")
print(my_dog.bark())
Dog is a class.
name and breed are attributes.
bark() is a method.
my_dog is an instance of the class.
The Four Pillars of OOP
The foundation of Object-Oriented Programming is often described using four principles:
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
1. Encapsulation
Encapsulation means combining data and methods in one class while restricting direct access to internal data.
Example in TypeScript:
class BankAccount {
private balance: number
private accountNumber: string
constructor(accountNumber: string, initialBalance: number) {
this.accountNumber = accountNumber
this.balance = initialBalance
}
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount
console.log(`Deposited $${amount}. Balance: $${this.balance}`)
}
}
public withdraw(amount: number): boolean {
if (amount > 0 && amount <= this.balance) {
this.balance -= amount
console.log(`Withdrew $${amount}. Balance: $${this.balance}`)
return true
}
return false
}
public getBalance(): number {
return this.balance
}
}
const account = new BankAccount("123456", 1000)
account.deposit(500)
account.withdraw(200)
Encapsulation helps:
- protect internal data
- control how data changes
- improve maintainability
2. Inheritance
Inheritance allows a class to reuse properties and methods from another class.
Example in Java:
public class Vehicle {
protected String brand;
protected int year;
public Vehicle(String brand, int year) {
this.brand = brand;
this.year = year;
}
public void start() {
System.out.println("Vehicle starting...");
}
}
Child class:
public class Car extends Vehicle {
private int numberOfDoors;
public Car(String brand, int year, int doors) {
super(brand, year);
this.numberOfDoors = doors;
}
@Override
public void start() {
System.out.println("Car engine starting...");
}
}
Inheritance allows code reuse and clearer relationships between classes.
3. Polymorphism
Polymorphism means one interface can have multiple implementations.
Example in Python:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
Implementation example:
class Rectangle(Shape):
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
Another implementation:
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14159 * self.r ** 2
Usage:
def print_shape(shape):
print("Area:", shape.area())
shapes = [
Rectangle(5, 10),
Circle(7)
]
for s in shapes:
print_shape(s)
The same function works with different object types.
4. Abstraction
Abstraction hides complex implementation details and exposes only the required functionality.
Example in TypeScript:
abstract class Database {
protected connectionString: string
constructor(connectionString: string) {
this.connectionString = connectionString
}
abstract connect(): Promise<boolean>
abstract disconnect(): Promise<void>
abstract query(sql: string): Promise<any[]>
}
Implementation example:
class MySQLDatabase extends Database {
async connect(): Promise<boolean> {
console.log("Connecting to MySQL")
return true
}
async disconnect(): Promise<void> {
console.log("Disconnect MySQL")
}
async query(sql: string): Promise<any[]> {
console.log("Query:", sql)
return []
}
}
SOLID Principles
Principle Description
Single Responsibility A class should have one responsibility Open Closed Open for extension, closed for modification Liskov Substitution Subtypes should replace base types safely Interface Segregation Prefer small interfaces Dependency Inversion Depend on abstractions
Best Practices
- keep classes focused on one responsibility
- prefer composition over inheritance when possible
- program against interfaces
- use clear class and method names
- keep methods small and focused
Closing
Object-Oriented Programming helps structure software in a clear way.
By understanding the four pillars:
- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
developers can build systems that are easier to maintain and extend.