Theory
SOLID Principles
📋 Overview
SOLID is an acronym for five foundational principles of object-oriented design intended to make software more understandable, flexible, and maintainable. Coined by Robert C. Martin (Uncle Bob), these principles help developers avoid "code rot" and build systems that are easy to refactor and extend over time.
🏗️ Core Principles & Characteristics
- Single Responsibility (SRP): A class should have one, and only one, reason to change.
- Open/Closed (OCP): Software entities should be open for extension but closed for modification.
- Liskov Substitution (LSP): Subtypes must be substitutable for their base types without altering program correctness.
- Interface Segregation (ISP): Clients should not be forced to depend on interfaces they do not use.
- Dependency Inversion (DIP): Depend on abstractions, not on concrete implementations. High-level modules should not depend on low-level modules.
⚖️ Trade-offs: Pros & Cons
Pros
- Decoupling: Reduces the interdependency between components, making the system more modular.
- Testability: Smaller, focused classes and interfaces are significantly easier to unit test.
- Agility: New features can be added by extending existing code rather than rewriting it, reducing the risk of regressions.
Cons
- Over-Engineering: Applying SOLID to a simple script can lead to unnecessary layers of abstraction and complexity.
- Verbosity: Following these principles often results in a larger number of files and more "boilerplate" code.
- Initial Development Time: It takes more thought and time to design a SOLID-compliant system initially, though it saves time in the long run.
🌍 Real-World Implementation
- Spring Framework (Java): A prime example of Dependency Inversion and Interface Segregation, allowing developers to swap implementations via Dependency Injection.
- React Components: SRP is often applied by breaking large components into smaller "presentational" and "container" components.
- Strategy Pattern: Frequently used to adhere to the Open/Closed principle by allowing new algorithms to be added without changing the context class.
💡 Interview "Gotchas" & Tips
- LSP and the "Square-Rectangle" Problem: This is the classic example. Be ready to explain why a
Squareinheriting fromRectangleoften violates LSP (changing the width of a square also changes its height, which a rectangle doesn't expect). - DIP vs. Dependency Injection (DI): Clarify that DIP is the principle (high-level goal), while DI is a technique (a way to achieve that goal).
- Practical Application: Don't just list the definitions. Mention that these are "guides, not laws." Sometimes, "pragmatic" code is better than "purely SOLID" code if the latter adds too much complexity.
📐 Suggested Architecture Primitives
- Interfaces/Protocols: To define contracts between modules.
- Dependency Injection Container: (e.g., InversifyJS, Spring IoC) To manage object instantiation and wiring.
- Composition over Inheritance: A foundational mindset that supports many SOLID principles.
- Factories and Providers: To encapsulate the creation of complex objects while hiding implementation details.
Canvas