Curso SOLID
Single Responsibility Principle
Una clase, o módulo, solo debe de tener un único motivo de cambio, distintos cambios no pueden afectar a la misma clase. Por ejemplo: - Si modificamos el movimiento del Hero/Player, la clase que gestiona los ataques no debería de verse afectada. - Si cambiamos cómo gestionamos la vida del héroe nuestro gestor de movimientos tampoco debería de verse afectado.
Beneficios de Single Responsibility Principle
- Nos aporta legibilidad y nos facilitará el cambio.
- Reducimos los daños colaterales de realizar cambios.
- Reducimos los bugs recurrentes.
- Son clases más fáciles de entender y explicar cuando alguien se incorpora al proyecto.
Mejorar nombres de clases y funciones*
Mejorar estructura de carpetas
Open-Close Principle (OCP)
Abierto a extension pero cerrado a modificación Algunas técnicas para respetar Abstracciones, trabajemos con clases abstractas o interfaces y recibamos la instancia de esta clase en el constructor.
- Permitirá a la clase concreta evolucionar sin afectar a los consumidores de esta.
- También nos permitirá que podamos cambiar una clase concreta por otra de la misma interfaz.
Evitar estructuras estáticas como Switch/Case para preguntar por los tipos. Podemos utilizar estructuras dinámicas como el diccionario, para crear de forma dinámica una asociación entre el tipo y la clase, o acción. Beneficios de Open-Close Principle
- Evitaremos cambios en cascada.
- Nos permitirá ampliar el sistema sin tener que revisitar clases que ya funcionan.
- Nos evitará daños colaterales y posibles bugs.
- En algunos casos podremos hacer que nuestro sistema crezca sin tener que implementar nada nuevo. Solo cambiando ficheros de configuración o la información que nos llega desde el servidor.
Liskov sustitution
Una clase debe poder ser remplazada por una de las clases que heredan de esta sin comprometer el funcionamiento. Esto significa que la clase A debe respetar el comportamiento y las restricciones de la clase B, y no introducir cambios que puedan romper la lógica o las expectativas del programa. Es importante analizar bien si realmente se necesita una herencia o si es mejor aplicar composición. Algunas razones por las cuales dudar si realmente debe ser una herencia:
Si requieres método vacío en la clase padre (virtuales). Si tienes que preguntar por el tipo en alguna función.
¿Realmente necesitamos esta herencia?
Composicion vs herencia
| Aspecto | Composición | Herencia |
|---|---|---|
| Flexibilidad | Alta, las clases pueden cambiarse fácilmente. | Baja, cambios afectan toda la jerarquía. |
| Reutilización | Reutiliza objetos en lugar de jerarquías. | Reutiliza lógica compartida. |
| Relación | "Tiene un". | "Es un". |
| Complejidad | Menos acoplamiento, más modular. | Más acoplamiento en jerarquías profundas. |
| Extensibilidad | Fácil de extender agregando nuevos componentes. | Difícil si hay una jerarquía compleja. |
Interface Segregation Principle
Este principio establece que un consumidor no debe depender de métodos que no utiliza, por lo cual el crear interfaces para funciones muy especificas seria lo mas correcto. Por ejemplo IStorage, serian una interface que serviría para el control de guardar y cargar usuarios pero no todos los consumidores deben poder cargar y guardar, por lo que separarlo en ILoad y ISave hace que estas clases no tengan métodos que no deban poder utilizar y solo tengan métodos de acuerdo a su funcionalidad
Dependency Inversion Principle
Esto significa que los módulos de alto nivel no deben conocer los detalles de implementación de los módulos de bajo nivel, sino que deben comunicarse a través de interfaces o clases abstractas.
De esta forma, se reduce el acoplamiento entre los módulos y se facilita su reutilización y mantenibilidad.
Un ejemplo de aplicación de este principio es el uso de la inyección de dependencias, que consiste en pasar las dependencias de un módulo como parámetros a su constructor o a un método setter.
*Que otra clase le avise al consumidor cuando tiene que hacer algo, asi separar la logica en diferentes clases
Esto exactamente es el patrón Strategy: https://www.dofactory.com/net/strategy-design-pattern
Abstracciones vs concreciones
En C#, las abstraccionesy las concrecionesson conceptos fundamentales en la programación orientada a objetos (POO).
Abstracción: La abstracción es un proceso donde se destacan los aspectos más importantes de un objeto, ignorando los detalles irrelevantes. En C#, esto se logra a través de clases abstractas e interfaces.
- Clases abstractas: Son clases que no pueden ser instanciadas y suelen actuar como clases base para otras clases. Las clases abstractas pueden tener métodos abstractos (que no tienen implementación en la clase abstracta y deben ser implementados por cualquier clase que herede de la clase abstracta) y métodos no abstractos.
- Interfaces: Son una forma de abstracción que encapsula un grupo de propiedades y métodos relacionados que pueden ser implementados por cualquier clase o struct. Las interfaces no contienen ninguna implementación, solo las firmas de los métodos, propiedades o eventos.
Concreción: La concreción es el proceso de implementar algo en términos de algo más básico. En C#, esto se refiere a las clases concretas que implementan las abstracciones.
- Clases concretas: Son clases que implementan todas las funcionalidades definidas en sus clases base abstractas o interfaces. Estas son las clases que instanciamos en nuestro código.
Aquí tienes un ejemplo:
public interface IVehiculo // Abstracción
{
void Conducir();
}
public class Coche : IVehiculo // Concreción
{
public void Conducir()
{
Console.WriteLine("Conduciendo el coche...");
}
}
En este ejemplo, IVehiculo es una abstracción que define un comportamiento que todos los vehículos deben tener: Conducir . Coche es una concreción porque proporciona una implementación específica del método . Espero que esto te ayude a entender mejor estos conceptos.
- S - Principio de responsabilidad única (Single Responsibility Principle): Este principio establece que una clase debe tener una sola razón para cambiar. Es decir, cada clase debe tener una única responsabilidad en el sistema. Ejemplo: Una clase GestorEmpleados debería ser responsable únicamente de la gestión de empleados, sin mezclar responsabilidades como la generación de informes.
- O - Principio de abierto/cerrado (Open/Closed Principle): Este principio establece que una clase debe estar abierta para la extensión pero cerrada para la modificación. Esto significa que deberíamos poder agregar nuevas funcionalidades sin modificar el código existente. Ejemplo: Si tenemos una clase FiguraGeometrica , podríamos agregar nuevas formas geométricas como Triangulo o Círculo sin modificar la clase FiguraGeometrica .
- L - Principio de sustitución de Liskov (Liskov Substitution Principle): Este principio establece que los objetos de una clase base deben poder ser sustituidos por objetos de sus clases derivadas sin afectar el comportamiento del programa. Ejemplo: Si tenemos una clase Vehiculo y una clase Automovil que hereda de Vehiculo , deberíamos poder usar un objeto Automovil en cualquier lugar donde se espera un objeto Vehiculo .
- I - Principio de segregación de la interfaz (Interface Segregation Principle): Este principio establece que una clase no debe depender de interfaces que no utilice. Es mejor tener interfaces específicas para cada cliente en lugar de tener una única interfaz grande. Ejemplo: En lugar de tener una interfaz Servicio con métodos para enviar correos electrónicos, mensajes de texto y notificaciones push, podríamos tener interfaces separadas como ServicioCorreo , ServicioSMS y ServicioNotificacionPush .
- D - Principio de inversión de dependencias (Dependency Inversion Principle): Este principio establece que las clases de alto nivel no deben depender de las clases de bajo nivel, sino que ambas deben depender de abstracciones. Además, las abstracciones no deben depender de los detalles, sino que los detalles deben depender de las abstracciones. Ejemplo: En lugar de que una clase Pedido dependa directamente de una clase Proveedor , ambas podrían depender de una interfaz ServicioProveedor que define métodos para obtener información del proveedor.