Every module, class or function should have responsibility over a single part of the functionality of the software, and that responsibility should be encapsulated entirely by the module, class or function.
Software entities should be open for extension but closed for modification.
When you inherit, feel free to add or reimplement functionality, but don't break it.
There should be an interface that includes only the things the client actually needs.
A. High-level modules should not depend on low-level modules. Both should depend on abstractions.
B. Abstractions should not depend upon details. Details should depend upon abstractions.
By SRP, we separate a module with too many responsibilities into several modules each covers a single part of the responsibility.
By OCP, we can either provide a custom behavior implementation to an existing class or have written a class that required a custom behavior to be supplied.
We can prevent violating LSP by limiting the abstraction to only what is common among all of the subclasses, and ensuring that no shape has a different meaning for a common attribute.
ISP is essencially the SRP but for interfaces.
We always want to reduce coupling where is possible, and that's why we decouple and invert dependencies according to DIP.
Achieve low coupling --- OCP, DIP
Achieve high cohesion --- SRP, OCP, DIP
Achieve strong encapsulation --- LSP, SRP, DIP
Reference
https://www.codemag.com/article/1001061