No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Principios de diseño

5/24
Recursos

Aportes 25

Preguntas 3

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Principios SOLID

  • 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.

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

  1. 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.

  2. 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.

  3. 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.

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.
class CiudadProcessor:
    def procesar_ciudad(self):
        pass

# Implementa una estrategia para procesar Cali.
class ProcesoCali(CiudadProcessor):
    def procesar_ciudad(self):
        print("Ejecutando proceso para Cali")

# Implementa una estrategia para procesar Buenos Aires.
class ProcesoBuenosAires(CiudadProcessor):
    def procesar_ciudad(self):
        print("Ejecutando proceso para Buenos Aires")

# Implementa una estrategia para procesar México.
class ProcesoMexico(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 in estrategias_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

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.

### Los 5 Principios SOLID 1. **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. 2. **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. 3. **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. 4. **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. 5. **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
```js 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 ! 🫶
Esta guay saber que lo que estaba haciendo de manera inconciente tiene nombre XD

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á.

Veamos el siguiente código:

app.post("/users", (req, res) => {
  const user = req.body;

  if (!user || !user.name || !user.email) {
    res.status(400).send("Bad Request");
  } else {
    pool.query(
      "INSERT INTO useers (name, email) VALUES (?, ?)",
      [user.name, user.email],
      (error, results) => {
        if (error) {
          console.log(error);
          res.status(500).send("Internal error");
        } else {
          res.status(201).send("User created");
        }
      }
    );
  }
});

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:

  1. 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.
  2. Luego podríamos querer hacer validaciones distintas, como verificar que el email que se ha enviado realmente sea un email.
  3. 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.

Veamos el siguiente código:

if (ciudad == "Cali") {
  ejecutarProcesoCali();
} else if (ciudad == "Lima") {
  ejecutarProcesoLima();
} else if (ciudad == "Buenos Aires") {
  ejecutarProcesoBuenosAires();
}

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.

Principio Open Closed. Un módulo debería estar abierto a extensiones y cerrado a modificaciones. Por ejemplo, en lugar de hacer una cadena de condicionales if cada uno ejecutando un método, podríamos hacer una clase abstracta o interfaz y luego el método a ejecutar dependerá de su implementación. De esta forma, liberamos al módulo de la decisión y la dejamos a una capa superior, donde se indique qué clases implementarán a cada interfaz.

Principio Single Responsability. Un módulo (archivo o clase) debería tener una sola razón para cambiar. Es decir, cada módulo debe encargarse de una única tarea, de modo de simplificar futuros cambios/correcciones/tests. Por ejemplo, un controlador de una API no debería conectarse a la base de datos directamente.

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)

El Principio de Inversión de Dependencias (último de los cinco principios SOLID).

Los módulos de alto nivel no deben depender de los módulos de bajo nivel. Ambos deben depender de abstracciones.
Las abstracciones no deben depender de los detalles. Los detalles deben depender de las abstracciones.
En la práctica, esto significa que las implementaciones de bajo nivel (por ejemplo, la lógica de acceso a datos BD) deben ser intercambiables sin que eso afecte a los módulos de alto nivel (por ejemplo, la lógica de negocio).

La inversión de control es una forma de lograr esto. En lugar de tener un módulo de alto nivel que crea y controla los objetos de bajo nivel, se utiliza un “contenedor IoC” que crea y gestiona estos objetos. El contenedor IoC puede inyectar estas dependencias en los módulos de alto nivel cuando los necesiten, un patrón conocido como Inyección de Dependencias.

Por lo tanto, el Principio de Inversión de Dependencias y la Inversión de Control son mecanismos que nos ayudan a desacoplar el código y a hacerlo más modular, flexible y fácil de probar.

LLevo bastante tiempo involucrado en proyectos de desarrollo donde usaba inyección de dependencias pero sin entender su razón de ser propiamente; que buena explicación del princio de inversión de control y por consiguiente como aplicar correctamente la inyección de dependencias : )

Si, los he aplicado unos mas que otros, los he aplicado en proyectos, dependiendo de lo que necesitemos

Principios de diseño

  • SRP (Single responsibility principle): Una sola razón para cambiar

  • OCP (open closed Principle): Abierto a extensiones y cerrado a modificaciones

  • DIP (Dependency inversion Principle): Modulos de alto nivel(ej: logica de negocio o el dominio) no dependen de detalles que cambia con el tiempo (modulos de bajo nivel).

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.

https://dannorth.net/2022/02/10/cupid-for-joyful-coding/

https://jnjsite.com/los-principios-del-diseno-software-kiss-dry-solid-stupid/

Dejo los términos en inglés por si llevan curiosidad:

Single Responsibility Principle (SRP)
Open-Closed Principle (OCP)
Liskov Substitution Principle (LSP)
Interface Segregation Principle (ISP)
Dependency Inversion Principle (DIP)

Me parece excelente para utilizar en el desarrollo de una aplicacion