Los principios de diseño SOLID son una serie de normas que guían a los desarrolladores en la creación de software más mantenible, flexible y escalable. Estos principios son esenciales para implementar arquitecturas limpias. Aunque son cinco en total, en esta ocasión nos enfocaremos en tres: el principio de responsabilidad única (S), el principio de abierto y cerrado (O) y el principio de inversión de dependencias (D).
¿Qué es el principio de responsabilidad única?
El principio de responsabilidad única, representado por la letra "S" en SOLID, afirma que un módulo debe tener una sola razón para cambiar. Un módulo puede ser un archivo o una clase, y este principio nos ayuda a evitar combinar múltiples responsabilidades en un solo lugar.
Ejemplo práctico:
Imagina una aplicación en JavaScript que recibe solicitudes a través del método POST, realiza validaciones y guarda datos en una base de datos. Actualmente, este código tiene tres razones para cambiar: la comunicación (HTTP), la validación de datos y el acceso a la base de datos.
functionhandleRequest(req){validateRequest(req);saveToDatabase(req);}functionvalidateRequest(req){// Validaciones para correo y nombre}functionsaveToDatabase(req){// Inserta en la base de datos}
En lugar de combinar estas tareas, el principio de responsabilidad única sugiere que debemos separarlas en funciones o clases, cada una con su propia responsabilidad.
¿Cómo aplicar el principio de abierto y cerrado?
El principio de abierto y cerrado, simbolizado por la letra "O", sugiere que un módulo debe estar abierto para extenderse pero cerrado para modificarse. Esto implica que, en lugar de alterar el código existente al añadir nuevas funcionalidades, las nuevas características deben integrarse sin modificar la estructura ya existente.
Ejemplo práctico:
En un sistema con estructuras condicionales encadenadas para diferentes ciudades:
Es preferible estructurarlo para permitir extensibilidad:
classCityProcessor{process(){}}classCaliProcessorextendsCityProcessor{process(){// Proceso para Cali}}classLimaProcessorextendsCityProcessor{process(){// Proceso para Lima}}
Al crear clases específicas, podemos añadir nuevas ciudades aplicando herencia, evitando modificar el código existente y mejorando la mantenibilidad.
¿Qué es el principio de inversión de dependencias?
El principio de inversión de dependencias, correspondiente a la letra "D", sostiene que los módulos de alto nivel no deben depender de los módulos de bajo nivel. Más bien, ambos deben depender de abstracciones, ya que los detalles deben depender de las abstracciones y no al revés.
Explicación detallada:
Presentación y Acceso a Datos: Son módulos de bajo nivel. Tienden a cambiar más frecuentemente.
Dominio (Lógica de Negocio): Es un módulo de alto nivel. Debe permanecer independiente de detalles de implementación específicos, como cambios en la base de datos o en la interfaz de usuario.
Uso de la inyección de dependencias:
La inyección de dependencias es crucial para implementar este principio, ya que permite que los módulos de alto nivel no dependan directamente de las implementaciones de bajo nivel, sino de interfaces o abstracciones.
Mediante estos principios, los desarrolladores pueden lograr sistemas más robustos, con un crecimiento controlado y sostenibles en el tiempo, comenzando por una estructura robusta y lógica desde el inicio de los proyectos. ¡Continúa explorando estos conceptos para perfeccionar aún más tus habilidades de programación!
S : Principio de Única Responsabilidad.
Un módulo debería tener una sola razón para cambiar.
O : Principio de Abierto Cerrado.
Un módulo debería estar abierto a extensiones y cerrado a modificaciones.
L : Principio de Sustitución de Liskov.
Las clases derivadas deben poder sustituirse por sus clases base.
I : Principio de Segregación de la Interfaz
Haz interfaces que sean específicas para un tipo de cliente
D : Principio de Inversión de Dependencias
Los módulos de alto nivel no deberían depender de los módulos de bajo nivel.
Que chido resumen!
por si a alguien le sirve, el Liskov Substitution Principle (junto con el OCP) también podemos aplicarlo a funciones o métodos. Solo asegurarnos de que en lugar de definir una clase, se use una interfaz que nos servirá para definir el contrato que deben cumplir los miembros que se pasen por parámetro
pseudo ejemplito en TS
type Notifier=....;constmailgunEmailNotifier:Notifier=(subject)=>({notify:(msg, recipient)=>{....}});constregisterUser=(user:User,userRepository:UserRepository,notifier:Notifier)=>{const newUser = userRepository.register(user); notifier.notify("Welcome!", newUser.email)}registerUser( data,typeOrmUserRepository(),mailgunEmailNotifier("Your new account"),);
Principios SOLID
Libro: Desarrollo ágil de software: principios, patrones y prácticas de Robert Martin
Single Responsibility Principle
Principio de responsabilidad única
Una clase sólo debe tener una razón para cambiar.
El principal objetivo de este principio es reducir la complejidad.
si una clase hace demasiadas cosas, tienes que cambiarla cada vez que una de esas cosas cambia. Al hacerlo, te arriesgas a descomponer otras partes de la clase que no pretendías cambiar.
Open/Closed Principle
Principio de abierto/cerrado
Las clases deben estar abiertas a la extensión pero cerradas a la modificación.
La idea fundamental de este principio es evitar que el código existente se descomponga cuando implementas nuevas funciones.
Este principio no está pensado para aplicarse a todos los cambios de una clase. Si sabes que hay un error en la clase, debes arreglarlo; no crees una subclase para ello. Una clase hija no debe ser responsable de los problemas de la clase padre.
Liskov Substitution Principle
Principio de sustitución de Liskov
Al extender una clase, recuerda que debes tener la capacidad de pasar objetos de las subclases en lugar de objetos de la clase padre, sin descomponer el código cliente.
Esto significa que la subclase debe permanecer compatible con el comportamiento de la superclase. Al sobrescribir un método, extiende el comportamiento base en lugar de sustituirlo con algo totalmente distinto.
Los tipos de parámetros en el método de una subclase deben coincidir o ser más abstractos que los tipos de parámetros del método de la superclase.
El tipo de retorno en el método de una subclase debe coincidir o ser un subtipo del tipo de retorno del método de la superclase.
Un método de una subclase no debe arrojar tipos de excepciones que no se espere que arroje el método base.
Una subclase no debe fortalecer las condiciones previas.
No debe añadir restricciones a los parámetros al sobrescribir métodos de superclases.
Una subclase no debe debilitar las condiciones posteriores.
Los invariantes de una superclase deben preservarse.
Una subclase no debe cambiar los valores de campos privados de la superclase.
Interface Segregation Principle
Principio de segregación de la interfaz
No se debe forzar a los clientes a depender de métodos que no utilizan.
Dependency Inversion Principle
Principio de inversión de la dependencia
Las clases de alto nivel no deben depender de clases de bajo nivel.
Ambas deben depender de abstracciones.
Las abstracciones no deben depender de detalles.
Los detalles deben depender de abstracciones.
Las clases de bajo nivel
Implementan operaciones básicas, como trabajar con un disco, transferir datos por una red, conectar con una base de datos, etc.
Las clases de alto nivel
Contienen la lógica de negocio compleja que ordena a las clases de bajo nivel que hagan algo.
Paso a paso
Para empezar, debes describir interfaces para operaciones de bajo nivel en las que se basarán las clases de alto nivel, preferiblemente en términos de negocio.
Por ejemplo, la lógica de negocio debe invocar un método abrirInforme(archivo) en lugar de una serie de métodos abrirArchivo(x) , leerBytes(n) , cerrarArchivo(x) . Estas interfaces cuentan como de alto nivel.
Ahora puedes hacer las clases de alto nivel dependientes de esas interfaces, en lugar de clases concretas de bajo nivel. Esta dependencia será mucho más débil que la original.
Una vez que las clases de bajo nivel implementan esas interfaces, se vuelven dependientes del nivel de la lógica de negocio, invirtiendo la dirección de la dependencia original.
Gran aporte bro, muchas gracias <3
Excelente aporte, gracias
Para el caso del principio "Abierto/Cerrado", la solución planteada por el profesor Manuel, está basada en el patrón de diseño "Abstract Factory", el cual pueden consultar en este enlace: https://refactoring.guru/es/design-patterns/abstract-factory
Un ejemplo de el codigo de ciudades y como evitar su modificación al dar mas datos de entrada, en este caso "ciudades", en el cual se verán como clases, se agregaran mas, pero no se tocara la lógica para ver si existe esa ciudad en tu "sistema"
# Define una interfaz para las estrategias de procesamiento de ciudades.classCiudadProcessor: def procesar_ciudad(self): pass
# Implementa una estrategia para procesar Cali.classProcesoCali(CiudadProcessor): def procesar_ciudad(self):print("Ejecutando proceso para Cali")# Implementa una estrategia para procesar BuenosAires.classProcesoBuenosAires(CiudadProcessor): def procesar_ciudad(self):print("Ejecutando proceso para Buenos Aires")# Implementa una estrategia para procesar México.classProcesoMexico(CiudadProcessor): def procesar_ciudad(self):print("Ejecutando proceso para México")# Definir diccionario que asocia ciudades con sus estrategias de procesamiento.estrategias_por_ciudad={"cali":ProcesoCali(),"buenos aires":ProcesoBuenosAires(),"Mexico":ProcesoMexico(),}# Supongamos que 'ciudad' contiene el nombre de la ciudad a procesar.ciudad="cali"
#estructura de codigo que no se va a modificar
if ciudad inestrategias_por_ciudad: estrategia = estrategias_por_ciudad[ciudad] estrategia.procesar_ciudad()else:print("Ciudad desconocida")
Los principios SOLID, son aplicables principalmente a POO, ya que a través de estos principios se habla acerca de las clases, herencia, polimorfismo, sin embargo estos principios tambien pueden ser aplicables a Paradigmas como la Programacion Funcional, entre otros. Sin embargo es importante destacar que estos principios pueden ser usados en distintos paradigmas, ya que con esto lo que se busca es optimizar el codigo escrito de manera que sea mas comprensible, mantenible y comprobable a través del tiempo y que a su vez permita la integración de varios desarrolladores
Hola Plazi, se me dificulta un poquito ver las clases y el código en pantalla modo reels, hay alguna opcion que deba configurar para pasar a modo normal?
Entiendo que eventualmente estará disponible una versión en horizontal de los videos.
He aplicado estos principios antes. Aún así notar que no siempre se deben seguir al pie de la letra. Existen varias corrientes contrarias al respecto como lo son CUPID y STUPID.
En el principio "Abierto/Cerrado"¿a nivel de código cómo queda la lógica condicional y la ejecución de cada implementación concreta?
Hola Juan! En ese caso habría un elemento adicional que decidiría que clase utilizar. En esos casos se suele usar inyección de dependencias, donde se recibiría por parámetro la clase a ejecutar.
Los principios SOLID son super importantes aplicarlos en el desarrollo de Software ayudan a tener una mejor estructura en el código, los utilizo mucho.
El ejemplo hay que resolverlo, como quedaria, se muestra el problema pero no la solucion aplicada.
Lo que pasa es que la ejemplificacion de como solucionarlo viene mas adelante pero lo importante para este punto es entender conceptos como clases abstracatas, interfaces sobrecarga de metodos el acoplamiento, la inversion de dependencias asi como otros cuantos, por que finalmente el usar arquitectura limpia de acuerdo como se lleve puede desencadenar desde separar la logica en varios archivos hasta hacerlo en varios subproyectos o microservicios y desacoplar las dependencias, es algo que requiere un buen nivel de detalle y se va a ver mas adelante, tambien tener en cuenta que el resumen contiene unas soluciones simples a los problemas del video.
Precisamente la idea de que cada capa en las arquitecturas tiene una responsabilidad definida, es lo que da vida al Principio de Única Responsabilidad.
Principios de diseño
Para sacarle el mayor provecho a las arquitecturas limpias y entenderlas es importante estudiar algunos principios de diseño, en particular S.O.L.I.D.
S - Principio de Única Responsabilidad
Un módulo debería tener una sola razón para cambiar, por ejemplo un archivo o una clase.
O - Principio de Abierto Cerrado
Un módulo debería estar abierto a extensiones y cerrado a modificaciones.
D - Principiop de Inversión de Dependencias
Los módulos de alto nivel no debería depender de los módulos de bajo nivel.
Presentación (Bajo nivel).
Dominio (Alto nivel).
Acceso a datos (Bajo nivel).
Se puede invertir la dirección de la dependencia:
Acceso a datos (Bajo nivel) → Dominio (Alto nivel)
yo a este man como que lo conozco jaja
Me pregunto cada cuanto sucede de que desees cambiar la base de datos de tu aplicación? Para ser un poco más concreto, ¿qué otros beneficios aporta la inversión de dependencias cuando se está hablando del acceso a los datos?
Yo considero que cuando una aplicación inicia, tiene poco trafico y así, no hay problema,pero si empieza a crecer, puede que se empiece a poner lenta, o no tuvimos en cuenta que debemos hacer reportes o cosas así, y puede que desde por costos, rendimiento, etc, merezca la pena cambiar, por ejemplo, de una BD relacional a una NoSql.
Brindan una mayor calidad en el código. Lástima que muy pocos desarrolladores implementen estos principios SOLID
Los 5 Principios SOLID
Principio de Responsabilidad Única (SRP):
Una clase debe tener una única razón para cambiar.
Ejemplo: Una clase que se encarga de calcular un impuesto y de enviar un correo electrónico está violando este principio. Deberían ser dos clases distintas.
Principio Abierto/Cerrado (OCP):
Las entidades (clases, módulos, funciones, etc.) deben estar abiertas para extensión, pero cerradas para modificación.
Ejemplo: En lugar de modificar una clase existente para agregar nueva funcionalidad, se debe crear una subclase que extienda la funcionalidad original.
Principio de Sustitución de Liskov (LSP):
Los objetos de una superclase deben poder ser reemplazados por objetos de sus subclases sin que se altere el correcto funcionamiento del programa.
Ejemplo: Si una clase Cuadrado hereda de una clase Rectángulo, pero no cumple con todas las propiedades de un rectángulo (por ejemplo, no se puede cambiar la altura sin cambiar el ancho), entonces se está violando este principio.
Principio de Segregación de Interfaz (ISP):
Muchas interfaces específicas son mejores que una única interfaz de propósito general.
Ejemplo: En lugar de tener una interfaz que defina todas las operaciones posibles de un objeto, es mejor crear interfaces más específicas para cada grupo de operaciones relacionadas.
Principio de Inversión de Dependencias (DIP):
Los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones.
Ejemplo: Una clase de negocio no debe depender directamente de una base de datos, sino de un repositorio abstracto que encapsula la lógica de acceso a datos.
Hola, en el primer ejemplo, no aplicaste el principio de única responsabilidad
func(repo *PostgresRepository)InsertProduct(ctx context.Context, product *models.Products) error { _,err:= repo.db.ExecContext(ctx,"INSERT INTO products (id , title , description , image_url , price , user_id) VALUES ($1, $2 ,$3 ,$4 ,$5 ,$6)", product.Id, product.Title, product.Description, product.ImageUrl, product.Price, product.UserId)return err
}```Este es un ejemplo que hice de un método que se encarga de insertar productos .Validación, consultas y conexiones se manejan por separado . Si estoy equivocado corríjanme ! 🫶
Principios Solid.
(Para efectos prácticos)
S: Principio de Única responsabilidad: un módulo debería tener una sola razón para que cambie.
O: Principio abierto Cerrado: un módulo debería estar abierto a extensiones y cerrado a modificaciones.
D: Principio de inversión de dependencias: podemos invertir la dirección de las dependencias.
Para aprovechar al máximo las arquitecturas limpias, es bueno conocer algunos principios de diseño, en particular los principios SOLID:
Para efectos prácticos, solo nos enfocaremos en S.O.D.
Principio de Única Responsabilidad (S)
Un módulo debería tener una sola razón para cambiar. Un módulo puede significar muchas cosas, pero podemos verlo como un archivo o una clase en la que se trabajará.
Como puedes ver en el código anterior, se está recibiendo una petición http, se valida la información, luego se hace una inserción a la base de datos, luego se manejan los errores para que al final inserte el usuario con éxito. Esto rompe el principio de una sola responsabilidad.
En caso de que queramos modificar este código, tenemos tres razones para cambiar:
La razón en la manera en que nos comunicamos con el cliente cambió (/users), ya que usamos algún método diferente o usamos otros parámetros.
Luego podríamos querer hacer validaciones distintas, como verificar que el email que se ha enviado realmente sea un email.
O que también la base de datos haya cambiado su estructura, como añadir algún nuevo campo o un cambio en la tabla.
Entonces al ver esto, podemos empezar a pensar en que podríamos separar todo este código según su responsabilidad. Una parte que se enfoque en la http, otro en las validaciones, otro al acceso de datos u otro que se encargue de los errores.
Principio de Abierto Cerrado (O)
Un módulo (clase, archivo) debería estar abierto a extensiones y cerrado a modificaciones.
Esto no es bueno para nada, ¿qué ocurre si un día debemos agregar 10 ciudades más o añadir más código dentro de las condiciones? Esto dificulta la navegación y la mantenebilidad de nuestro código. Nosotros deberíamos buscar una forma en que ese código no se modifique, sino que añada nuevo código pero lo que funcione se mantenga quieto, con el objetivo de que podamos añadir ciudades de una manera más segura sin comprometer el código que ya está.
Algo para que cumpla el principio de abierto cerrado, puede ser lo siguiente:
![[Captura de pantalla 2023-09-18 a la(s) 22.34.38.png|400]]
Esta estructura nos permite tener mucho más encapsulado nuestro código, así que a la hora de tener que modificar alguna ciudad, vamos a su clase o si debemos añadir otra, creamos una nueva. Extiendo y lo que funciona, no lo modifico. Es decir, estoy abierto a extender, pero cerrado a modificar el código que funciona.
Principio de inversión de dependencias (D)
Los módulos de alto nivel no deberían depender de los módulos de bajo nivel.
Si vemos la arquitectura de tres capas:
![[Captura de pantalla 2023-09-18 a la(s) 21.22.10.png|300]]![[Captura de pantalla 2023-09-18 a la(s) 22.37.56.png|300]]
La presentación y acceso a datos son módulos de bajo nivel, ¿por qué? porque son detalles que pueden cambiar en el tiempo y están más cercanos a una implementación específica. El dominio es de alto nivel ya que es mucho más perdurable en el tiempo y general, que no debiese verse afectado si en algún momento cambiamos nuestra bd a SQL Server, por ejemplo.
Se puede invertir la dirección de dependencia:
![[Captura de pantalla 2023-09-18 a la(s) 22.40.55.png|300]]
El dominio ya no depende del acceso a datos, sino que el acceso a datos depende del dominio, porque justamente los módulos de alto nivel no debe depender de detalles que cambien con el tiempo.
Nota
Alto nivel -> Dominio
Bajo nivel -> Todos los detalles que cambian con el tiempo
Principio Dependency Inversion. Los módulos de alto nivel no deberían depender de los módulos de bajo nivel. Esto se debe a que los módulos de alto nivel deberían ser más generales, y no entrar en detalles como los de más bajo nivel, quienes cambian más seguido.