🕐 ~8 min read
📝 A note on this article
This post is based on my personal study notes on OOP and SOLID principles. To make these notes more readable and useful — for myself and for others — I worked with AI to help expand and structure them into a proper blog format. The ideas, learning journey, and understanding are mine; the AI helped with the writing and presentation.
Good code doesn't happen by accident. Behind every codebase that's a pleasure to work with, there's a set of ideas guiding how things are structured and connected.
This article covers that full journey — from what OOP is, how objects relate, what bad design looks like, and finally: the five SOLID principles that tie it all together.
Let's go.
Part 1: What Is OOP?
Object Oriented Programming (OOP) is a way of writing code that mirrors the real world. Instead of one long list of instructions, you group related data and behavior into objects.
🤖 Think of a robot toy. It has properties (color, height, battery) and behaviors (walk, talk, wave). In OOP, that robot is an object — and the blueprint used to build it is called a class.
Two key terms:
- Class — the blueprint (template)
- Object — a real instance built from the blueprint
The Four Pillars
| Pillar | What it does |
|---|---|
| Inheritance | A child class inherits properties and behaviors from a parent class |
| Encapsulation | Internal data is hidden — only controlled access is allowed from outside |
| Abstraction | Complex internals are hidden; only what's needed is exposed |
| Polymorphism | The same action can behave differently depending on the object |
Part 2: How Objects Relate
Objects don't exist in isolation. They connect, collaborate, and depend on each other — and the type of relationship matters.
Association
Two objects know about each other and can interact. Comes in three cardinalities:
- One-to-one — one passport per person
- One-to-many — one teacher, many students
- Many-to-many — many students, many courses
Inside association, when one object "owns" another, the relationship is either:
- Aggregation (loose) — the child survives without the parent. A backpack contains books, but the books still exist if you throw the backpack away.
- Composition (tight) — the child cannot exist without the parent. A house has rooms; demolish the house, the rooms cease to exist.
Dependency
A temporary, usage-based relationship. One class uses another inside a method but doesn't hold onto it permanently. Weaker than association.
Generalization & Realization
- Generalization — pulling shared traits from multiple classes into one superclass (inheritance)
- Realization — a class implementing an interface, promising to fulfill a defined contract
Part 3: What Bad Design Looks Like
Before learning the rules, it helps to understand what you're guarding against. Robert C. Martin identified three symptoms of bad design:
🪨 Rigidity — the system is hard to change. Touch one thing, and a cascade of other things need updating. Developers become afraid to make changes.
🍪 Fragility — the system breaks in unexpected places when you make a change. You fix a bug in the payment module, and somehow the email notifications stop working.
🏗️ Immobility — useful components can't be reused. They're so entangled with their surroundings that extracting them would take longer than rewriting from scratch.
All three share the same root cause: poor management of dependencies between components.
Part 4: SOLID
SOLID is a set of five principles that directly address the problems above. Each one targets a specific failure mode.
S — Single Responsibility Principle
"A module should be responsible to one, and only one, actor."
Every class should have exactly one reason to change — it serves one actor (one group of stakeholders).
👨🍳 A restaurant worker who is simultaneously the chef, cashier, security guard, and delivery driver is a disaster waiting to happen. One change in one role disrupts all the others.
Split your classes. One responsibility each.
O — Open/Closed Principle
"A software artifact should be open for extension but closed for modification."
Add new behavior by extending — not by editing existing, working code.
🔌 A power strip lets you plug in new devices without rewiring the strip itself. That's OCP in action.
When requirements change, slot in new code alongside the old — don't rewrite what's already working.
L — Liskov Substitution Principle
"If S is a subtype of T, objects of type T may be replaced with objects of type S without altering program correctness."
A subclass must be fully substitutable for its parent class. Every promise the parent makes, the child must keep.
🤖 If the parent robot can cook, the child robot must be able to cook too — not throw an error or silently do nothing.
If your subclass has empty methods, stub implementations, or "not supported" throws — that's a Liskov violation.
I — Interface Segregation Principle
"Clients should not be forced to depend upon interfaces that they do not use."
Don't create fat interfaces that force classes to implement methods they don't need. Split interfaces into smaller, focused ones.
🍴 Don't give every restaurant customer a steak knife, soup spoon, chopsticks, and a fondue skewer when they only ordered soup.
Smaller interfaces = narrower dependencies = changes stay localized.
D — Dependency Inversion Principle
"High-level modules should not depend on low-level modules. Both should depend on abstractions."
Your business logic should not be hardwired to specific implementations. It should talk to interfaces — and the low-level details implement those interfaces.
🤖 Don't solder a spatula into the robot's arm. Give the arm a standard connector, and let tools be swapped in and out.
This is what makes infrastructure (databases, APIs, file systems) swappable without touching business logic.
Quick Reference: SOLID at a Glance
| Principle | Problem it solves | Key question to ask |
|---|---|---|
| SRP | Classes doing too much | "Does this class serve more than one group of stakeholders?" |
| OCP | Editing old code to add features | "Can I add this as an extension instead of a modification?" |
| LSP | Broken inheritance hierarchies | "Can I swap the child for the parent without anything breaking?" |
| ISP | Fat interfaces with unused methods | "Is this class forced to implement something it doesn't need?" |
| DIP | Business logic locked to infrastructure | "Does my high-level code depend on a concrete class it shouldn't?" |
Where to Start
If you're applying these for the first time:
- Start with SRP — find your "doing too much" classes and split them
- Check inheritance for LSP violations — empty methods are the warning sign
- Introduce interfaces at infrastructure boundaries — that's DIP in practice
These principles aren't a checklist. They're questions to ask every time you sit down to write code.
Originally published at sanudin.dev
Top comments (0)