Table of Contents
- Introduction
- A Quick Recap of OOP
- Why OOP Alone Is Not Enough
- What is SOLID?
- 1. Single Responsibility Principle (SRP)
- 2. OpenClosed Principle (OCP)
- 3. Liskov Substitution Principle (LSP)
- 4. Interface Segregation Principle (ISP)
- 5. Dependency Inversion Principle (DIP)
- Composition Over Inheritance
- Common Misconceptions About SOLID
- When Not to Apply SOLID
- SOLID in a Real E-Commerce System
- Conclusion
Introduction
Imagine you're building an application using Object-Oriented Programming (OOP).
At first, everything seems great:
- The code is clean.
- Features work correctly.
- Development moves quickly.
Then new requirements arrive.
Suddenly:
- Fixing one bug breaks another feature.
- A single class handles business logic, database access, and email notifications.
- Adding a new feature requires modifying multiple existing classes.
- Testing becomes increasingly difficult.
If this sounds familiar, you've encountered one of the biggest challenges in software development:
Managing change.
This is exactly the problem SOLID was designed to address.
A Quick Recap of OOP
Object-Oriented Programming is built on four fundamental concepts.
Encapsulation: Hide internal state and expose behavior through controlled interfaces.
Abstraction: Expose only what is necessary while hiding implementation details.
Inheritance: Reuse behavior through parent-child relationships.
Polymorphism: Allow different objects to respond to the same message in different ways.
Why OOP Alone Is Not Enough
OOP provides powerful tools.
However, tools alone do not guarantee good design.
Poorly designed OOP systems often suffer from:
- God Classes
- Tight Coupling
- Fragile Inheritance Hierarchies
- Difficult Testing
- Low Maintainability
- Poor Extensibility
SOLID provides guidelines for using OOP effectively.
What is SOLID?
SOLID is a collection of five design principles popularized by Robert C. Martin (Uncle Bob).
- S — Single Responsibility Principle
- O — Open/Closed Principle
- L — Liskov Substitution Principle
- I — Interface Segregation Principle
- D — Dependency Inversion Principle
The goal is not perfect design.
The goal is software that remains easy to evolve over time.
1. Single Responsibility Principle (SRP)
A class should have one and only one reason to change.
Relationship to OOP
SRP complements Encapsulation by encouraging related behaviors to stay together while separating unrelated concerns.
It is also closely related to:
- Cohesion
- Separation of Concerns
A highly cohesive class typically has a single responsibility.
Common Misconception
A responsibility is not a method.
This is perfectly acceptable:
class UserValidator {
public boolean validateEmail(String email) {}
public boolean validatePhone(String phone) {}
public boolean validateName(String name) {}
}
All methods belong to the same responsibility:
Validation
SRP Violation
class User {
public void validate() {}
public void saveToDatabase() {}
public void sendWelcomeEmail() {}
}
This class has multiple reasons to change.
Benefits
- Higher cohesion
- Easier maintenance
- Better testing
- Reduced coupling
2. Open/Closed Principle (OCP)
Software entities should be open for extension but closed for modification.
Relationship to OOP
OCP relies heavily on:
- Abstraction
- Polymorphism
OCP Violation
class PaymentProcessor {
public void process(String type) {
if(type.equals("credit")) {
} else if(type.equals("paypal")) {
}
}
}
Every new payment method requires modifying existing code.
Better Design
interface PaymentMethod {
void process();
}
class CreditCardPayment implements PaymentMethod {
public void process() {}
}
class PayPalPayment implements PaymentMethod {
public void process() {}
}
OCP Is Not About Interfaces
Many developers think:
OCP = Create an Interface
Not necessarily.
Interfaces are only one implementation technique.
Other approaches include:
- Strategy Pattern
- Plugin Architecture
- Event-Driven Systems
- Dependency Injection
- Configuration-Based Extensions
The real goal is:
Extend behavior without modifying stable code.
3. Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types.
Relationship to OOP
LSP protects the correct use of:
- Inheritance
- Polymorphism
Example
interface Bird {
void eat();
void fly();
}
class Sparrow implements Bird {
public void eat() {}
public void fly() {}
}
class Ostrich implements Bird {
public void eat() {}
public void fly() {
throw new UnsupportedOperationException();
}
}
The abstraction promises that all birds can fly.
Ostrich breaks that promise.
Better Design
interface Bird {
void eat();
}
interface FlyingBird extends Bird {
void fly();
}
ISP vs LSP
Although often discussed together, they solve different problems.
| Principle | Focus |
|---|---|
| ISP | Interface Design |
| LSP | Substitutability |
LSP focuses on behavioral compatibility between abstractions and implementations.
4. Interface Segregation Principle (ISP)
Clients should not be forced to depend on interfaces they do not use.
ISP Violation
interface Worker {
void work();
void eat();
}
class Robot implements Worker {
public void work() {}
public void eat() {
throw new UnsupportedOperationException();
}
}
The interface is too broad.
Better Design
interface Workable {
void work();
}
interface Eatable {
void eat();
}
Benefits
- Smaller contracts
- Lower coupling
- Easier maintenance
5. Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions.
DIP Violation
class NotificationService {
private EmailSender sender = new EmailSender();
}
Business logic depends directly on implementation details.
Better Design
interface MessageSender {
void send(String message);
}
class EmailSender implements MessageSender {}
class NotificationService {
private MessageSender sender;
public NotificationService(MessageSender sender) {
this.sender = sender;
}
}
DIP vs Dependency Injection
A common misconception is:
DIP and DI are the same thing.
They are not.
DIP is a design principle.
DI is a technique used to implement that principle.
DIP answers:
What should depend on what?
DI answers:
How are dependencies provided?
The true goal of DIP is to ensure that business policies do not depend on implementation details.
Composition Over Inheritance
A principle commonly associated with SOLID is:
Favor Composition Over Inheritance
Example
interface NotificationSender {
void send(String message);
}
class EmailSender implements NotificationSender {}
class SmsSender implements NotificationSender {}
class NotificationService {
private final NotificationSender sender;
public NotificationService(NotificationSender sender) {
this.sender = sender;
}
}
Notice that NotificationService does not inherit from EmailSender or SmsSender.
Instead, it composes behavior through abstraction.
Benefits:
- More flexibility
- Better testability
- Lower coupling
- Stronger support for OCP and DIP
Common Misconceptions About SOLID
Misconception #1
"Every class must have one method."
False.
SRP is about responsibilities, not method count.
Misconception #2
"OCP means creating interfaces."
False.
Interfaces are only one possible implementation.
Misconception #3
"DIP equals Dependency Injection."
False.
DI is a technique.
DIP is the principle.
Misconception #4
"SOLID should always be applied everywhere."
False.
Over-engineering is a real problem.
When Not to Apply SOLID
Not every project needs enterprise-level architecture.
For small applications:
interface UserValidator
class UserValidatorImpl
may add unnecessary complexity.
Apply SOLID when it solves a real problem.
Avoid applying it purely for theoretical purity.
SOLID in a Real E-Commerce System
SRP
Separate:
- Order Processing
- Persistence
- Notification
OCP
Add new payment methods without modifying existing services.
LSP
Allow specialized order types to safely replace base order types.
ISP
Create focused contracts:
- Payable
- Refundable
- Shippable
DIP
Depend on abstractions rather than specific database implementations.
Conclusion
At its core, SOLID is about managing change.
As software evolves, requirements change, business rules shift, and new features emerge.
Poor design makes these changes expensive and risky.
Each SOLID principle addresses a different source of rigidity:
- SRP reduces unrelated changes.
- OCP minimizes modifications to stable code.
- LSP protects substitutability.
- ISP avoids unnecessary dependencies.
- DIP separates business rules from implementation details.
Together, these principles help developers build systems that can evolve without becoming increasingly difficult to maintain.
The goal is not to eliminate change.
The goal is to make change less expensive.
Top comments (0)