Week 135 — What is SOLID in the context of OOP?
Question of the Week #135
What is SOLID in the context of OOP?
5 Replies
SOLID is an acronym used for 5 principles of clean code in object-oriented programming:
The Single Responsibility Principle typically refers to the fact that a class should have one purpose/be responsible for one thing (part of the program) or have only a single "reason" to change. If a class is doing responsible for various things not directly related to each other, it should likely be split up into multiple classes. Sometimes, it is considered to be about each class having a single developer or group of developers being responsible for it but that definition is less common.
For example, a class managing users in a system should not be responsible for managing an inventory. This also means that general utility classes doing unrelated actions should be avoided if reasonably possible.
The Open/Closed principle means that classes should be open for extension but closed for modification. This means that classes should be written in a way that new functionality can be added without (significantly) modifying existing code. Classes should (where reasonable) permit subclasses to extend their functionality by adding additional fields, creating new methods and overriding existing methods of the superclass.
The Liskov substitution principle states that it should be possible to substitute superclasses (or interfaces) by superclasses while keeping the specification/promises of the superclass. If the superclass guarantees functionality, subclasses should keep that to make sure all code relying on these operations still works the same if a subclass is used. While this is not always possible to achieve (sometimes it is necessary to restrict or change some functionality in order to enable other functionality), this principle should be followed when it is reasonably possible. If this principle is not followed, the subclass should be passed solely to code that is known to not rely on the compromised functionality.
For example, if a class representing users promises that all users allow retrieving their roles, subclasses should adhere to that:
Interface Segregation means that an interface should only mandate the operations necessary to use it. Classes implementing an interfaces should not be required to implement methods that are not needed for their functionality. Interfaces should be modeled after what they actually represent, not by their implementation.
Instead
Product
should have been split into distinct interfaces for products (having a price) and e.g. products with a screen.
Dependency Inversion (sometimes Dependency Injection) means that classes should not be responsible for obtaining the components (e.g. objects) they need. Instead, these components should be supplied by the part instantiating objects of the class. While this can be a dependency injection framework, this is not required for the dependency inversion principle.
These principles should be used with care and not just blindly followed without considering context. The goal of these principles is to help developers write clean, maintainable code but that doesn't mean that following these principles will always result in clean code.
📖 Sample answer from dan1st
S - Single Responsibility Principle
O - Open-Closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Submission from fpznx
SOLID is an acronym that presents five fundamental design principles in any object-oriented programming. It was introduced by Robert C. Martin.
S - Single Responsibility Principle (SRP)
"A class should have only one reason to change."
This principle states that a class should have only one job or responsibility. When a class has multiple responsibilities, changes to one responsibility can affect the others, making the code fragile.
Bad Example:
Proper Example:
O - Open/Closed Principle (OCP)
"Software entities should be open for extension but closed for modification."
You should be able to extend a class's behavior without modifying its existing code, typically obtained through inheritance and polymorphism.
Bad Example:
Good Example:
L - Liskov Substitution Principle (LSP)
"Objects of a superclass should be replaceable with objects of a subclass without breaking the application."
Derived classes must be substitutable for their base classes without altering the way the program is supposed to function.
Good Example:
I - Interface Segregation Principle (ISP)
"No client should be forced to depend on methods it does not use."
Instead of one large interface, create multiple smaller, specific interfaces so that clients only need to know about methods that are relevant to them.
Bad Example:
Good Example:
D - Dependency Inversion Principle (DIP)
"High-level modules should not depend on low-level modules. Both should depend on abstractions."
Depend on abstractions (interfaces) rather than a concrete implementation.
Bad Example:
Good Example:
Benefits of Following SOLID Principles:
- Maintainability: Code is easier to understand and modify
- Flexibility: Easy to extend functionality without breaking existing code
- Testability: Classes with single responsibilities are easier to unit test
- Reusability: Well-designed components can be reused in different contexts
- Reduced Coupling: Components are less dependent on each other
- Better Organization: Code structure becomes more logical and predictable
Drawbacks of Following SOLID Principles:
i) Over-Engineering and Unnecessary Complexity:
One of the most common criticisms of SOLID is that it can lead to over-engineering simple solutions. Sometimes developers may create elaborate class hierarchies and interfaces when a simple, straightforward approach would suffice.
ii) Performance Overhead
SOLID principles often introduce additional layers of abstraction, which can impact performance:
- Interface calls: Virtual method calls through interfaces can be slower than direct method calls.
- Object creation: More objects and abstractions mean more memory allocation and garbage collection.
- Reflection overhead: Dependency injection frameworks often use reflection, which has performance costs in runtime.
There are a ton of disadvantages/drawbacks that can be discussed about SOLID principle, however the key is know when to apply SOLID Principles and finding balance.
You should ask yourself these questions:
- Is this code likely to change frequently?
- Do I need to support multiple implementations?
- Is the complexity justified by the flexibility gained?
- Will other developers need to extend this code?
Practical Guidelines:
- Start simple and refactor toward SOLID when complexity justifies it
- Consider the YAGNI principle (You Aren't Gonna Need It)
- Balance flexibility with simplicity
- Don't create abstractions until you have at least 2-3 concrete implementations
⭐ Submission from daysling