Mastering Object-Oriented Programming: A Complete Guide

December 26, 2024

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:

  1. Encapsulation
  2. Inheritance
  3. Polymorphism
  4. 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.