In object-oriented computer programming, the term SOLID is a mnemonic acronym for five design principles intended to make software designs more understandable, flexible and maintainable. Though they apply to any object-oriented design, the SOLID principles can also form a core philosophy for methodologies such as agile development or adaptive software development.
Single Responsibility Principle
A class should have one, and only one, reason to change.
– Robert C. Martin (Uncle Bob)
The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.
In development world, we are aware that requirements change over time. With change of requirement, it may force us to create another class or change (adding) the responsibility of at least one class. The more responsibilities your class has, the more often you need to change it. If your class implements multiple responsibilities, they are no longer independent of each other. And even worse, if one class is changed, the dependencies also need to be aware about the changes; on how they interact with the class, or do we need to recompile them. And imagine the domino effect if we are also forced to adding more responsibilities to dependency classes.
See the side-effects of change/adding of responsibility in one class, and how it snowballing to it's dependencies. Regardless the size of project, it’s better to avoid these problems by making sure that each class has only one responsibility. To start implement this, is not difficult. Start by defining what is the responsibility of our class/component/micro-service? If our 'definition' includes the word "and", we're most likely breaking the single responsibility principle. It’s better take a step back and rethink our design, there is most likely a better way to implement it.
Classes, software components and micro-services that have only one responsibility are much easier to explain, understand and implement than the ones that doing (too) many things. As easier to understand, is easier for programmer to debug, investigate a problem/find bugs, and make enhancements. Sure you can make many enhancements to improve your software quality, as long it's still in single responsibility boundary.
Open/closed Principle
Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.
– Bertrand Meyer (Object-Oriented Software Construction - 1988)
Bertrand Meyer is generally credited for having originated the term open/closed principle, which appeared in his 1988 book Object Oriented Software Construction.
- A module will be said to be open if it is still available for extension. For example, it should be possible to add fields to the data structures it contains, or new elements to the set of functions it performs.
- A module will be said to be closed if [it] is available for use by other modules. This assumes that the module has been given a well-defined, stable description (the interface in the sense of information hiding).
At the time Meyer was writing, adding fields or functions to a library inevitably required changes to any programs depending on that library. (Unfortunately) Meyer's proposed solution to this dilemma relied on the notion of object-oriented inheritance (specifically implementation inheritance):
A class is closed, since it may be compiled, stored in a library, baselined, and used by client classes. But it is also open, since any new class may use it as parent, adding new features. When a descendant class is defined, there is no need to change the original or to disturb its clients.
In later development, Robert C. Martin and others redefined the Open/Closed Principle to the Polymorphic Open/Closed Principle. This definition advocates inheritance from abstract base classes. Interface specifications can be reused through inheritance but implementation need not be. The existing interface is closed to modifications and new implementations must, at a minimum, implement that interface.
Liskov Substitution Principle
Liskov substitution principle extends the Open/Closed Principle by focusing on the behavior of a superclass and its subtypes.
"objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program."
That requires the objects of our sub-classes to behave in the same way as the objects of our super-class. We can achieve this by following a few rules, which are pretty similar to the design by contract concept. As example: an overridden method of a subclass needs to accept the same input parameter values as the method of the super-class. We can implement less restrictive validation rules, but not allowed to enforce stricter ones in our subclass. Otherwise, any code that calls this method on an object of the super-class might cause an exception, if it gets called with an object of the subclass.
Interface Segregation Principle
Clients should not be forced to depend upon interfaces that they do not use.
– Robert C. Martin (Uncle Bob)
We quite often violating these two principles (Single Responsibility and Interface Segregation) since our software always evolves and we need to enhance, and add more features.
many client-specific interfaces are better than one general-purpose interface."
Similar to the Single Responsibility Principle, the goal of the Interface Segregation Principle (ISP) is to reduce the side effects and frequency of required changes by splitting the software into multiple, independent parts or modules. ISP splits interfaces that are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. Such shrunken interfaces are also called role interfaces. ISP is intended to keep a system decoupled and thus easier to refactor, change, and redeploy
Dependency Inversion Principle
one should "depend upon abstractions, [not] concretions."
The Dependency Inversion Principle is a specific form of decoupling software modules. 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 this, we need to have an abstraction that decouples the high-level and low-level modules from each other.
The principle states:
- 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