Introducción a la clase
Los principios SOLID son la base para escribir código limpio, mantenible y escalable. Estos principios ayudan a estructurar mejor el software y facilitan la implementación de arquitecturas como Clean Architecture, Hexagonal y Vertical Slice.
En esta clase, aprenderás qué es SOLID, por qué es importante y cómo aplicarlo en C# con ejemplos prácticos.
¿Qué obtendrás de esta clase?
- Comprenderás los cinco principios SOLID y su importancia en el desarrollo de software.
- Aprenderás a aplicar cada principio con ejemplos en C#.
- Identificarás problemas comunes que SOLID ayuda a resolver.
- Prepararás el camino para escribir código más modular y desacoplado.
¿Qué es SOLID?
SOLID es un conjunto de cinco principios de diseño propuestos por Robert C. Martin (Uncle Bob) para mejorar la calidad del software. Cada letra representa un principio clave:
- S – Single Responsibility Principle (SRP)
- O – Open/Closed Principle (OCP)
- L – Liskov Substitution Principle (LSP)
- I – Interface Segregation Principle (ISP)
- D – Dependency Inversion Principle (DIP)
Estos principios buscan reducir la complejidad del código y mejorar su mantenibilidad.
Single Responsibility Principle (SRP)
Cada clase debe tener una única razón para cambiar.
Ejemplo sin SRP (Mala práctica)
public class PedidoService
{
public void ProcesarPedido(Pedido pedido)
{
Console.WriteLine("Validando pedido...");
Console.WriteLine("Calculando impuestos...");
Console.WriteLine("Guardando pedido en la base de datos...");
Console.WriteLine("Enviando notificación al cliente...");
}
}
Problema: Esta clase tiene múltiples responsabilidades (validación, persistencia, notificación).
Aplicando SRP (Mejor práctica)
public class PedidoValidator
{
public void Validar(Pedido pedido)
{
Console.WriteLine("Validando pedido...");
}
}
public class PedidoRepository
{
public void Guardar(Pedido pedido)
{
Console.WriteLine("Guardando pedido en la base de datos...");
}
}
public class NotificacionService
{
public void EnviarNotificacion(string mensaje)
{
Console.WriteLine($"Enviando notificación: {mensaje}");
}
}
public class PedidoService
{
private readonly PedidoValidator _validator;
private readonly PedidoRepository _repository;
private readonly NotificacionService _notificacionService;
public PedidoService(PedidoValidator validator, PedidoRepository repository, NotificacionService notificacionService)
{
_validator = validator;
_repository = repository;
_notificacionService = notificacionService;
}
public void ProcesarPedido(Pedido pedido)
{
_validator.Validar(pedido);
_repository.Guardar(pedido);
_notificacionService.EnviarNotificacion("Pedido procesado con éxito");
}
}
Ventajas de aplicar SRP:
- Código más modular y fácil de modificar.
- Menor impacto al hacer cambios.
- Mayor reutilización de clases.
Open/Closed Principle (OCP)
Las clases deben estar abiertas para extensión, pero cerradas para modificación.
Ejemplo sin OCP (Mala práctica)
public class CalculadoraImpuestos
{
public decimal CalcularImpuesto(decimal monto, string tipoProducto)
{
if (tipoProducto == "Alimento")
return monto * 0.05m;
else if (tipoProducto == "Electrónico")
return monto * 0.15m;
else
return monto * 0.10m;
}
}
Problema: Cada vez que se agrega un nuevo tipo de producto, hay que modificar la clase, rompiendo el código existente.
Aplicando OCP (Mejor práctica)
public interface IImpuesto
{
decimal Calcular(decimal monto);
}
public class ImpuestoAlimento : IImpuesto
{
public decimal Calcular(decimal monto) => monto * 0.05m;
}
public class ImpuestoElectronico : IImpuesto
{
public decimal Calcular(decimal monto) => monto * 0.15m;
}
public class CalculadoraImpuestos
{
public decimal CalcularImpuesto(decimal monto, IImpuesto impuesto)
{
return impuesto.Calcular(monto);
}
}
Ventajas de aplicar OCP:
- Permite agregar nuevas funcionalidades sin modificar código existente.
- Reduce el riesgo de errores al realizar cambios.
Liskov Substitution Principle (LSP)
Las clases derivadas deben poder sustituir a su clase base sin alterar el comportamiento esperado.
Ejemplo sin LSP (Mala práctica)
public class Rectangulo
{
public virtual int Ancho { get; set; }
public virtual int Alto { get; set; }
public int CalcularArea()
{
return Ancho * Alto;
}
}
public class Cuadrado : Rectangulo
{
public override int Ancho { set { base.Ancho = base.Alto = value; } }
public override int Alto { set { base.Ancho = base.Alto = value; } }
}
Problema: El cuadrado altera el comportamiento esperado del rectángulo.
Aplicando LSP (Mejor práctica)
public abstract class Forma
{
public abstract int CalcularArea();
}
public class Rectangulo : Forma
{
public int Ancho { get; set; }
public int Alto { get; set; }
public override int CalcularArea() => Ancho * Alto;
}
public class Cuadrado : Forma
{
public int Lado { get; set; }
public override int CalcularArea() => Lado * Lado;
}
Ventajas de aplicar LSP:
- Evita errores inesperados al usar herencia.
- Permite reutilizar código sin modificaciones innecesarias.
Interface Segregation Principle (ISP)
Las interfaces deben ser específicas y no obligar a implementar métodos innecesarios.
Ejemplo sin ISP (Mala práctica)
public interface IEmpleado
{
void CalcularSalario();
void GenerarReporte();
}
Problema: No todos los empleados generan reportes, pero están obligados a implementarlo.
Aplicando ISP (Mejor práctica)
public interface ISalario
{
void CalcularSalario();
}
public interface IReporte
{
void GenerarReporte();
}
public class Empleado : ISalario
{
public void CalcularSalario() { }
}
public class Gerente : ISalario, IReporte
{
public void CalcularSalario() { }
public void GenerarReporte() { }
}
Dependency Inversion Principle (DIP)
Las clases deben depender de abstracciones y no de implementaciones concretas.
public interface INotificador
{
void EnviarMensaje(string mensaje);
}
public class EmailNotificador : INotificador
{
public void EnviarMensaje(string mensaje) => Console.WriteLine($"Enviando email: {mensaje}");
}
public class PedidoService
{
private readonly INotificador _notificador;
public PedidoService(INotificador notificador)
{
_notificador = notificador;
}
public void ProcesarPedido()
{
_notificador.EnviarMensaje("Pedido completado");
}
}
Ventajas:
- Mayor flexibilidad al cambiar implementaciones.
- Código más desacoplado y fácil de probar.
Cuestionario de Autoevaluación
- ¿Qué significa cada letra en SOLID?
- ¿Cómo ayuda SRP a mejorar la mantenibilidad del código?
- ¿Cómo aplicarías OCP en un sistema real?
- ¿Cuál es el problema de la herencia en LSP?
- ¿Por qué es importante DIP en arquitecturas desacopladas?
Próximo paso
En la siguiente clase veremos las diferencias entre Clean Architecture, Hexagonal y Vertical Slice.