Skip to content

SE 03 Object Oriented Programming

Dr M H B Ariyaratne edited this page Jun 8, 2026 · 1 revision

SE-03: Object-Oriented Programming (OOP)

Part of the Software Engineering Principles series


What Is OOP?

Object-Oriented Programming is a programming paradigm that models a system as a collection of objects — entities that bundle state (data) and behaviour (methods) together. Instead of writing a program as a sequence of instructions acting on separate data, OOP organises code around the things that exist in the problem domain.

A hospital information system, for example, has Patients, Prescriptions, Bills, and Pharmacists. OOP lets you represent each of these as a distinct type with its own data and operations, rather than as loose collections of variables and functions.


The Four Pillars

1. Encapsulation

Definition: Bundling data and the methods that operate on it into a single unit (a class), and restricting direct access to internal data from outside that unit.

Why it matters: If any part of the system can modify an object's data directly, it becomes impossible to reason about the object's state. Encapsulation creates a controlled interface and enforces invariants.

In practice:

// Without encapsulation — any caller can corrupt stock
public class StockItem {
    public int quantity;
    public double unitPrice;
}

// With encapsulation — rules enforced internally
public class StockItem {
    private int quantity;
    private double unitPrice;

    public void issueUnits(int amount) {
        if (amount <= 0) throw new IllegalArgumentException("Amount must be positive");
        if (amount > quantity) throw new IllegalStateException("Insufficient stock");
        this.quantity -= amount;
    }

    public int getQuantity() { return quantity; }
}

The second version guarantees that quantity can never go negative through legitimate use. The first cannot make that guarantee.

Key practices:

  • Fields are private by default
  • Provide getters/setters only where genuinely needed
  • Put validation inside setters and mutating methods, not in callers

2. Inheritance

Definition: A mechanism by which a class (subclass) can acquire the properties and behaviour of another class (superclass), extending or specialising it.

Why it matters: Eliminates code duplication for types that share common structure, and allows code to work with the general type without knowing the specific type.

In practice:

public abstract class Bill {
    protected Patient patient;
    protected LocalDateTime createdDateTime;
    protected double netValue;

    public abstract BillType getBillType();

    public String getSummary() {
        return getBillType() + " | " + patient.getName() + " | " + netValue;
    }
}

public class PharmacyBill extends Bill {
    private Department dispensingDepartment;

    @Override
    public BillType getBillType() { return BillType.PHARMACY; }
}

public class InpatientBill extends Bill {
    private Admission admission;

    @Override
    public BillType getBillType() { return BillType.INPATIENT; }
}

Code that works with Bill works with both PharmacyBill and InpatientBill without modification.

When to use it carefully:

Inheritance creates tight coupling between the superclass and subclass. A change to the superclass can silently break all subclasses. Prefer composition over inheritance when the relationship is "has a" rather than "is a":

  • PharmacyBill is a Bill → inheritance is appropriate
  • Bill has a Patient → use a field, not inheritance

3. Polymorphism

Definition: The ability for objects of different types to be treated as objects of a common type, with each type responding to the same method call in its own way.

Why it matters: Polymorphism allows code to be written in terms of abstractions rather than concrete types, making it open to extension without modification.

Two forms:

Compile-time (method overloading): Multiple methods with the same name but different parameters.

public class ReportGenerator {
    public void generate(Patient patient) { ... }
    public void generate(Department dept) { ... }
    public void generate(Patient patient, DateRange range) { ... }
}

Runtime (method overriding): A subclass provides its own implementation of a method defined in the superclass.

public abstract class PaymentProcessor {
    public abstract Receipt process(double amount);
}

public class CashProcessor extends PaymentProcessor {
    @Override
    public Receipt process(double amount) {
        // handle cash drawer
    }
}

public class CardProcessor extends PaymentProcessor {
    @Override
    public Receipt process(double amount) {
        // handle card terminal
    }
}

// This code works with any payment type — present and future
public void collectPayment(PaymentProcessor processor, double amount) {
    Receipt receipt = processor.process(amount);
    receipt.print();
}

4. Abstraction

Definition: Hiding implementation details and exposing only what is necessary for the user of a class or interface to work with it.

Why it matters: Users of a component should not need to understand how it works internally — only what it does. This reduces cognitive load and isolates the impact of internal changes.

In practice — interfaces:

// Abstraction: what a stock repository does
public interface StockRepository {
    Optional<StockItem> findByItem(Item item, Department dept);
    void save(StockItem stockItem);
    List<StockItem> findLowStock(Department dept, int threshold);
}

// Concrete implementation — caller does not know or care about this
public class JpaStockRepository implements StockRepository {
    @PersistenceContext
    private EntityManager em;

    @Override
    public Optional<StockItem> findByItem(Item item, Department dept) {
        // JPQL query details hidden here
    }
}

The service layer works against StockRepository and is completely insulated from the JPA details. The implementation can be swapped (e.g., for a test double) without touching the service.


Classes and Objects

A class is a blueprint. An object is an instance of that blueprint.

// Class — the blueprint
public class Patient {
    private String name;
    private LocalDate dateOfBirth;

    public Patient(String name, LocalDate dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    public int getAge() {
        return Period.between(dateOfBirth, LocalDate.now()).getYears();
    }
}

// Objects — instances of the blueprint
Patient alice = new Patient("Alice Perera", LocalDate.of(1985, 3, 12));
Patient bob   = new Patient("Bob Silva",   LocalDate.of(1972, 9, 4));

Each object has its own independent state. alice.getAge() and bob.getAge() return different values.


Key OOP Concepts Beyond the Pillars

Interfaces

An interface declares a contract — a set of methods a class must implement — without providing any implementation. Interfaces are the primary tool for abstraction in Java.

public interface Printable {
    void print();
    default void printWithHeader(String header) {
        System.out.println("=== " + header + " ===");
        print();
    }
}

Abstract Classes

An abstract class is a partially implemented class that cannot be instantiated directly. It provides common implementation for subclasses while leaving specific behaviour to be defined by them.

Use an abstract class when subclasses share significant implementation code. Use an interface when you only need to define a contract.

Composition

Building complex objects by containing simpler objects as fields, rather than inheriting from them.

public class Prescription {
    private Patient patient;           // composition
    private Practitioner prescriber;   // composition
    private List<PrescriptionItem> items; // composition
    private LocalDateTime issuedAt;
}

Prescription is composed of a Patient, a Practitioner, and a list of items. It does not inherit from any of them.


Common Mistakes

Mistake Problem Fix
God class One class does everything Split by responsibility
Anemic domain model Classes are just data bags with no behaviour Move logic into the class that owns the data
Inappropriate inheritance "I need to reuse this code" via inheritance Use composition instead
Leaking internals Returning mutable internal collections directly Return unmodifiable views or copies
Overusing setters Every field has a public setter Prefer constructors and specific mutating methods

OOP and the SOLID Principles

The four pillars of OOP describe the mechanics of the paradigm. The SOLID principles describe how to apply those mechanics well. OOP without SOLID produces classes that are technically correct but structurally fragile.


Previous: SE-02: Software Development Life Cycle
Next: SE-04: SOLID Principles

Back to Software Engineering Principles

Clone this wiki locally