Pages

SOLID Principles Explained








SOLID in the acronym, named by Michael Feathers, that represents five design principles present in Object Oriented Programming. The theory of these principles was explained by Robert C. Martin, in the lecture Design Principles and Design Patterns. In this paper, Uncle Bob describes how the software can present some symptoms of rotting design, like:  
  • Rigidity: tendency of the software to be difficult to change. The most common reason is the higher interchangeability between the modules that compose the program; 
  • Fragility: the tendency of the software to break in many places, every time it is changed. Sometimes this can occur in areas not related directly with the area that was changed; 
  • Immobility: when it’s nos possible to reuse the software or parts of it in other projects, because of the dependencies. The solution is to rewrite everything; 
  • Viscosity: when the development team needs to implement some “hacks” to preserve the current behavior (front or back). 
The idea of SOLID principles is to solve the problems above, with some techniques that can provide the software design more understandable, flexible and maintainable. 

SRP - Single Responsibility Principle 

“A class should only, and only one, reason to change” 


This means that a class should have the responsibility over a single part of the functionality provided by the software. If you have a class with many dependencies (reasons to change), one change can have directly influence in the entire code, and one change can break everything. Look at this example: 

This class has lots of behaviors: validate email, connect with database and send email. The objective of AddClient() method should be add the client, just this, without worry about database connection, the mail settings or validation strategies: 

We can divide the class, to provide unique behaviors. This class is now responsible. It has the properties, and the condition to validate it’s own consistency. The rules to validate specific properties (like email), are implemented in specific services (unique responsibility again): 

EmailService class, responsible to manage emails: 

This is the repository class, with the responsibility focused on the connection with the database: 

The ClientService class, responsible to add the client:  


OCP – Open/Closed Principle 

“Software entities (classes, modules, functions, etc.)  
should be open for extension, but closed for modification.”



Through OCP, it’s possible to create extensible modules, without the necessity of change the existing code, when you add some new feature. Abstraction is the key of OCP. 

Here is the example of a Product class, that contains properties like Size and Color: 

If your boss comes with the idea to add some filter, you can solve this in an easily way, adding a simple class: 
  
Few days after, you must add another filter (by Color) and,  after some time, another filter (this time by Color and Size). You can perceive that this class, ProductFilter, is not open for extension, just for modification, breaking the OCP Principle. How can we solve the problem? 

Let’s use the Specification Pattern, to create a Filter that create an abstraction to our validation: 

And now we can have our particular implementation of the filter, and add many mores if you want:  

And now we can use in a more focused way, using the correct specification: 


LSP – Liskov Substitution Principle 

“Subclasses should be substitutable for their base classes” 

If it looks like a duck, quacks like a duck, but need batteries,  
you probably have the wrong abstraction 
  
The Liskov Principle, introduced by Barbara Liskov in 1987 explains how objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. 

In this example, we have a problem between the base class and the subclasses: 

 To solve the problem, we can create a new class above both: 


ISP – Interface Segregation Principle 

“No client should be forced to depend on methods it does not use” 


This principle defines that it is better to use lots of specific interfaces rather than a single one. This strategy provides a more decoupled solution, turning into an easier to refactor and change solution. 

Suppose that you have an interface to implement different types of interactions in a document: 

The implementation sounds good isn’t it? 

But now, if we want to implement an Old Fashioned Printer that will have only the Print feature, we should expose all the methods that are defined in the interface: 

The solution for this, is turn into a more atomic solution. First thing is to divide the interface into multiple interfaces with single purpose: 

And now we can define a class specifically for the purpose of scanning and printing, for example: 

Another strategy is to create multiple specific interfaces, and use the Decorator Pattern to solve the dependencies: 


DIP – Dependency Inversion Principle 

“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.”



The dependency inversion principle is a way for decoupling our application. The idea is to depend on an abstraction (e.g. interfaces), not directly from one implementation. 

Remember the class Client of the SRP example? Here we have a tightly couple EmailServices that Client depends on. Any change in this concrete class can break the class Client entirely: 

Same problem here in the ClientService. This class has a directly dependence with ClientRepository and EmailService classes: 

The solution is to expose the classes that I’ll inject as interfaces. In this case IEmailServices: 

And now, I can inject in the Client class. It’s low coupled now: 

Same thing for ClientService: 

Just one observation: for this code to work, we should use a dependency injection container. You should tell the container what the concrete class you should instantiate, according with the interface you are injecting. They are lot’s of different container you can use to do this. The .NET Core has his own DI container, and I’ve added a list of container in the references. 

References used for the examples: 

I hope this material can be useful for you in your studies. You can clone the Github repository with the code I used in the examples. 

Thank you.  

Fabio Ono

No comments:

Post a Comment