SOLID Principles
SOLID Principles
SOLID is five design principles formulated by Robert C. Martin that guide object-oriented design toward maintainability, extensibility, and testability. They are not rules to follow mechanically but lenses for diagnosing why code is hard to change.
Single Responsibility Principle (SRP)
A class should have one reason to change. Reason to change = domain responsibility. A User class that handles authentication, profile management, email sending, and database persistence has four reasons to change. When the email service changes its API, you're editing the User class — but User isn't about email.
Violation symptoms: classes with >200 lines, methods that do setup + business logic + I/O, test files that require enormous mocks to isolate a single behavior.
SRP at the module level: a module responsible for one domain concept means changes in that domain touch one module, not many. This is the basis of domain-driven design.
Open/Closed Principle (OCP)
Software should be open for extension but closed for modification. The original formulation (Bertrand Meyer) meant inheritance. The modern interpretation: you add new behavior by adding new code — a new class, a new function, a new decorator — not by editing existing, tested code.
A payment processor with a switch statement on payment type violates OCP: adding a new payment type requires editing the switch statement, risking regressions in existing payment types. A better design: a PaymentStrategy interface with implementations per type. Adding a new type adds a new class, not a modification.
OCP is enabled by good abstractions. Where you expect extension points, define interfaces. Where the logic is stable, keep it closed.
Liskov Substitution Principle (LSP)
If S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering program correctness (Barbara Liskov, 1987).
Classic violation: Square extends Rectangle. Rectangle has setWidth(w) and setHeight(h) that operate independently. Square overrides both to keep sides equal. Code that sets width and height independently then checks the area will fail for Square instances — a Square is not substitutable for a Rectangle in this context.
LSP violations often manifest as: subclass methods that throw NotImplementedException, type-checking with instanceof in code that should be polymorphic, or subclasses that weaken postconditions (return narrower output) or strengthen preconditions (require more of callers).
Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they do not use. Fat interfaces force implementors to provide stubs for irrelevant methods and force callers to depend on things they don't need.
A Worker interface with methods work(), eat(), and sleep() forces a Robot class to implement eat() and sleep() with empty stubs or exceptions. Split into Workable (work()), Feedable (eat()), Restable (sleep()). Robot implements only Workable.
ISP is especially relevant in TypeScript/Java interfaces used for dependency injection. A narrow interface is also easier to mock in tests.
Dependency Inversion Principle (DIP)
High-level policy modules should not depend directly on low-level mechanism modules. Both should depend on abstractions.
Without DIP: OrderService directly instantiates EmailSender, MySQLRepository, and StripePayment. To test OrderService, you need a real email server, database, and payment processor. Every change to those implementations requires changes in OrderService.
With DIP: OrderService depends on IEmailSender, IOrderRepository, IPaymentProcessor interfaces. Implementations inject through the constructor. Tests inject fakes. Swapping MySQL for Postgres touches only the repository implementation, not OrderService.
DIP is the theoretical foundation of dependency injection frameworks (Spring, .NET DI, NestJS providers).
Using SOLID as Diagnostics
SOLID principles are most useful as diagnostic tools:
- Code hard to test → DIP violation (concrete dependencies)
- Change one feature, break another → SRP violation (mixed responsibilities)
- Adding a feature requires editing existing code everywhere → OCP violation (no extension points)
- Implementing an interface requires stubs → ISP violation (interface too broad)
- Subclass breaks tests written for parent → LSP violation
Apply them selectively. Over-engineering a simple script to be SOLID-compliant is worse than a pragmatic SRP violation. SOLID matters when code is shared, evolved, and tested over time.