Different design principles are aimed at solving a specific design problem, and in some cases they may contradict each other .
It can be said that different principles “pull” the design in different directions and you need to find the right vector, which is most useful in this particular case: SRP - speaks about the simplicity of the solution, OCP - about the isolation of module components, DIP - about the “correctness” of relations between classes, and LSP is about “correct” polymorphism.
Following one principle may violate another. So, for example, any inheritance can be considered as a violation of SPR , since now a single group of classes is responsible for one responsibility (drawing figures). Following DIP and OCP can lead to the appearance of additional “seams”, i.e. interfaces / base classes in the system, which, again, will lead to violation of SRP and / or ISP .
But this relationship between principles is not fixed. For a simple case, highlighting a hierarchy of figures for drawing is a violation of SRP , since the “drawing” in the first iteration may consist in outputting text to the console and spreading this information across several classes will be redundant. But as the decision becomes more complex, the appearance of the inheritance hierarchy will be justified from the point of view of the SRP , since the complexity of displaying each individual figure will be so high that the concept of “responsibility” will also change. If at the beginning “single responsibility” was the display of all the figures, then now one responsibility will be divided into many: “circle display”, “square display”, etc.
The principle of YAGNI (You Aren't Gonna Need It) is a more fundamental principle (“higher order principle” or “meta principle”), which helps to understand when to follow principles / patterns / rules and when not.
The principle of YAGNI is based on several observations:
- Programmers, like people in general, poorly predict the future.
- No flexible solution will be flexible enough.
These observations lead to the following conclusions: an attempt to create a flexible solution in the early stages of development is doomed to create an over-complicated solution . This is due to the fact that in the early stages it is not yet known what kind of changes in the system will be needed, and it is simply not clear where to “lay the straw” for future changes.
Since we don’t know at the early stages what kind of flexibility is needed, we will put the flexibility not where it is needed: we will foresee the replacement of the data access layer, but due to “leaky abstractions”, we will still fix the solution on a specific database, or such flexibility just never needed. We will create a “framework” for parsing command line arguments that will be used in one application, and the cost of screwing it into another application will be so great that no one will do it.
Good design is the simplicity of the solution when changing requirements leads to linear labor costs.
The easiest way to achieve this is through evolutionary design: we start by dividing the system into large components, but we are not engaged in isolating the excess. Base classes are not needed if there are currently no at least 2 or 3 heirs. And even if such heirs "may appear in the future," then it is necessary to select the type hierarchy exactly when this very future comes.
The principle of YAGNI can be expressed as follows: the allocation of unnecessary abstractions (and any other complication) is justified only if the cost of their allocation in the future will be significantly more expensive than now.
Investments in the reasonableness of the library programming interface (API) will be justified, since the cost of making changes is very high. The cost of separating the interface / base class of an application is almost the same today or in a year.
Solving the problem as it is received allows you to focus on the tasks that are relevant today and allows you to avoid work that you may not need at all.
PS Well, it seems to me that you have not quite a correct understanding of the principles of OCP and DIP , which are not at all reduced to the need to use inheritance.
Here are some related articles:
And separately, in the article “On Design Principles”, I consider approximately the same thing as in this answer: that blindly following the principles will lead to an over complicated and heavy solution.