← Back to list

What OOP Actually Buys You And Why “Real World Modeling” Is a Lie

If you can write class Dog extends Animal in an exam that gives the correct answer at the end, you’ve passed. You may still not be sure why…

Sagar Patel · 2026-06-30 16:57 · 20 claps · 4.0 min read
#java #programming #software-engineering #computer-science #objectorientedprogramming
Open on Medium ↗
Wiki topics: 💻 · Programming 🔬 · Science · General 🐾 · Pets & Animals

What OOP Actually Buys You And Why “Real World Modeling” Is a Lie

If you can write class Dog extends Animal in an exam that gives the correct answer at the end, you’ve passed. You may still not be sure why anybody cares.

It’s not your fault; it’s the way OOP is normally taught. Most courses teach encapsulation, inheritance, and polymorphism in such abstract examples (Animals! Shapes! Employees!) That you learn by rote and don’t have to think about the syntax the problem solves. You are able to write OOP code with no real understanding of why it’s better than the other approach.

Let’s do that by building something: a library fine calculator.

What is the “Before”: Code That Works (For Now)

Here is one that compiles, runs, and gives correct output. No classes other than Main. Only data and functions are performing their duties.

public class FineCalculator {
    static String[] bookNames = {"Java Basics", "DSA", "OS Concepts"};
    static int[] daysLate = {5, 0, 12};
    static boolean[] isReturned = {true, false, true};

    public static void calculateFines() {
        for (int i = 0; i < bookNames.length; i++) {
            if (isReturned[i] && daysLate[i] > 0) {
                int fine = daysLate[i] * 5;
                System.out.println(bookNames[i] + ": Rs. " + fine);
            }
        }
    }

    public static void main(String[] args) {
        calculateFines();
    }
}

There’s nothing wrong with this. It’s correct. It’s even readable at this size.

Now, this is what breaks it: “Faculty members pay a lower fine per day (Rs. 2/day compared to Rs. 5/day for students), and also faculty are given a 3-day grace period before the fines kick in”.

Simple requirement, right? See how it affects this code.

You must create a new array, called String[] memberType. Your three parallel arrays become four, and yet they all need to be in sync by index forever by convention — there’s no enforcement. So there should be a member type branch in the Then calculateFines() statement. A separate grace-period check, likely another if, likely nested inside the first if. And when someone calls tomorrow for a third type of member, “alumni, get another rate,” again you’re in this same method with another branch and hoping not to blow up the first two.

No one tells you this clearly enough: the problem didn’t get harder; the code got harder. It became more difficult as no one was stopping it.

Naming is the Actual Pain

Three specific costs, not vague ones:

  1. Data and logic are separated by a great distance. daysLate[i] could be modified by any function in this file. If you’re not familiar with what a “book” actually is, you’ll need to keep four different arrays in your head in sync.
  2. Invalid states are not blocked. daysLate[2] = -7 compiles fine. It does the same for an array of 3 elements as isReturned[5] does for an array of 5 elements, until the runtime crashes. There’s no gatekeeper.
  3. Each new feature requires a search of miscellaneous logic. Adding “faculty fine rate” will open a method called calculateFines, not calculateFinesForDifferentMemberTypesWithDifferentGracePeriods. The method’s responsibility slowly increased, and you have to read the entire thing to determine how to make a safe change.

This is the real meaning of “spaghetti code”. No bad indentation unconfined change.

The “After”: Same Problem, Contained Change

class Book {
    private String name;
    private int daysLate;
    private boolean returned;

    public Book(String name, int daysLate, boolean returned) {
        this.name = name;
        this.daysLate = daysLate;
        this.returned = returned;
    }

    public boolean isOverdue() {
        return returned && daysLate > 0;
    }

    public int getDaysLate() {
        return daysLate;
    }

    public String getName() {
        return name;
    }
}

abstract class Member {
    protected String name;

    public Member(String name) {
        this.name = name;
    }

    public abstract int calculateFine(Book book);
}

class StudentMember extends Member {
    public StudentMember(String name) {
        super(name);
    }

    public int calculateFine(Book book) {
        return book.isOverdue() ? book.getDaysLate() * 5 : 0;
    }
}

class FacultyMember extends Member {
    private static final int GRACE_DAYS = 3;

    public FacultyMember(String name) {
        super(name);
    }

    public int calculateFine(Book book) {
        int chargeableDays = book.getDaysLate() - GRACE_DAYS;
        return (book.isOverdue() && chargeableDays > 0) ? chargeableDays * 2 : 0;
    }
}

Now rerun the same feature request from before and add an “Alumni” member type with yet another rate. You write one new class:

class AlumniMember extends Member {
    public int calculateFine(Book book) {
        return book.isOverdue() ? book.getDaysLate() * 8 : 0;
    }
}

That’s it. Book doesn't change. StudentMember doesn't change. FacultyMember doesn't change. Nothing you already tested and trusted is touched. The change is contained exactly where it belongs.

Notice what fixed each specific pain point:

  • The “invalid state” problem is resolved by encapsulation (private fields and controlled access via methods): if daysLate is not set from outside Book, it cannot be set to -7.
  • The “hunting through unrelated logic” problem is answered by single responsibility per class: fine logic resides in one place: FacultyMember.
  • The problem “branching if chains forever” is solved by abstraction + polymorphism: the rule is contained in each subclass (Member is an abstract class and each subclass defines its own calculateFine method), and the calling code need not know what subclass of Member it is dealing with.

The Actual Payoff

Don’t think “OOP models real-world objects”. This is nice to read in a book, but it will tell you very little about why you would prefer this over procedural code.

The actual pitch is: OOP as an approach to dealing with change without damaging what works.

Software is not written once, but read thousands of times. It is modified many times by people who have not written it, under the pressure of a deadline, and usually haven’t read the entire codebase. The whole point of classes, encapsulation, and inheritance is that you make a change in one place, and rest assured that you have not inadvertently broken something 3 files deep.

This is not a “nice to have”. That is the basis of the survivability of large software systems, in general.


메타데이터
post_id
f5f081d8eb2a
slug
what-oop-actually-buys-you-and-why-real-world-modeling-is-a-lie-f5f081d8eb2a
url
https://medium.com/@sagarpatel.it/what-oop-actually-buys-you-and-why-real-world-modeling-is-a-lie-f5f081d8eb2a
canonical_url
https://medium.com/@sagarpatel.it/what-oop-actually-buys-you-and-why-real-world-modeling-is-a-lie-f5f081d8eb2a
author_url
https://medium.com/@sagarpatel.it
status
ok
fetched_at
2026-07-29 11:52:52