What Is SOLID?
SOLID = Five design principles for better code
S - Single Responsibility Principle
O - Open/Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Goal: Maintainable, flexible, understandable code.
S - Single Responsibility Principle
The Analogy
A chef in a restaurant:
- Good: Chef cooks food
- Bad: Chef cooks, takes orders, cleans tables, manages finances
Too many responsibilities! When something breaks, you don't know where to look.
The Principle
A class should have only ONE reason to change.
Bad: UserManager
- Saves to database
- Sends emails
- Validates input
- Generates reports
Good:
- UserRepository (database)
- EmailService (emails)
- UserValidator (validation)
- ReportGenerator (reports)
Benefits
â Easier to understand
â Easier to test
â Easier to change
â Fewer surprises when modifying
O - Open/Closed Principle
The Analogy
A power strip:
- Open for extension: Plug in any device
- Closed for modification: Don't rewire the strip
Add new devices without changing the strip itself.
The Principle
Software entities should be:
- Open for EXTENSION
- Closed for MODIFICATION
Add new behavior by adding NEW code.
Don't change existing, working code.
Example
Bad: Modifying existing code for each new shape
if shape.type == "circle":
area = Ď * r²
elif shape.type == "square":
area = s²
elif shape.type == "triangle": â Must modify!
area = ...
Good: Extend with new classes
Shape (interface) â getArea()
Circle extends Shape
Square extends Shape
Triangle extends Shape â Just add new class!
Benefits
â New features don't break old code
â Reduced regression risk
â Easier maintenance
L - Liskov Substitution Principle
The Analogy
A rental car:
- You can drive any car in the rental fleet
- They all have steering wheel, pedals, gear
- Any car can substitute for another
If a specific car didn't have a steering wheel, it would break the contract!
The Principle
Subtypes must be substitutable for their base types.
If you use Parent class, any Child should work.
Child shouldn't surprise you with different behavior.
Example
Bad: Subclass breaks expectations
class Rectangle:
setWidth(w)
setHeight(h)
class Square extends Rectangle:
setWidth(w):
width = w
height = w â Surprise! Also changes height?!
User expects rectangle behavior, gets square behavior.
Good: Don't inherit if contract differs.
Signs of Violation
â Overriding method to throw exception
â Overriding method to do nothing
â Subclass adds restrictions parent didn't have
â Client needs to check which type it actually is
I - Interface Segregation Principle
The Analogy
A Swiss Army knife vs specialized tools:
- Swiss knife: Everything in one tool
- Many features you rarely use
- Awkward for specific tasks
Better to have specific tools for specific jobs.
The Principle
Clients shouldn't depend on interfaces they don't use.
Many small, specific interfaces
Better than one large, general interface.
Example
Bad: Fat interface
interface Worker:
work()
eat()
sleep()
Robot implements Worker:
work() â
eat() â What?! Robots don't eat!
sleep() â Robots don't sleep!
Good: Segregated interfaces
interface Workable:
work()
interface Eatable:
eat()
interface Sleepable:
sleep()
Human implements Workable, Eatable, Sleepable
Robot implements Workable
Benefits
â Classes only implement what they need
â Smaller, focused interfaces
â Easier to understand and maintain
D - Dependency Inversion Principle
The Analogy
Electrical outlets (again):
- Lamp depends on outlet (interface), not on power plant
- Can change power source without changing lamp
- Outlet is the abstraction between them
The Principle
High-level modules shouldn't depend on low-level modules.
Both should depend on abstractions.
Abstractions shouldn't depend on details.
Details should depend on abstractions.
Example
Bad: Direct dependency
class OrderService:
def __init__(self):
self.database = PostgresDatabase() â Concrete!
self.emailer = GmailSender() â Concrete!
Can't change database without changing OrderService.
Good: Depend on abstractions
class OrderService:
def __init__(self, database: Database, emailer: EmailService):
self.database = database
self.emailer = emailer
Inject any implementation!
PostgresDatabase or MongoDatabase
GmailSender or SendGridSender
Benefits
â Easy to swap implementations
â Easy to test (inject mocks)
â Decoupled modules
â Flexible architecture
SOLID Summary Table
| Principle | Key Idea | Remember |
|---|---|---|
| Single Responsibility | One reason to change | Do one thing well |
| Open/Closed | Extend, don't modify | Add, don't change |
| Liskov Substitution | Subtypes work everywhere | Children honor parent contracts |
| Interface Segregation | Small, focused interfaces | Don't force unused methods |
| Dependency Inversion | Depend on abstractions | Inject dependencies |
Common Violations
God Class
One class does everything.
Violates: Single Responsibility
Fix: Split into focused classes.
Switch on Type
switch (object.type):
case A: doA()
case B: doB()
case C: doC()
Violates: Open/Closed
Fix: Polymorphism. Each type implements behavior.
Square-Rectangle Problem
Square extends Rectangle but breaks width/height independence.
Violates: Liskov Substitution
Fix: Separate hierarchy or immutable shapes.
Monster Interface
interface DoEverything:
method1(), method2(), ... method50()
Violates: Interface Segregation
Fix: Split into focused interfaces.
Creating Dependencies
class Service:
def __init__(self):
self.dep = ConcreteDependency() â Creating internally
Violates: Dependency Inversion
Fix: Inject dependencies through constructor.
When to Apply SOLID
Good Fit
â Long-lived applications
â Growing codebases
â Team development
â Need for testing
â Changing requirements
Might Be Overkill
â Scripts and one-offs
â Very small programs
â Throwaway code
â Performance-critical hot paths
FAQ
Q: Must I follow all five?
They're guidelines, not laws. Use judgment. Some situations warrant trade-offs.
Q: Does SOLID add complexity?
Initially, yes. Pays off as code grows and changes.
Q: SOLID for functional programming?
Principles apply differently. Dependency injection still relevant. SRP maps to function focus.
Q: How do I learn to apply SOLID?
Practice! Refactor existing code. Review others' code. Read design pattern books.
Summary
SOLID principles guide object-oriented design toward maintainable, flexible code.
Key Takeaways:
- Single Responsibility: One job per class
- Open/Closed: Extend don't modify
- Liskov Substitution: Subtypes work interchangeably
- Interface Segregation: Small focused interfaces
- Dependency Inversion: Depend on abstractions
Apply SOLID for code that's easier to understand, test, and change!
Related Concepts
Leave a Comment
Comments (0)
Be the first to comment on this concept.
Comments are approved automatically.