El principio de responsabilidad única, conocido también como Single Responsibility Principle dentro del conjunto de buenas prácticas SOLID, nos invita a diseñar cada clase con una única responsabilidad o razón para cambiar. Esto significa que cada clase debe estar enfocada en hacer una sola cosa, evitando mezclar funcionalidades que puedan generar confusión o dificultades a la hora de mantener el código.
Imaginemos que estamos desarrollando un sistema orientado a objetos para imprimir facturas. En este caso, la clase encargada de imprimir facturas debería ocuparse exclusivamente de esa tarea: generar el PDF o enviar la factura a la impresora. No debería encargarse de recopilar datos ni realizar cálculos, porque esas responsabilidades corresponden a otras clases, como podría ser una clase recolectora de información que accede a la fuente de datos y prepara la información necesaria para la factura.
Esta separación clara de responsabilidades mejora la cohesión interna de cada clase y reduce el acoplamiento entre ellas, lo que facilita el mantenimiento y la evolución del sistema. La razón de esto es que cada responsabilidad implica un posible motivo para modificar la clase. Si una clase tiene múltiples responsabilidades, aumentan las probabilidades de que un cambio en una funcionalidad afecte a otras, complicando la gestión del código.
Un ejemplo práctico que ilustra este principio es el diseño de un formulario que inicialmente valida y envía datos. Supongamos que tenemos una clase Formulario que recibe un usuario y una contraseña, y que tiene métodos para validar esos datos y enviarlos a un backend. En un principio, esta clase puede parecer suficiente, pero si las reglas de validación cambian o se vuelven más complejas, como exigir que la contraseña tenga ciertas características específicas, la clase comienza a mezclar responsabilidades: manejar datos, validar según reglas y enviar información.
Para aplicar el principio de responsabilidad única, podemos refactorizar este diseño separando las responsabilidades en distintas clases. Por ejemplo, una clase Formulario que solo almacena los datos, sin lógica adicional:
public class Formulario {
private String username;
private String password;
public Formulario(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
Luego, una clase ValidadorFormulario que se encargue exclusivamente de validar el formulario según un conjunto de normas:
import java.util.List;
public class ValidadorFormulario {
private Formulario formulario;
private List<Norma> normas;
public ValidadorFormulario(Formulario formulario, List<Norma> normas) {
this.formulario = formulario;
this.normas = normas;
}
public boolean validar() {
for (Norma norma : normas) {
if (!norma.cumple(formulario)) {
return false;
}
}
return true;
}
}
Y finalmente, una clase EnvioFormulario que se encargue solo de enviar el formulario validado:
public class EnvioFormulario {
public void enviar(Formulario formulario) {
// Lógica para enviar el formulario a un backend
}
}
De esta manera, cada clase tiene una única responsabilidad: almacenar datos, validar o enviar. Esto facilita que, si cambian las reglas de validación, solo modifiquemos la clase ValidadorFormulario, sin afectar el envío ni la estructura de datos.
Sin embargo, aplicar este principio no es trivial. Encontrar el equilibrio adecuado entre el número de clases y sus responsabilidades puede ser complicado. Si creamos muy pocas clases, corremos el riesgo de que tengan múltiples responsabilidades. Pero si creamos demasiadas, el sistema puede volverse difícil de entender y mantener, con muchas clases que solo se llaman entre sí y un alto acoplamiento.
Por eso, es fundamental analizar bien las necesidades de cada aplicación y diseñar un diagrama de clases que refleje un buen equilibrio, donde cada componente sea fácil de testear, reutilizar y modificar sin afectar al resto del sistema.
En definitiva, el principio de responsabilidad única nos ayuda a segregar la lógica de negocio en componentes claros y bien definidos, lo que contribuye a construir aplicaciones más robustas y mantenibles a largo plazo.