SOLID is an acronym for five object-oriented design principles introduced by Robert Martin in the early 2000s. Each principle addresses a specific failure mode of object-oriented design — symptoms you recognise in real codebases: classes that are impossible to extend, modules that break in unexpected places when changed, code that cannot be tested in isolation. SOLID principles are not rules to apply mechanically — they are lenses for identifying design problems and guiding refactoring. This lesson covers each principle precisely, shows the violation before the fix, and explains the architectural implications.
Martin's formulation: 'A class should have only one reason to change.' The word 'reason' is key — it refers to an actor or stakeholder whose requirements might drive a change, not a syntactic 'one method' rule. A class that formats reports AND saves them to the database has two reasons to change: the reporting team (format changes) and the ops team (database migration). When those changes happen simultaneously, they conflict. SRP says these concerns belong in separate classes.
Bertrand Meyer's formulation (1988), reframed by Martin: 'Software entities should be open for extension, but closed for modification.' A class is closed for modification when its tested, deployed code does not need to change to accommodate new behavior. It is open for extension when new behavior can be added by writing new code — a new subclass, a new strategy implementation, a new decorator — without touching the existing class. The most common mechanism: depend on abstractions (interfaces) and inject concrete implementations.
Barbara Liskov's formulation (1987): 'If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program.' In plain terms: a subclass must honor the contract of its superclass — not just its method signatures, but its behavioral expectations (preconditions, postconditions, invariants). The classic LSP violation is the Square-Rectangle problem: Square extends Rectangle, but overriding setWidth to also set height violates the rectangle's contract that width and height are independent.
Martin's formulation: 'Clients should not be forced to depend on interfaces they do not use.' Fat interfaces — interfaces with many unrelated methods — force implementors to provide stub implementations of methods they do not need, and force clients to import a type that carries capabilities they do not use. ISP says: prefer many small, client-specific interfaces over one large general-purpose interface. The design pressure from ISP naturally produces Role Interfaces — interfaces that describe a role an object plays (Printable, Saveable, Auditable) rather than the full menu of everything an object can do.
Martin's formulation: '(A) High-level modules should not depend on low-level modules. Both should depend on abstractions. (B) Abstractions should not depend on details. Details should depend on abstractions.' DIP is the principle that underlies Onion Architecture, Hexagonal Architecture, and Clean Architecture — all the architectures covered in the previous chapter. It is also the principle that makes dependency injection meaningful: DI is the mechanism; DIP is the reason.