SOLID Principles
Single responsibility, open/closed, Liskov, interface segregation, dependency inversion
The SOLID principles, coined by Robert C. Martin, are five object-oriented design guidelines that produce maintainable, extensible, and testable software. Adhering to SOLID reduces coupling, increases cohesion, and makes systems resilient to requirement changes. Violations manifest as rigidity (hard to change), fragility (breaks in unexpected places), and immobility (impossible to reuse). These principles underpin frameworks like Spring, Angular, and ASP.NET Core.
Key Points
- SRP: A class should have exactly one reason to change — separates concerns, enabling independent evolution.
- OCP: Open for extension, closed for modification — use abstractions, strategy pattern, or decorators to add behavior without touching existing code.
- LSP: Subtypes must be substitutable for their base types — violated when a Square extends Rectangle and breaks setWidth/setHeight invariants.
- ISP: Prefer narrow, role-specific interfaces over fat interfaces — prevents clients from depending on methods they do not use.
- DIP: High-level modules must not depend on low-level modules; both depend on abstractions — enables dependency injection and testability.
- Applying SOLID together prevents "God classes" and monolithic modules that become change magnets.
- SOLID does not mandate a specific language; apply it in Go (interfaces), Rust (traits), and TypeScript (abstract classes) alike.
| Principle | One-Line Definition | Classic Violation Example |
|---|---|---|
| Single Responsibility (SRP) | One class, one reason to change | UserService handles auth, email, and DB persistence in one class |
| Open/Closed (OCP) | Open for extension, closed for modification | Adding a new payment type requires editing a giant switch statement in PaymentProcessor |
| Liskov Substitution (LSP) | Subtypes must behave like their base type | Square overrides Rectangle.setWidth and silently breaks area calculations for callers expecting a Rectangle |
| Interface Segregation (ISP) | No client should depend on unused methods | IWorker interface has eat(), work(), and sleep() — forcing Robot to implement eat() |
| Dependency Inversion (DIP) | Depend on abstractions, not concretions | OrderService directly instantiates MySQLRepository instead of injecting an IRepository interface |
Real-World Example
Angular itself is built on DIP and ISP: components inject abstract HttpClient tokens, and Angular Material uses narrow component interfaces. Spring Boot's @Autowired is DIP in practice — production code depends on interfaces, not implementations.