Fundamentals of OOP

What Is Object-Oriented Programming?

Object-Oriented Programming (OOP) is a paradigm based on the concept of “objects”, which bundle data (attributes) and behavior (methods).

OOP enables modeling of real-world entities using classes, instantiation into objects, and structuring code via inheritance, encapsulation, and polymorphism.

Describing Classes

Modeling a Car

Imagine a Car class that encapsulates attributes like make, model, and fuelLevel, and methods such as startEngine() and drive(). Each individual car on the road (yours or anyone else's) becomes an object with its own state, yet all share the same blueprint defined by the Car class.

Modeling a Bank Account

Consider a BankAccount class recording accountNumber, balance, and ownerName. Methods like deposit(amount) and withdraw(amount) manage the account’s behavior. Two different accounts are distinct objects, each maintaining its own balance, yet both adhere to the same class definition.

Caution: Don’t pack every attribute or method into one class: keep your class focused on a single responsibility to avoid unmaintainable “Über-objects.”

Describing Inheritance

Inheritance lets one class (subclass) acquire fields and methods of another (superclass), promoting code reuse.

Car Hierarchy Example (Inheritance)

class Car:
    def __init__(self, make, model):
        self.make, self.model = make, model
    def start_engine(self):
        print(f"{self.make} {self.model}: engine started")
    def drive(self):
        print(f"{self.make} {self.model}: driving")

class ElectricCar(Car):
    def __init__(self, make, model, battery_level=100):
        super().__init__(make, model)
        self.battery_level = battery_level
    def charge_battery(self, amt=10):
        self.battery_level = min(100, self.battery_level + amt)
        print(f"Charging... battery={self.battery_level}%")

ec = ElectricCar("Tesla", "Model 3", 50)
ec.start_engine(); ec.drive(); ec.charge_battery(30)
public class Car {
    String make, model;
    public Car(String make, String model) { this.make = make; this.model = model; }
    void startEngine() { System.out.println(make + " " + model + ": engine started"); }
    void drive() { System.out.println(make + " " + model + ": driving"); }
}

class ElectricCar extends Car {
    int batteryLevel;
    public ElectricCar(String make, String model, int batteryLevel) {
        super(make, model);
        this.batteryLevel = batteryLevel;
    }
    void chargeBattery(int amt) {
        batteryLevel = Math.min(100, batteryLevel + amt);
        System.out.println("Charging... battery=" + batteryLevel + "%");
    }
    public static void main(String[] args) {
        ElectricCar ec = new ElectricCar("Tesla", "Model 3", 50);
        ec.startEngine(); ec.drive(); ec.chargeBattery(30);
    }
}

Caution: Deep (multi-level) inheritance chains can be hard to follow: favour composition over inheritance if you find yourself making more than one or two levels of subclasses.

Describing Encapsulation

Encapsulation restricts direct access to an object’s internal state, exposing only defined methods (getters/setters, accessors/mutators).

BankAccount Encapsulation Example

class BankAccount:
    def __init__(self, balance=0.0):
        self._balance = float(balance)  # "private" by convention
    def deposit(self, amt: float):
        if amt > 0: self._balance += amt
    def withdraw(self, amt: float) -> bool:
        if 0 < amt <= self._balance:
            self._balance -= amt; return True
        return False
    def get_balance(self) -> float:
        return self._balance

acct = BankAccount(100)
acct.deposit(50); acct.withdraw(30)
print("Balance:", acct.get_balance())
public class BankAccount {
    private double balance;

    public BankAccount(double initial) { this.balance = initial; }

    public void deposit(double amt) {
        if (amt > 0) balance += amt;
    }
    public boolean withdraw(double amt) {
        if (amt > 0 && amt <= balance) { balance -= amt; return true; }
        return false;
    }
    public double getBalance() { return balance; }

    public static void main(String[] args) {
        BankAccount a = new BankAccount(100);
        a.deposit(50); a.withdraw(30);
        System.out.println("Balance: " + a.getBalance());
    }
}

Caution: Over-restricting access (e.g. making everything private) can make testing and subclassing difficult: choose your access modifiers thoughtfully.

Describing Polymorphism

Polymorphism allows a method from a superclass to be redefined in its subclass, enabling different behaviours under a common method name (interface).

Shape Polymorphism Example

class Shape:
    def draw(self):
        print("Drawing a shape")

class Circle(Shape):
    def draw(self):
        print("Drawing a circle")

class Rectangle(Shape):
    def draw(self):
        print("Drawing a rectangle")

class Mystery(Shape):
    # Inherit from parent

shapes = [Circle(), Rectangle(), Mystery()]
for s in shapes:
    s.draw()
public class Shape {
    void draw() { System.out.println("Drawing a shape"); }
}

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

class Rectangle extends Shape {
    @Override void draw() { System.out.println("Drawing a rectangle"); }
}

class Mystery extends Shape {
    // Inherit from parent
}

class Demo {
    public static void main(String[] args) {
        Shape s1 = new Circle();
        Shape s2 = new Rectangle();
        Shape s3 = new Mystery();
        s1.draw();  // prints "Drawing a circle"
        s2.draw();  // prints "Drawing a rectangle"
        s3.draw();  // prints "Drawing a shape"
    }
}

Caution: Relying too heavily on polymorphism without clear hierarchies can hide which implementation is actually in use: document your class behaviours clearly.

Core OOP Concepts

Concept Description
Class Blueprint defining a set of attributes and methods.
Object Concrete instance of a class with specific data.
Inheritance Mechanism to derive a new class (subclass) from an existing one (superclass), reusing code.
Encapsulation Hiding internal state and requiring all interaction through methods.
Polymorphism Ability for different classes to be treated as instances of the same base class, often via method overriding.

Advantages and Disadvantages of OOP

Advantages

  • Models real-world entities naturally.
  • Encourages code reuse via inheritance.
  • Encapsulation enhances maintainability and security.
  • Polymorphism allows flexible and extensible code.

Disadvantages

  • Can introduce extra complexity for simple tasks.
  • Overhead of many small objects can impact performance.
  • Designing class hierarchies requires careful planning.

 Key Takeaways

  • Classes and objects are the foundation of OOP.
  • Inheritance, encapsulation, and polymorphism enable robust, reusable code.
  • OOP excels at modeling complex problems (domains) but can slow programs down because creating objects and extra layers takes more work.