What is SOLID principles
SOLID is a mnemonic for five design principles intended to make software designs more understandable, flexible and maintainable.
As with everything in life, using these principles mindlessly can cause more harm than good. The cost of applying these principles into a program’s architecture might be making it more complicated than it should be. I doubt that there’s a successful software product in which all of these principles are applied at the same time. Striving for these principles is good, but always try to be pragmatic and don’t take everything written here as dogma.
SOLID first principle: Single Responsibility Principle
A class should have just one reason to change.
Try to make every class responsible for a single part of the functionality provided by the software, and make that responsibility entirely encapsulated by (you can also say hidden within) the class
SOLID second principle: Open/Closed Principle
Classes should be open for extension but closed for modification.
The main idea of this principle is to keep existing code from breaking when you implement new features.
A class is open if you can extend it, produce a subclass and do whatever you want with it—add new methods or fields, override base behavior, etc. Some programming languages let you restrict further extension of a class with special keywords, such as final. After this, the class is no longer open. At the same time, the class is closed (you can also say complete) if it’s 100% ready to be used by other classes—its interface is clearly defined and won’t be changed in the future.
SOLID third principle: Liskov Substitution Principle
When extending a class, remember that you should be able to pass objects of the subclass in place of objects of the parent class without breaking the client code.
This means that the subclass should remain compatible with the behavior of the superclass. When overriding a method, extend the base behavior rather than replacing it with something else entirely.
The substitution principle is a set of checks that help predict whether a subclass remains compatible with the code that was able to work with objects of the superclass. This concept is critical when developing libraries and frameworks because your classes are going to be used by other people whose code you can’t directly access and change.
SOLID fourth principle: Interface Segregation Principle
Clients shouldn’t be forced to depend on methods they do not use.
Try to make your interfaces narrow enough that client classes don’t have to implement behaviors they don’t need.
According to the interface segregation principle, you should break down “fat” interfaces into more granular and specific ones. Clients should implement only those methods that they really need. Otherwise, a change to a “fat” interface would break even clients that don’t use the changed methods.
Class inheritance lets a class have just one superclass, but it doesn’t limit the number of interfaces that the class can implement at the same time. Hence, there’s no need to cram tons of unrelated methods to a single interface. Break it down into several more refined interfaces—you can implement them all in a single class if needed. However, some classes may be fine with implementing just one of them.
SOLID fifth principle: Dependency Inversion Principle
High-level classes shouldn’t depend on low-level classes. Both should depend on abstractions. Abstractions shouldn’t depend on details. Details should depend on abstractions.
Usually when designing software, you can make a distinction between two levels of classes.
Low-level classes implement basic operations such as working with a disk, transferring data over a network, connecting to a database, etc.
High-level classes contain complex business logic that directs low-level classes to do something.
Sometimes people design low-level classes first and only then start working on high-level ones. This is very common when you start developing a prototype on a new system, and you’re not even sure what’s possible at the higher level because low-level stuff isn’t yet implemented or clear. With such an approach business logic classes tend to become dependent on primitive low-level classes.
The dependency inversion principle suggests changing the direction of this dependency.
For starters, you need to describe interfaces for low-level operations that high-level classes rely on, preferably in business terms. For instance, business logic should call a method openReport(file) rather than a series of methods openFile(x), readBytes(n), closeFile(x). These interfaces count as high-level ones.
Now you can make high-level classes dependent on those interfaces, instead of on concrete low-level classes. This dependency will be much softer than the original one.
Once low-level classes implement these interfaces, they become dependent on the business logic level, reversing the direction of the original dependency.
The dependency inversion principle often goes along with the open/closed principle: you can extend low-level classes to use with different
REF
https://barryvanveen.nl/articles/51-8-resources-to-learn-about-solid-design-principles