Another acronym in software engineering?! That is not very special, or is it? It looks SOLID, but let’s see… This article aims to give a explanation of SOLID Principles and give some insight on their benefits and potential issues when applying them. Let’s go through each of them briefly. solid S — Single Responsibility Principle(S.R.P) A class should have one, and only one, reason to change. When requirements change, this implies that the code has to undergo some reconstruction, meaning that the classes have to be modified. The more responsibilities a class has, the more change requests it will get, and the harder those changes will be to implement. The responsibilities of a class are coupled to each-other, as changes in one of the responsibilities may result in additional changes in order for the other responsibilities to be handled properly by that class. What is a responsibility?! A responsibility can be defined as a reason for change. Whenever we think that some part of our code is potentially a responsibility, we should consider separating it from the class. Let’s say we are working on a project that helps people become more active in their community, and the system needs to have social media integration. It would be a good idea to separate the social media integration responsibility from the other parts of the system, as we should always be prepared for external changes. If you want to see SRP in action, check out about PORO(Plain Old Ruby Objects), which explains how SRP is implemented in Ruby on Rails applications to make the codebase scalable and maintainable. this article Have you ever heard of something being Open and Closed at the same time?! Well, I certainly have, so lets take a look at it together. O — Open-Closed Principle You should be able to extend a class’s behavior, without modifying it. This principle is the foundation for building code that is maintainable and reusable. Robert C. Martin How can something be open closed?! A class follows the OCP if it fulfills these two criteria: and Open for extension This ensures that the class behavior can be extended. As requirements change, we should be able to make a class behave in new and different ways, to meet the needs of the new requirements. Closed for modification The source code of such a class is set in stone, no one is allowed to make changes to the code. How do we achieve this? Through . In order to be able to extend the behavior of a class without changing a single line of code, we need to make abstractions. For example, if we had a system that works with different shapes as classes, we would probably have classes like , , etc. In order for a class that depends on one of these classes to implement OCP, we need to introduce a interface/class. Then, wherever we had Dependency Injection, we would inject a instance instead of an instance of a lower-level class. This would give us the luxury of adding new shapes without having to change the dependent classes’ source code. abstractions Circle Rectangle Shape Shape How do we know whether to make a class or an interface? For that, we’ve got the Liskov Substitution Principle, which tells us when inheritance is suitable. Let’s take a look, shall we? Shape L — Liskov Substitution Principle Derived classes must be substitutable for their base classes. What is wanted here is something like the following substitution property: Iffor each object o1 of type S there is an object o2 of type T such that for allprograms P defined in terms of T, the behavior of P is unchanged when o1 issubstituted for o2 then S is a subtype of T. Barbara Liskov Let’s visualize the definition with a case study. Let’s say we have a class, and we have a class that extend it, . Let’s also say that has two methods, and , which, well, set the width and height of the rectangle respectively. Rectangle Square Rectangle setWidth setHeight The problem is that the behavior for the two methods differs between the and the classes. The reason for that is that a , by mathematical definition is a Rectangle with equal height and width. So, the two methods will change the same value, whereas for the , they will change the width and height respectively, which are different values from each other. Rectangle Square Square Rectangle When we are using abstraction(Open-Closed Principle), we want the methods to behave the same for each derived class, and not differently. In this case, we can clearly see that a class should not be extending the class, because the behavior of the inherited methods differs. Square Rectangle The solution As Robert C. Martin suggests , we should . What this means is that each method should have and defined. Preconditions must hold true in order for a method to execute, and postconditions must hold true after the execution of a method. here design by contract preconditions postconditions …when redefining a routine [in a derivative], you may only replace itsprecondition by a weaker one, and its postcondition by a stronger one. Bertrand Meyer This is a clear and to-the-point explanation of this definition that I found : online Assume your baseclass works with a member int. Now your subtype requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken. Likewise, assume the same scenario, but the base class used to guarantee that the member would be positive after being called. Then the subtype changes the behavior to allow negative ints. Code that works on the object (and assumes that the post-condition is a positive int) is now broken since the post-condition is not upheld. Robert C. Martin suggests that it is helpful to document(comments) the preconditions and postconditions for each method. I — Interface Segregation Principle Make fine grained interfaces that are client specific. Clients should not be forced to implement interfaces they do not use. Robert C. Martin In other words, it is better to have many smaller interfaces, than fewer, fatter interfaces. For example, let’s say we had an interface called , which would have , and methods. This would mean that we have a monolithic interface called , which would not be the perfect abstraction, because some animals can . Breaking this monolithic interface into smaller interfaced based by , we would get , and interfaces. This would then make it possible for a species to , and for example . A species would be a combination of roles, instead of being characterized as an animal, which would not necessarily be the best description. At a larger scale, microservices are a very similar case, they are pieces of a system separated by responsibilities, instead of being a great monolith. Animal eat sleep walk Animal fly role CanEat CanSleep CanWalk eat sleep fly By breaking down interfaces, we favor Composition instead of Inheritance, and Decoupling over Coupling. We favor composition by separating by roles(responsibilities) and Decoupling by not coupling derivative classes with unneeded responsibilities inside a monolith. D — Dependency Inversion Principle Depend on abstractions, not on concretions. A. High level modules should not depend upon low level modules. Both should depend upon abstractions. B. Abstractions should not depend upon details. Details should depend upon abstractions. Robert C. Martin Let’s say we have a system that handles authentication through external services such as Google, GitHub, etc. We would have a class for each service: , , etc. Now, let’s say that some place in our system, we need to authenticate our user. To do that, as mentioned, we have several services available. To be able to make use of all the services, we have two possibilities: We either write a piece of code that adapts each service to the authentication process, or we define an abstraction of the authentication services. The first possibility is a dirty solution that will potentially introduce technical debt in the future; in case a new authentication service is to be integrated to the system, we will need to change the code, which as a result violates the . The second possibility is much cleaner, it allows for future addition of services, and changes can be done to each service without changing the integration logic. By defining a interface and implementing it in each service, we would then be able to use Dependency Injection in our authentication logic and have our authentication method signature look something like this: . Then, we could authenticate by a specific service like this: . This helps us generalize the authentication logic without having to integrate each service separately. GoogleAuthenticationService GitHubAuthenticationService OCP AuthenticationService authenticate(AuthenticationService authenticationService) authenticate(new GoogleAuthenticationService) By depending on higher-level abstractions, we can easily change one instance with another instance in order to change the behavior. Dependency Inversion increases the reusability and flexibility of our code. Benefits Following principles always has benefits. It is no different in software engineering. Following the SOLID Principles gives us many benefits, they make our system reusable, maintainable, scalable, testable and more. For more about the benefits of each of these principles, make sure to read Uncle Bob’s . articles For a fun and simple explanation of the SOLID principles, check out these . photos Feel free to if you have any questions, challenging opportunities, or you just want to say hi. email me Finally, be sure to so that you do not miss any new stories I publish. follow me