SOLID Principles

When we start learning Object-Oriented Programming, we often just learn classes, objects, and inheritance. But when we start working on real projects, we realize that writing code that works is not enough — it should be easy to change, easy to extend, and easy to understand.
That’s where SOLID Principles come in.
They are like the “good habits” of programming.
In this post, let’s not just list definitions — we’ll think, reason, and see these principles in action with simple Python examples and real-life parallels.
What is SOLID
SOLID is an acronym for five design principles:
S — Single Responsibility Principle
O — Open/Closed Principle
L — Liskov Substitution Principle
I — Interface Segregation Principle
D — Dependency Inversion Principle
Single Responsibility Principle (SRP)
Definition:
A class should have only one reason to change.
That means: one class = one focused job.
EXAMPLE OF VIOLATION:
class Report:
def __init__(self, data):
self.data = data
def calculate_statistics(self):
# performs some analysis
pass
def save_to_file(self, filename):
# writes data to file
pass
def send_email(self, address):
# sends the report to someone
pass
At first glance, this looks fine — it’s just one class doing everything about a report, right?
But think carefully: this class is doing 3 unrelated things:
Data analysis
File handling
Emailing
Now, if tomorrow you want to change how reports are sent (e.g., switch from email to Slack), you’ll have to modify this class — even though the core report logic didn’t change.
That’s a violation of SRP.
Fixing It — The SRP Way
class Report:
def __init__(self, data):
self.data = data
def calculate_statistics(self):
# perform analysis
pass
class FileSaver:
def save(self, report: Report, filename: str):
# logic to save report
pass
class EmailSender:
def send(self, report: Report, address: str):
# logic to send report
pass
Now, if you want to change how saving or emailing works, you just modify the respective class.
Each class has one clear reason to change.
How it connects to the principle:
You separated responsibilities so that each class has its own clear purpose.
Changing one part doesn’t risk breaking unrelated parts.
Think of a restaurant: The chef cooks, the waiter serves, the cashier bills.
If one person did everything, a single change (e.g., new billing system) would mess up everything else.
Open/Closed Principle (OCP)
Definition:
Classes should be open for extension, but closed for modification.
That means: you should be able to add new functionality without changing existing code.
EXAMPLE OF VIOLATION:
class PaymentProcessor:
def process(self, method):
if method == "credit_card":
print("Processing credit card payment")
elif method == "paypal":
print("Processing PayPal payment")
Now, imagine adding UPI payments.
You’ll need to modify this class again — and again for every new payment type.
That’s against OCP, because existing code keeps changing.
Fixing It — The OCP Way
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def pay(self):
pass
class CreditCard(PaymentMethod):
def pay(self):
print("Paid with Credit Card")
class PayPal(PaymentMethod):
def pay(self):
print("Paid with PayPal")
class PaymentProcessor:
def process(self, method: PaymentMethod):
method.pay()
You don’t touch existing code — just add a new class.
That’s extension without modification.
How it connects to the principle:
The system is stable (existing code safe) but flexible (can add new behavior).
You built a plug-in model instead of a switch-case model.
Think of your smartphone: You install new apps without rewriting Android or iOS.
Liskov Substitution Principle (LSP)
Definition:
Subclasses should be replaceable for their parent classes without breaking the program.
It sounds abstract, but here’s the essence:
If a subclass behaves differently in a way that violates the parent’s expected behavior, you break this rule.
EXAMPLE OF VIOLATION
class Bird:
def fly(self):
print("Flying high!")
class Ostrich(Bird):
def fly(self):
raise Exception("I can’t fly!")
Here, an Ostrich is a Bird, but it can’t fly.
If your code assumes all Bird objects can fly(), substituting an Ostrich will cause an error.
Fixing It — The LSP Way
class Bird:
pass
class FlyingBird(Bird):
def fly(self):
print("Flying high!")
class Sparrow(FlyingBird):
pass
class Ostrich(Bird):
def run(self):
print("Running fast!")
Now, Ostrich doesn’t pretend to have a fly() method.FlyingBird is a better abstraction for birds that can actually fly.
How it connects to the principle:
We refactored so that every subclass honors the expectations of its parent.
Replacing a parent with a child won’t break the system’s logic.
Think of a USB charger: Any USB-C device can plug in — if one “USB-C” device explodes when connected, it breaks the assumption.
Interface Segregation Principle (ISP)
Definition:
Don’t force a class to implement methods it doesn’t use.
Large “fat” interfaces are bad.
Clients should only know about the methods they actually need.
EXAMPLE OF VIOLATION:
from abc import ABC, abstractmethod
class MultiFunctionPrinter(ABC):
@abstractmethod
def print(self):
pass
@abstractmethod
def scan(self):
pass
@abstractmethod
def fax(self):
pass
Now, if you build a simple Printer, you’re forced to implement scan() and fax()— even if you don’t need them.
Fixing It — The ISP Way
class Printer(ABC):
@abstractmethod
def print(self):
pass
class Scanner(ABC):
@abstractmethod
def scan(self):
pass
class Fax(ABC):
@abstractmethod
def fax(self):
pass
Now, classes implement only what they need:
class SimplePrinter(Printer):
def print(self):
print("Printing document...")
class AllInOne(Printer, Scanner, Fax):
def print(self): ...
def scan(self): ...
def fax(self): ...
How it connects to the principle:
You avoided “fat interfaces” by giving smaller, focused contracts.
Each client depends only on what it uses — not unnecessary baggage.
Think of your smartphone charger: You only plug in the cable you need — you don’t attach extra unused connectors.
Dependency Inversion Principle (DIP)
Definition:
High-level modules shouldn’t depend on low-level modules.
Both should depend on abstractions.
The idea is to make components communicate via interfaces or abstract classes — not through hard-coded dependencies.
class MySQLDatabase:
def fetch_data(self):
print("Fetching from MySQL")
class ReportGenerator:
def __init__(self):
self.db = MySQLDatabase() # tightly coupled
def generate(self):
self.db.fetch_data()
Now, if you want to switch to PostgreSQL, you must rewrite ReportGenerator.
Fixing It — The DIP Way
from abc import ABC, abstractmethod
class Database(ABC):
@abstractmethod
def fetch_data(self):
pass
class MySQLDatabase(Database):
def fetch_data(self):
print("Fetching from MySQL")
class PostgreSQLDatabase(Database):
def fetch_data(self):
print("Fetching from PostgreSQL")
class ReportGenerator:
def __init__(self, db: Database):
self.db = db # depends on abstraction
def generate(self):
self.db.fetch_data()
Now, ReportGenerator can work with any database implementation.
It’s flexible and testable.
How it connects to the principle:
We inverted the dependency — the high-level module (ReportGenerator) depends on the abstract contract (Database), not the concrete class.
Think of a plug socket: You can plug in a fan or a phone charger — the wall doesn’t care which, because both follow the same voltage interface.
Summign Up:
| Principle | Key Idea | Real-Life Analogy |
| SRP | One class = one purpose | Chef doesn’t do billing |
| OCP | Add features without editing old code | Install apps without changing OS |
| LSP | Subclasses shouldn’t break parent behavior | All USB-C devices should “just work” |
| ISP | Don’t force classes to use extra methods | Plug only the cables you need |
| DIP | Depend on abstraction, not details | Socket works for any plug |
Final Thoughts
SOLID isn’t about writing more classes — it’s about writing more sensible ones.
The principles often overlap. For example, applying SRP naturally helps with OCP and DIP.
When in doubt, ask yourself:
“If I change this tomorrow, will something unrelated break?”
If the answer is “yes,” one of the SOLID principles might be calling for your attention.



