El Principio de Responsabilidad Única es la idea de que una clase debe tener una sola razón para cambiar.
Empezamos nuestra inmersión en SOLID con la S: el Principio de Responsabilidad Única (Single Responsibility Principle o SRP).
Este principio está estrechamente relacionado con el concepto de cohesión que vimos anteriormente.
La definición clásica, formulada por Robert C. Martin, dice así:
“A class should have one, and only one, reason to change.” (Una clase debe tener una, y solo una, razón para cambiar.)
A menudo, la gente confunde esto con “una clase debe hacer solo una cosa”. Aunque ambas ideas están relacionadas, no son exactamente lo mismo. El SRP habla de quién provoca los cambios en el software y de agrupar las cosas que cambian por las mismas razones.
¿Qué significa “razón para cambiar”?
Supongamos que tenemos una clase llamada InformeEmpleado. Esta clase hace dos cosas:
- Calcula el sueldo del empleado.
- Imprime el informe en formato PDF.
¿Cuántas razones tiene esta clase para cambiar?
- Si el departamento de Finanzas cambia las reglas de cálculo de nóminas… la clase cambia.
- Si el departamento de IT decide cambiar el formato del reporte o la librería de PDF… la clase cambia.
Tenemos dos actores distintos (Finanzas e IT) que pueden solicitar cambios en la misma clase. Estamos violando el SRP. Si modificamos la clase para satisfacer a Finanzas, corremos el riesgo de romper la generación del PDF sin querer.
El SRP nos dice que debemos separar el código que cambia por diferentes razones o responde a diferentes actores del negocio.
Ejemplo de una violación del SRP
Vamos a ver un ejemplo muy común: la típica clase Usuario que empieza siendo pequeña y acaba convirtiéndose en un “objeto dios”.
// ❌ MAL DISEÑO: Violación de SRP
public class Usuario
{
public string Nombre { get; set; }
public string Email { get; set; }
// Razón de cambio 1: Lógica de negocio (Validación)
public bool ValidarEmail()
{
return Email.Contains("@");
}
// Razón de cambio 2: Persistencia (Base de Datos)
public void GuardarEnBaseDeDatos()
{
// Código SQL para insertar usuario...
Console.WriteLine($"Guardando {Nombre} en SQL...");
}
// Razón de cambio 3: Comunicación (Infraestructura)
public void EnviarEmailBienvenida()
{
// Configuración SMTP y envío...
Console.WriteLine($"Enviando email a {Email}...");
}
}
Esta clase sabe demasiado. Sabe cómo son las reglas de negocio, cómo hablar con la base de datos y cómo enviar correos. Tiene una cohesión baja.
Si cambiamos la base de datos de SQL Server a MongoDB, tenemos que tocar la clase Usuario. ¿Tiene sentido? No. El usuario es un concepto de negocio, no debería importarle dónde se guarda.
Refactorización aplicando SRP
Para cumplir con el principio de responsabilidad única, debemos separar estas responsabilidades en clases distintas.
La clase Usuario debería ser solo eso: una representación de los datos del usuario (un POCO de dominio, por ejemplo).
public class Usuario
{
public string Nombre { get; set; }
public string Email { get; set; }
}
Creamos una clase encargada exclusivamente de guardar y recuperar datos.
public class UsuarioRepository
{
public void Guardar(Usuario usuario)
{
Console.WriteLine($"Guardando {usuario.Nombre} en SQL...");
}
}
Creamos una clase encargada exclusivamente de las comunicaciones.
public class EmailService
{
public void EnviarBienvenida(Usuario usuario)
{
Console.WriteLine($"Enviando email a {usuario.Email}...");
}
}
Finalmente, podemos tener un servicio que coordine todo. Fíjate en que ahora cada pieza se ocupa de lo suyo.
// ✅ BUEN DISEÑO: Alta Cohesión
public class ServicioDeRegistro
{
private UsuarioRepository _repo;
private EmailService _emailService;
// Inyección de dependencias (tema para el principio DIP)
public ServicioDeRegistro(UsuarioRepository repo, EmailService emailService)
{
_repo = repo;
_emailService = emailService;
}
public void RegistrarUsuario(Usuario usuario)
{
if (!usuario.Email.Contains("@")) // Validación simple
throw new Exception("Email inválido");
_repo.Guardar(usuario);
_emailService.EnviarBienvenida(usuario);
}
}
Beneficios de aplicar SRP
Al principio parece que hemos trabajado más: hemos pasado de una clase a cuatro. Aun así, la separación merece la pena.
- Mantenibilidad: Si hay un error en el envío de emails, sabemos exactamente dónde mirar (
EmailService). No tenemos que bucear en una clase de 2000 líneas. - Reusabilidad: Podemos usar el
EmailServicepara enviar correos de facturas o alertas, no está atado a la claseUsuario. - Testabilidad: Probar la clase
Usuarioes trivial. Probar elUsuarioRepositoryse puede hacer por separado sin enviar emails reales accidentalmente. - Colaboración: Un desarrollador puede estar mejorando la base de datos mientras otro mejora las plantillas de email, y no tocarán el mismo archivo.
¿Dónde está el límite?
Aquí viene la trampa. Si llevamos el SRP al extremo, podríamos acabar con clases que tienen un solo método (GuardarUsuario, LeerUsuario, BorrarUsuario…). Esto se conoce como fragmentación excesiva y también es malo.
El criterio está en agrupar las funciones que cambian juntas. Si cada vez que cambias la validación también sueles cambiar el formato de guardado, quizá deberían estar juntas (aunque es raro). Usa el sentido común.
Con las clases pequeñas y enfocadas, el siguiente problema es cambiar su comportamiento sin modificar su código.
Siguiente paso en el curso: