Principios SOLID en C# y .NET
| Author: | Raul Lopez |
| Category: | backend |
| Elapsed time: | 1 year ago |
| Published: | 10/21/2024 |
| Description: | Descubre cómo aplicar los principios SOLID en tus proyectos de C# y .NET para mejorar la estructura, mantenibilidad y escalabilidad de tu código. |
El motivo de este post es para ademas de poder convertirnos en un mejor programador o programadora la intencion es que obtengamos conocimientos de buenas practicas y podamos resolver desafios comunes que se presenten dia a dia cuando uno esta programando.
Aparte de esto nos servira este post demasiado para estar preparados para entrevistas de trabajo para puestos .NET.
La metodologia de este post es bastante siemple, analizaremos de manera teorica y luego pasaremos a ejemplos practicos en donde analizaremos codigo que esta implementado o que no cumple con las buenas practicas (SOLID) y hablaremos porque esta mal disenado y mal implementado y que problema puede traernos un codigo asi. Luego ajustaremos el codigo para que cumpla con los principios solid.
Al finalizar este post estoy seguro quie tendras un buen conocimiento de los principios solid y de las buenas practicas al momento de programar en C#
¿Qué son las buenas prácticas y el código limpio?
Cuando hablamos de buenas practicas hablamos de Best Practices en ingles, la traduccion como podemos ver no es literal pero podemos utilizar ambos conceptos para buscar informacion en internet sobre esto. Ya se por “Buenas Practias” en espanol o por “Best Practices” en ingles.
Buenas practicas
Estas buenas practicas nos ayudan a resolver desafios en ecenarios comunes cuando estamos desarrollando, las diferencias que existen entre buenas practicas y los estandares que nosotros creamos dentro de nuestros proyectos con nuestro equipo de trabajo o en las empresas donde estamos trabajando es que las buenas practicas ya son comprobadas, ya son tecnicas que han sido utilizadas en multiple proyectos y en multiples escenarios y estan comprobados que funcionan y que ayudan sobre todo a tener un mejor codigo una mejor arquitecura, a que el codigo sea mucho mas facil de comprender.
Las buenas practicas nos brindan guias que son faciles de aprender y de comprender y facilmente de aplicar dentro de el codigo o la implementacion que estamos haciendo. Las buenas practicas nos permite tener una estructura similar de los multiples proyectos que estamos trabajando, la idea es tener una serie de principios de estandares base para todos los proyectos y de esta manera sea facil movernos de un proyecto a otro y ademas que los proyectos se comuniquen entre si, si es necesario, en eso es en lo que nos ayudan las buenas practicas.
En internet existen muchas ideas o nuevos estandares que se estan construyendo en base de los cambios generacionales y de la inovacion que se a generado en el teme del desarrollo de software sin embargo el tema de los principios SOLID ya estan bien arraigado tras los pasos de los anos y han pasado de generacion a generacion ya que se conservan todos los lenguajes de programacion orientados a objetos asi que es parte de los principios basicos que debe saber cualquier programador.
Codigo Limpio o Clean Code
Cuando hablamos de codigo limpio o clean code hablamos de buenas practicas pero directamente asociadas a la sintaxis o al codigo que estamos escribiendo. Un codigo limpio es un codigo que es facil de entender, que es facil de analizar lo que permite por ejemplo que cuando llega un nuevo desarrollador al equipo que nunca a trabajado en este proyecto con este codigo, pueda comprenderlo facilmente y pueda empezar a trabajar en cambios en mejoras, en hacer extrender o expander ese codigo lo mas rapido posible, por eso entre mejor se construya el codigo, mejor va a ser la adptabilidad que van a tener los desarrolladores a el y obviamente esto a futuro va a significar en reduccion de gastos, reduccion de inversion en un mejoramiento continuo que va a tener ese codigo, podemos decir que el codigo es facil de mantener porque basicamente un codigo siempre esta constantemente evolucionando, creciendo y entre mas limpio este el codigo y entre mejor se sigan las buenas practicas mas facil va ser de lograr esto.
Imaginemos que tenemos un proyecto pequeno para salir del apuro, pero que tal si este producto se vuelve popular y necesite escalar, lo que va pasar es que empezara a crecer, a crecer y acrecer de una manera muy desordenada. Por eso es iomportante que tengamos el codigo limpio desde le principio siempre que salgamos a produccion, sin importar la magnitud del proyecto debemos de tratar de implementar esta tecnica de buenas practicas y de codigo limpio para que luego ese codigo pueda escalar de una manera mucho mas organizada.
Para lograr codigo limpio debemos de lograr algunas reglas que debemos de tomar encuenta:
-
**Mantener bajo acoplamiento dentro de los componentes dentro del sistema ** lo que aqui queremos es que no exista una gran dependencia de un componente y otro ya que muy probablemente en el futuro exista la necesidad de remplazar un componente por otro. Asi que el bajo acoplamiento es parte de las buenas practicas que debemos de tener en cuenta para poder lograr codigo limpio.
-
Utilizar sintaxis simple y actual: Mantener simple el codigo y poder actualizar la sintaxis que estamos utilizando en ultimas versiones de lenguajes de programacion con el que estemos trabajando. Los lenguajes de programacion se actualizan para incluir nuevos Features y de esa manera simplificar la manera en la que escribimos el codigo. Es importante tener en cuenta esto y estar actualizando constante mente nuestro codigo.
-
**Evitar incorporar muchas librerias de tercer: **Esto es algo muy importante generalmente cuando nosotros incorporamos estas librerias es causar una gran dependencia dentro de nuestro proyecto, que basicamente hace que luego a futuro perdamos el control de lo que estamos haciendo y dependamos de muchos terceros para poder hacer una actualizacion para poder hacer un cambio o para poder lograr una implementacion en especifico.
-
**Distibucion de responsabilildades: **Tambien es muy importante que cuando escribamos nuestro codigo hagamos una buena distribucion de las responsabilidades. Es importante que cada componente dento del sistema tenga una responsabilidad especifica y no crear componentes que hagan mil cosas en el codigo y que toda la aplicacion dependa en particular de este componente, si este componente se llega a cambiar o muchos desarrolladores estan trabajando a la vez en el es posible que la aplicacion se vea afectado por este componente.
-
**Creacion de componentes pequenos: **Tambien debemos de tratar de crear componentes muy pequenos, esto va de la mano con lo que hablamos de hacer una distribucion de responsabilidades y que cada componente haga algo en especifico, el echo de utilizar una sintaxis simple y actual basicamente si lo logramos estos dos podemos tener componentes mucho mas pequenos que no tengan tanto codigo que realmente digamos al final vaya a ser dificil de mantener en el futuro y que toda una aplicacion vaya a depender de ese codigo que se encuentra dentro de este componente.
Una recomendacion para empezar a trabajar con buenas practicas es el mitico libro: Clean Code a HandBook of Agile Software Craftsmanshiop de robert C. Martin. Este libro lleva mucho tiempo en el mercado y en el se establecieron las bases y los principios para poder trabajar de la mejor manera sobre todo con lenguajes de programacion orientados a objetos como JAVA y C#, basicamente gran parte de lo que miraremos en este post se encuentra en este libro y fueron tambien publicados en ese momento por primera vez en este libro y todo el mundo a venido aprendiendo con estas bases. Ahora si bien existen muchos mas recursos donde explican digamos de una manera mas moderna orientado a las librerias o a los frameworks que tenemos hoy en dia, este libro es la base de todo lo que podemos aprender sobre buenas practicas en estandares o en nuevos patrones que existan en el momento.
En que se relacionan estos conceptos que hemos aprendido de buenas practicas con los principios solid que es el titulo del post?
Bien los principios solid son lo que nos estaran ayudando a mantener codigo limpio y buenas practicas en el proyecto en el que nosotros estemos trabajando.
Basicamente los principos solid son las bases de las buenas practicas para los lenguajes de programacion orientados a objetos como JAVA, C#, o C++.
Es muy importante entender que estos principios cuando son aplicados desde el comienzo cuando estamos apenas creando las bases o aquitecturas de nuestro codigo, nos ayudaran a tener un codigo que se pueda mantener en el tiempo, que se pueda extender de una manera organizada, por eso los principios SOLID hacen parte de lo que normalmente se preguntan en las entrevistas de trabajo y de los conocimientos que debe de tener cualquier desarrollador, sobre todo un desarrollador SENIOR que este de encargado de crear un proyecto base para una aplicacion y liderar un equipo de trabajo de desarollo
¿Qué son los principios SOLID?
Los principios solid realmente el significado que tiene la palabra S.O.L.I.D viene de las letras que componen la palabra, es decir que cada letra de la palabra tiene un significado o representa un principio en particular que debemos de aprender y que es aplicable al proyecto que estemos trabajando:
- S. Single responsibility principle-Principio de responsabilidad única.
- O. Open/closed principle- Principio de abierto/cerrado.
- L. Liskov substitution principle-Principio de sustitución de Liskov.
- I. Interface segregation principle- Principio de segregación de la interfaz
- D. Dependency inversion principle- Principio de inversión de la dependencia.
- El primer ejemplo es el de** S. Single responsibility principle-Principio de responsabilidad única.** este principio esta relacionado a que debemos de relacionar una unica responsabilidad a cada uno de los componentes involucrados dentro del sistema
- **O Open/closed principle- Principio de abierto/cerrado. ** Basicamente nos dice que un codigo debe estar abierto a extenciones y cerrado para cambio.
- ** L. Liskov substitution principle-Principio de sustitución de Liskov. ** Basicamente nos habla de la relacion que tienen que tener los tipos y los subtipos dentro de un sistema
- I. Interface segregation principle- Principio de segregación de la interfaz Este principio nos ayuda a asignar una responsabilidad especifica a cada una de las interfaces que tiene el sistema como tipos abstractos que estemos utilizando.
- D. Dependency inversion principle- Principio de inversión de la dependencia. nos habla de los conceptos de inyecciones de dependencias y la importancia que tiene para evitar las inyecciones de dependencias entre un componente y otro, para evitar acoplamiento dentro de un sistema.
Con cada uno de estos principios estaremos realizando demos con cada uno de ellos, analizaremos la importancia que tienen, tendremos algunos escenarios dentro del post que nos van a mostrar que son las malas practicas, porque son malas practicas y luego aplicaremos el principio solid para soluncionar esta mala practica y mejorar esta aplicacion siguiendo el principio, veremos diferencias y aprenderemos la importancia de los principios solid.
Caracteristicas de SOLID
Algo que podemos mencionar de solid es que esta muy orientado al paradigma de orientacion a objetos (POO), se le aplica a los lenguajes que estan orienatados a este paradigma o a programar a objetos.
Si bien estos conceptos no fueron creados por el autor del libro antes mencioando Robet C. Martin el los recopilo y los lanzo en su libro, es por eso que se les atribuyen a el.
Estos principios solid aplican para el diseno como para la refactorizacion del codigo. Es decir tenemos que aplicar los principios cuando estemos creando nuestro proyecto base o estemos creando el proyecto desde cero pero tambien cuando nosotros tenemos que hacer un cambio dentro del codigo para resolver una implementacion que se hizo mal, tenemos que basarnos en los principios solid para poder que hacer ese cambio lo mejor posible y utilizar las mejores practicas en ese codigo que vamos a actualizar.
Se debe implementar desde el inicio del proyecto cuando se crean los componentes, realmente lo ideal de los principios solid es utilizarlo desde un inicio ya que se estan conformand o los componentes bases, los componentes que seguramente se estaran reutilizando dentro del sistema, que se van a utilizar multiple veces y que formaran la base de la arquitectura de nuestro sistema, asio que basicamente si no aplicamos estos principios solid desde el principio muy probablemente tendremos problemas al momento de que este proyecto empiece a crecer.
Solid Nos ayuda a:
Es importante mencionar que solid nos ayuda en varios aspectos cuando nostros estamos trabajando en un proyecto con lenguaje de programacion orientado a objetos, uno de ellos es lograr codigo escalable, hacer que el codigo sea facil de crecer, que cada vez que nos pidan nuevos componentes, nuevos modulos, nuevas funcionalidades, podamos crecer el codigo de una manera ordenada y tambien podamos reutilizar parte del codigo que ya esta echo.
Solid tambien nos ayuda a evitar deuda tecnica, este concepto se r efiere a los cambios que nos toque hacer en un futuro porque el codigo se hizo mal o se hizo un mal diseno o una mala implementacion, entonces cuando llegue ese momento, queramos o no vamos a tener que actualizar el codigo, a eso se le llama **deuda tecnica **.
Si logramos implementar los principios solid desde un inicio, muy probablemente podamos reducir mucho la dedua tecnica que podamos tener en un futuro.
Tambien solid no ayuda a plantear los fundamentos para lograr aplicar el desarrollo aplicado sobre pruebas, un software en donde podamos hacer pruebas unitarias y luego construir el codigo basado en las pruebas que estamos haciendo, es una metodologia altamente utilizada que genera muy buenos resultados que basicamente vamos a tener un set de pruebas completo antes de hacer la implementacion del codigo y eso nos va ayudar a validar constantemente el codigo que estamos construyendo.
Podemos tener tambien un estandar claro para cada uno de los proyectos que estamos trabajando si tenemos un portafolio de m,ultiples proyectos que de alguna manera se relacionan entre si. Con los principios solid vamos a poder tener unos estandares muy claros y unas buenas practicas muy claras en cada uno de los proyectos, esto nos facilitara que un desarrollador se pueda mover de un proyecto a otro
Ahora si vamos a empezar a analizar cada unos de los princios solid uno por uno.
Conociendo el principio de responsabilidad única
Este principio es conocido en ingles como** Single responsibility principle ** es importante saberlo porque originalmente de ahi viene y podermos profundizar o encontrar mas informacion.
Este principio nos inviar a que distribuyamos las responabilidades que existen en un sistema entre diferentes componentes cada componente dentro del sistema debe de tener una unica responsabilidad, basicamente por ejemplo en c# que manejamos todo a travez de objetos, utilizando clases y metodos,
- podemos decir que las clases deben de tener una unica responsabilidadad, deben de estar relacionadas a un unico modelo y deben de hacer una unica cosa.
- Si vamos a los metodos, este debe hacer una unica cosa y el nombre del metodo y de la clase debe ser lo suficiente descriptivo para indicar que es lo que hacen en particular, cual es esa responabilidad unica que tiene.
El princio de responsabilidad unica esta aplicado a todos los componentes del sistema dentro de un codigo es decir a los modulos a las clases a los metodos a las funcionalidades a las librerias que se esten utilizando, basicamente a TODO.
Incluso si vamos mas aya y estamos trabajando con arquitecturas modernas como la de Micro servicios pasa exactamente lo mismo, cada micro servicio deberia tener una unica responsabilidad. Si aplicamos ese mismo principio esa misma logica, basicamente nos estamos basando en los principios SOLID para poder construir esta arquitectura utilizando micro servicios.
Ejemplo:
” Como usuario luego de confirmar la compra espero ver un mensaje de confirmacion, tener la posibilidad de descargar la factura y un correo electronico de confirmacion ”
Imaginemos que comprarmos cualquier cosa en una tienda en linea, despues de pasar nuestro pago por el carrito, el checkout, esperamos tener mensaje de confirmacion de nuestra compra para saber que todo salio correcto, deberiamos tener tambien la posibilidad de descargar la factura o el comprobante del pago que hicimos y tambien recibir un correo electronico de confirmacion.
Analizando bien este requerimiento tenemos 3 responsabilidades aqui, 3 acciones que se estan ejecutando aqui simultaneamente aqui al mismo tiempo. El objetivo entonces es crear clases distribuidas en el codigo donde cada una de ellas tenga la responsabilidad correspondiente entre diferentes servicios y que cada uno tenga su responsabilidad en particular.
- Accion de confirmacion.
- Accion de descargar factura
- Correo electronico
Si bien todo se ejecuta dentro del mismo evento dentro de la misma accion debemos de distribuir en diferentes componentes cada una de estas responabilidades. Como estos ejemplos en las historias de usuario siempre tendremos multiples funcionalidades y tenemos que tratar de distribuir esas acciones esos efectos, estas funcionalidades que nos estan pidiendo entre diferentes componentes.
Repositorio GIT: Conociendo el principio de responsabilidad única
Conociendo el principio de abierto/cerrado
Este principio en ingles se conoce en ingles como “Open/Closed principle” y se lle otorga o se le reconocea Bertrand Meyer pero fue Robert Martin quien lo expuso al mundo en su libro anterior mencionado. Lo que podemos decir de este principio es que un componente dentro de un sistema debe ser abierto para extensiones y cerrado para cambios.
Esto es muy importante porque el principio nos dice que no debemos de mover codigo existente o que sabemos que ya esta funcionando o que ya esta comprobado y que este codigo deberia poder estar abierto y preparado para poderse extender.
Como podemos hacer esta extencion de componentes que debemos de estar haciendo constante mente cuando nos esten pidiendo nuevos modulos? nuevas funcionalidades.
Lo que estariamos haciendo es estarnos apoyando de los diferentes tipos abstractos que existen dentro de C#. Por ejemplo las clases abstractas y el caso de las interfaces, apoyandonos de estos tipos podemos hacer crecer el codigo de una manera mas ordenada y sin afectar el codigo que ya esta implementado.
Es importante resaltar que en c# no tenemos herencia multiple, asi que tenemos que apoyarnos en el uso de interfaces para poder ir creciendo nuestra aplicacion **No soporta multi-herencia: **
public class SubClass: MainClass
En este ejemplo podemos ver que tenemos una clase que hereda de la clase principal, esta clase principal puede tener multiples propiedades, todo esto lo hederaria la subclase. Aqui todo esta bien y se puede implementar porque podemos estar creando clases y subtipos todo el tiempo y de esta manera ir haciendo crecer el codigo en lugar de estar haciendo cambiar la clase princiapal.
**Soporta multiples implementaciones de interfaces: **
public DataAccess: IDataBase, IQuery, IDelete
Tambien en el caso de las interfaces podemos ver que tenemos un componente que se encarga del acceso a los datos y que implementa multiples interfaces que dan el acceso a las bases de datos, implementa otra interface “IQUERY” que seguro nos sirve para hacer consultas en la base de datos y por ultimo tenemos “IDELETE” que seguro que ayuda con una funcion para poder eliminar datos.
[Repositorio GIT: Principio de abierto/cerrado](https://github.com/raulxdev/.NET-SOLID/tree/main/2-OpenClose “Repositorio GIT: Principio de abierto/cerrado
Conociendo el principio de sustitución de Liskov - Liskov substitution principle
Este principio propuesto princial mente por Barbara Liskov y expuesto en el libro de Robert Martin nos habla de la relacion que existe entre el tipo y el subtipo dentro del sistema.
Basicamente debemos de tener la posibilidad de remplazar con los subtipos cualquier tipo que se encuentre dentro de los componententes de nuestro sistema o del mismo sistema como tal. Para lograr esto debemos de garantizar que todos los subtipos implementen todas las propiedades que tiene el tipo y que cada propiedad que tiene el tipo tenga sentido dentro del subtipo o pertenezca como tal al subtipo. De esa manera podemos hacer el remplazo en cualquier momento si es necesario en alguna implementacion.
Se meciona mucho la palabra **Tipo **y Subtipo, pero hasta donde entiendo, se puede tambien usar la palabra clase base y sub clase para entender este principio.
“Los objectos en una clase-base, deben poder reemplazarse por sub-classes de esta sin alterar la logica o el funcionamiento
Esta fue la manera en la que se expuso por primera vez como debe aplicarse el principio:
Sea φ(x) una propiedad comprobable acerca de los objetos de “x” de tipo “T”. Entonces φ(y) debe ser verdad para los objetos “y” del tipo S, donde S es un subtipo de T.
Repositorio GIT: Principio de sustitución de Liskov
Conociendo el principio de segregación de interfaces
Este principio conocido en ingles como* interface segregation principle* nos ayuda a dividir las interfaces que tenemos dentro de nuestro proyecto en interfaces muy pequenas que tengan una unica responsabilidad.
Basicamente con esto vamos a poder implementar multiples interfaces en el caso de que sea requerido y poder extender de una manera mucho mas ordenada el codigo que nosotros estamos haciendo.
Recordatorio: En C-SHARP / C# no podemos hederar de multiples clases pero **si podemos implementar multiples interfaces **
Asi que la estrategia de utilizar interfaces es la mejores que hay para poder hacer crecer el codigo de una manera mucho mejor ordenada y poder aplicar este principio de abierto y cerrado en el que vamos a extender y no cambiar el codigo existente.
Este principio nos permite desaclopar componentes y hacerlos crecer de una manera mucho mas ordenada y es importante entender que con este principio estamos tratando de que no exista tanta dependencia entre los diferentes componentes de la aplicacion y tambien poder hacer crecer los componentes de una manera mucho mas ordenada ya que basicamente lo que estamos haciendo es extender todo el tiempo todos los componentes y agregar nuevas funcionalidades en lugar de simplemente modificar el codigo que ya tenemos.
Basicamente podriamos concluir que este principio es el mismo principio de **Responsabilidad unica ** que hemos mencionado al principio del post pero esta vez aplicado a las interfaces
Ejemplo:
Tenemos una interfaz que implementa multiples actividades o responsabilidades de diferentes roles dentro de un proyecto, se debe aplicar el principio del ISP para distribuir de manera acorde las actividades e implementar solo lo necesario
Repositorio GIT: Conociendo el principio de segregación de interfaces
Conociendo el principio de inversión de la dependencia
En ingles conocido como Dependency Inversion Principle. Este principio busca desacoplar los componentes de la aplicacion de tal manera de que no exista esa dependencia directa entre un componente y otro y cuando yo haga un ajuste en alguno de estos componente, inmediatamente se quiebre todos los componentes que lo esten utilizando.
El principio de inversion de la dependencia nos ayuda a poder realizar cambios afectando el codigo lo menos posible que basicamente cambios siempre van a existir, pero mas que todo siempre vamos a tener que extender nuestro codigo agregar mas funcionalidades pero la idea es que este efecto/accion que debemos que realizar en el codigo sea lo menos traumatico posible.
Para poder aplicar este principio siempre debemos utilizar tipos abstractos, el mas utilizado y el mas importante para este principio en particular son las INTERFACES, estas nos permiten como tal crear un contrato para poder crear esa relacion entre un componente y otro y no esa dependencia directa entre los diferentes tipos de datos.
Es importante resaltar que existen 3 tipos de inyeccion de dependencias que podes utilizar para poder aplicar este principio, podemos hacerlo mediante el constructor de la clase, podemos hacerlo atravez de una propiedad de la clase y tambien podemos hacerlo por parametro inyectandolo por un metodo o una funcion en particular que tenga la clase. El mas utilizado es por constructor, es el que mas se utiliza y por estandar por muchos anos se esta implementando, es la manera mas logica de saber cuales son las dependencias que tiene un componente y de esa manera recibirlas en ese punto en particular que seria el momento en el que se ejecuta el constructor y ya luego la podemos utilizar en el resto de la clase, ya sea en propiedades en diferentes metodos que tenga la clase, podemos utilizar porque ya fueron recibidas dentri del constructor.
Este principio es muy importante para poder aplicar la metodologia de Test Driven Developmet (TDD) (Desarrollo guiado por pruebas).
Sino aplicamos este principio es practicamente imposible poder lograr y aplicar esta metodologia y crear pruebas unitarias de la manera correcta.
Demo - Escenario: Tenemos una API que devuelve una lista de estudiantes. Esta API utiliza un repositorio que devuelve una coleccion de datos y utiliza una bitacora (logbook) para guardar los eventos o llamadas a la API. Debemos aplicar el principio de inversion de la dependencia en esta API
Repositorio GIT: Conociendo el principio de inversión de la dependencia
Principios SOLID en C# y .NET