SOLID — Learning OOP & SOLID Principles the Pedarasi Peddamma Way (Java 21 Edition)
The Veranda, The Lamp, and the First Question
SOLID — Learning OOP & SOLID Principles the Pedarasi Peddamma Way (Java 21 Edition)

The Veranda, The Lamp, and the First Question
Every evening, once the buffaloes were tied and the last crow had gone quiet, the children of Kondapalli village gathered around their grandmother — Pedarasi Peddamma. She wasn’t a software engineer. She had never touched a keyboard. But she had raised a joint family of fourteen people under one roof for forty years, and if you think about it long enough, running a joint family and designing good software aren’t all that different.
One evening, her grandson Ravi — home from his first job as a software engineer in the city — sat down looking troubled.
“What’s wrong, Ravi? Some problem at office?” she asked.
“Grandma, my code is so confusing. My senior keeps saying I’m not following the ‘SOLID principles.’ I just don’t understand it.”
Peddamma smiled the way she always did before turning a problem into a story.
“Okay. Let me tell you a story. But first, let’s understand four basic things — because before we build SOLID, you need to understand the foundation of your own house.”
And so, over five evenings, she told the story of the joint family — and without meaning to, she explained Object-Oriented Programming and every single one of the SOLID principles.
Part 1: The Four Pillars — OOP, Peddamma Style
1. Encapsulation — The Spice Box
“Look here, Ravi,” she said, pulling out her old steel spice box from the kitchen shelf. “Only I know exactly how much of what is inside this box. You don’t get to reach in and take things out directly — I hand it to you with a spoon. Why? Because I need to control the quantity. If everyone just reached in whenever they wanted, this box would become complete chaos.”

Nobody touches the spices directly. Everyone asks Peddamma, and she decides what goes out, how much, and when. That’s encapsulation — hiding the internal state (the spices) and only exposing controlled access (the spoon).
public final class MasalaDabba {
private final Map<String, Integer> spices = new HashMap<>();
public void addSpice(String name, int grams) {
if (grams <= 0) throw new IllegalArgumentException("Grams should be positive");
spices.merge(name, grams, Integer::sum);
}
// controlled access — the "spoon"
public int takeSpice(String name, int grams) {
int available = spices.getOrDefault(name, 0);
int given = Math.min(available, grams);
spices.put(name, available - given);
return given;
}
}
Nobody outside MasalaDabba can directly modify the spices map. They go through the spoon — the public methods.
2. Inheritance — The Family Recipe
“My mother taught me a dish — pulihora, tamarind rice. I taught it to your mother, and she added her own little twist — a bit more oil, the way she likes it. You’ll learn it too one day — the base recipe stays the same, but every generation adds their own style.”

The base recipe is inherited, but each generation extends it with their own flavor. That’s inheritance — a subclass gets the base behavior and can add or override it.
public class FamilyRecipe {
protected String baseIngredient = "rice";
public String cook() {
return "Cooking with " + baseIngredient;
}
}
public class DaughterInLawRecipe extends FamilyRecipe {
@Override
public String cook() {
return super.cook() + " and an extra spoon of ghee, just how I like it";
}
}
3. Abstraction — The Village Hand Pump
“Look at the hand pump outside our house. All you need to know is this — push the handle, water comes out. What happens inside the pump — the piston, the valve, the pipe — none of that concerns you. That’s not something you need to know.”

You only need to know what it does — pump water — not how it does it internally. That’s abstraction.
public interface WaterSource {
String draw();
}
public class HandPump implements WaterSource {
// internal mechanism hidden from the villager
private boolean primePiston() { return true; }
@Override
public String draw() {
primePiston();
return "Water drawn from underground";
}
}
4. Polymorphism — How Peddamma Greets Everyone Differently
“I don’t speak to everyone the same way. When elders come, I say ‘namaskaram.’ When relatives visit, I say ‘come, come, welcome.’ When your friends show up, I just say ‘hey, what’s up.’ The action is the same — greeting someone — but the style is different for every single person.”

Same action — greet() — but different behavior depending on who it's for. That's polymorphism. Here it is using Java 21's sealed interfaces and pattern matching switch:
public sealed interface Guest permits Elder, Relative, Friend {}
public record Elder(String name) implements Guest {}
public record Relative(String name) implements Guest {}
public record Friend(String name) implements Guest {}
public class Peddamma {
public String greet(Guest guest) {
return switch (guest) {
case Elder e -> "Namaskaram, " + e.name() + " garu";
case Relative r -> "Come, come, " + r.name() + "! Welcome home";
case Friend f -> "Hey " + f.name() + ", what's up?";
};
}
}
Part 2: The Five SOLID Stories
“So, did you understand the four pillars now? Good. Let’s move on to SOLID. This too is something that happens in our house every single day.”
S — Single Responsibility: “One Job Per Person”
“In our house, everyone has exactly one job. Your grandfather looks after the fields. Your mother cooks. I look after the children. If one person tries to do all the jobs at once — what happens? Nothing gets done properly. Everything ends up half-finished.”

A class should have one reason to change — one job.
// Bad — one class doing everything
class FamilyMember {
void cookFood() {}
void growCrops() {}
void teachChildren() {}
}
// Good — each class, one responsibility
class Cook { void cookFood() {} }
class Farmer { void growCrops() {} }
class Teacher { void teachChildren() {} }
O — Open/Closed: “Buy a New Jar, Don’t Rebuild the Mixer”
“See this mixer-grinder? I bought it fifteen years ago, and the motor inside has never changed. Over the years, whenever I needed something new — chutney one day, batter the next, dry masala another day — I didn’t ask anyone to open up the machine and rewire it. I simply bought a new jar that fits the same base. The machine stays exactly as it is; only the jars keep growing.”

Classes should be open for extension, but closed for modification.
public interface GrinderJar {
String grind();
}
public class ChutneyJar implements GrinderJar {
public String grind() { return "Ground into smooth chutney"; }
}
public class BatterJar implements GrinderJar {
public String grind() { return "Ground into idli-dosa batter"; }
}
public class Mixer {
// the motor — never touched when a new jar arrives
public String run(GrinderJar jar) {
return jar.grind();
}
}
Next month, a SpiceJar can be added and handed to the same Mixer — no change to Mixer itself, and the older jars keep working exactly as before.
L — Liskov Substitution: “Whoever Goes to the Pump Must Come Back With a Full Pot”
“Every morning, someone from the house goes to the hand pump to fetch water — some days it’s your uncle, some days it’s your elder cousin. It doesn’t matter who goes, the result must be the same: they leave with an empty pot and come back with a full one. But if I send your baby cousin — who’s far too small to lift a full pot — expecting the same result, the whole morning routine breaks down. He simply cannot do what the role demands, no matter how willing he is.”

If B is a subtype of A, you should be able to replace A with B without breaking the behavior the caller expects.
public interface WaterFetcher {
String fetchAndCarry();
}
public class Teenager implements WaterFetcher {
public String fetchAndCarry() { return "Pot filled and carried home"; }
}
public class Uncle implements WaterFetcher {
public String fetchAndCarry() { return "Pot filled and carried home"; }
}
// Violates LSP — breaks the contract callers rely on
public class Toddler implements WaterFetcher {
public String fetchAndCarry() {
throw new UnsupportedOperationException("Too small to carry a full pot");
}
}
If code written for WaterFetcher starts crashing when handed a Toddler, the substitution has broken the contract — that's an LSP violation.
I — Interface Segregation: “Don’t Force Everyone to Do Everything”
“In our house, the big trunk has all sorts of things inside it — farming tools, cooking vessels, children’s books, all in one box. But every person only takes out what they actually need. The farmer doesn’t need cooking vessels, the teacher doesn’t need farming tools. You shouldn’t force one big ‘do everything’ interface on everyone.”

Don’t force a class to implement methods it doesn’t need. Split large interfaces into smaller, focused ones.
// Bad — a fat interface forces everyone to implement everything
interface FamilyDuties {
void cook();
void farm();
void teach();
}
// Good — segregated interfaces
interface Cooking { void cook(); }
interface Farming { void farm(); }
interface Teaching { void teach(); }
public class Grandmother implements Cooking, Teaching {
public void cook() { /* ... */ }
public void teach() { /* ... */ }
// not forced to implement farm()
}
D — Dependency Inversion: “It Doesn’t Matter Where the Water Comes From”
“We fetch water from wherever we can — the well, the pond, the overhead tank. What matters to us is water, not exactly where it comes from. If the well dries up tomorrow, we’ll draw from the pond instead. We shouldn’t depend rigidly on one specific source — we should depend on the idea of ‘something that gives us water.’”

High-level modules shouldn’t depend on low-level details — both should depend on abstractions.
public interface WaterSource {
String fetchWater();
}
public class Well implements WaterSource {
public String fetchWater() { return "Water from well"; }
}
public class Pond implements WaterSource {
public String fetchWater() { return "Water from pond"; }
}
public class Household {
private final WaterSource source;
public Household(WaterSource source) {
this.source = source; // depends on abstraction, not a concrete source
}
public String getWater() {
return source.fetchWater();
}
}
Household doesn't care if it's a Well or a Pond — it only depends on the WaterSource contract. If the well dries up, swap in a Pond without touching Household at all.
The Lamp Burns Low

By the fifth evening, Ravi finally understood. “Grandma, our house has been following SOLID principles all along — not in code, but in life!”
Peddamma laughed. “That’s exactly what good design is, Ravi. Every person has their own job (SRP), new things can be added without disturbing what already works (OCP), whoever steps in as a substitute must meet the same expectations (LSP), give people only what they actually need (ISP), and never depend rigidly on just one thing (DIP). At home, in life, in code — it’s all the same principle.”
A good family runs on the same principles as good software: everyone has a clear role, the system grows without breaking what already works, no one becomes irreplaceable in a way that breaks trust, no one is burdened with what isn’t theirs, and nothing depends too rigidly on one single thing.
That, in the end, is what Pedarasi Peddamma taught without ever writing a line of code.
If this story helped SOLID finally make sense, share it with a junior developer who’s still confused about the difference between abstraction and encapsulation — chances are, their grandmother already explained it to them once, in a different story.
메타데이터
- post_id
- 5490a7cee6c2
- slug
- solid-learning-oop-solid-principles-the-pedarasi-peddamma-way-java-21-edition-5490a7cee6c2
- url
- https://medium.com/@ramanjaneyulu.vaddi18/solid-learning-oop-solid-principles-the-pedarasi-peddamma-way-java-21-edition-5490a7cee6c2
- canonical_url
- https://medium.com/@ramanjaneyulu.vaddi18/solid-learning-oop-solid-principles-the-pedarasi-peddamma-way-java-21-edition-5490a7cee6c2
- author_url
- https://medium.com/@ramanjaneyulu.vaddi18
- status
- ok
- fetched_at
- 2026-08-12 21:38:07