Why SOLID Matters
SOLID is an acronym for five design principles introduced by Robert C. Martin (Uncle Bob). They aren't strict rules — they're guidelines that, when followed, result in code that is easier to understand, extend, and refactor. Ignoring them tends to produce what developers call a "big ball of mud": a tangled codebase where changing one thing breaks five others.
S — Single Responsibility Principle
"A class should have one, and only one, reason to change."
A class that handles database queries, formats output, and sends emails is a liability. Split responsibilities clearly:
// Bad: one class doing too much
class UserService {
saveToDatabase(user) { ... }
sendWelcomeEmail(user) { ... }
formatUserForDisplay(user) { ... }
}
// Better: each class has one job
class UserRepository { saveToDatabase(user) { ... } }
class UserMailer { sendWelcomeEmail(user) { ... } }
class UserFormatter { format(user) { ... } }
O — Open/Closed Principle
"Software entities should be open for extension, but closed for modification."
Instead of modifying existing code to add new behavior (risking breakage), extend it through abstraction:
// Use a strategy pattern — add new discount types without touching existing logic
class DiscountCalculator {
calculate(order, strategy) {
return strategy.apply(order);
}
}
class SeasonalDiscount { apply(order) { return order.total * 0.9; } }
class LoyaltyDiscount { apply(order) { return order.total * 0.85; } }
L — Liskov Substitution Principle
"Subtypes must be substitutable for their base types without altering correctness."
If a subclass overrides a method in a way that breaks the expected contract, you've violated LSP. A common example: a Square that extends Rectangle but breaks assumptions about independent width/height setters.
I — Interface Segregation Principle
"No client should be forced to depend on methods it does not use."
Prefer many small, focused interfaces over one large "god" interface. A Printable interface shouldn't force a class to implement scan() just because some printers also scan.
D — Dependency Inversion Principle
"Depend on abstractions, not concretions."
// Bad: high-level module depends directly on low-level module
class OrderService {
constructor() { this.db = new MySQLDatabase(); }
}
// Good: depend on an abstraction, inject the implementation
class OrderService {
constructor(database) { this.db = database; }
}
Dependency injection makes your code dramatically easier to test and swap out implementations later.
Putting It All Together
You don't need to apply every principle perfectly in every situation — that leads to over-engineering. Instead, use SOLID as a diagnostic lens. When code feels hard to test, that's often a DIP or SRP violation. When adding a feature requires editing a dozen files, look for an OCP opportunity.
A Quick Reference
- S – One reason to change per class
- O – Extend, don't modify
- L – Subtypes keep their promises
- I – Small, focused interfaces
- D – Depend on abstractions
Internalizing SOLID takes time and practice. Start by applying SRP and DIP to your current project — you'll feel the difference almost immediately.