The SOLID Principles (of OOP)
Each of the following principles can stand on its own and has the goal to improve the robustness and maintainability of object-oriented applications and software components. But they also add to each other so that applying all of them makes the implementation of each principle easier and more effective.
- Single Responsibility Principle (SRP)
- A single component should have a single, well-defined responsibility and should not combine unrelated functionality.
- Open/Closed Principle (OCP)
- A class should be open to extension, but closed for modification. Inheritance is one way to accomplish this. Other mechanisms are templates, function overloading, and more. In general, we speak about customization points in this context.
- Liskov Substitution Principle (LSP)
-
You should be able to replace an instance of an object with an instance of a subtype of that object without breaking the application.
You can achieve that by following a few rules, which are pretty similar to the design by contract concept defined by Bertrand Meyer.
An overridden method of a subclass needs to accept the same input parameter values as the method of the superclass. That means you can implement less restrictive validation rules, but you are not allowed to enforce stricter ones in your subclass. Otherwise, any code that calls this method on an object of the superclass might cause an exception, if it gets called with an object of the subclass.
Similar rules apply to the return value of the method. The return value of a method of the subclass needs to comply with the same rules as the return value of the method of the superclass. You can only decide to apply even stricter rules by returning a specific subclass of the defined return value, or by returning a subset of the valid return values of the superclass.
- Interface Segregation Principle (ISP)
- Keep interfaces clean and simple. It is better to have many smaller, well-defined single-responsibility interfaces than to have broad, general-purpose interfaces.
- Dependency Inversion Principle (DIP)
-
The general idea is quite simple: High-level modules, which provide complex logic, should be easily reusable and unaffected by changes in low-level modules, which provide utility features. To achieve that, you need to introduce an abstraction that decouples the high-level and low-level modules from each other.
Based on this idea, Robert C. Martin's definition of the Dependency Inversion Principle consists of two parts:
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions.
An important detail of this definition is, that high-level and low-level modules depend on the abstraction. The design principle does not just change the direction of the dependency, as you might have expected when you read its name for the first time. It splits the dependency between the high-level and low-level modules by introducing an abstraction between them. So in the end, you get two dependencies:
- the high-level module depends on the abstraction, and
- the low-level depends on the same abstraction.