Regla
Las clases deben tener una única responsabilidad.
Las clases que gestionan múltiples preocupaciones violan
el Principio de Responsabilidad Única.
Lenguajes compatibles: JS, TS, PY, JAVA, C/C++,
C#, Swift/Objective C, Ruby. PHP, Kotlin,
Scala, Rust, Haskell, Groovy, Dart. Julia,
Elixir, Clojure, OCaml, DelphiIntroducción
Las clases que hacen demasiado se convierten en cuellos de botella. Una clase que gestiona la autenticación, los correos electrónicos y la validación requiere cambios cada vez que evoluciona alguna de estas preocupaciones, lo que conlleva el riesgo de romper funcionalidades no relacionadas. Las pruebas requieren simular (mocking) la clase completa incluso al probar un solo aspecto. El Principio de Responsabilidad Única establece que una clase debería tener solo una razón para cambiar.
Por qué es importante
Mantenibilidad del código: Las clases con múltiples responsabilidades cambian con más frecuencia porque la evolución de cualquier preocupación afecta a toda la clase.
Complejidad de las pruebas: Probar clases con múltiples responsabilidades requiere simular todas las dependencias, incluso para probar una sola funcionalidad.
Reutilización: No se puede extraer una responsabilidad sin arrastrar todas las dependencias. Los desarrolladores duplican código en lugar de desenredar clases con múltiples responsabilidades.
Coordinación del equipo: Múltiples desarrolladores trabajando en la misma clase para diferentes funcionalidades generan frecuentes conflictos de fusión (merge conflicts). Las clases de responsabilidad única permiten el desarrollo paralelo sin conflictos.
Ejemplos de código
❌ No conforme:
class UserManager {
async createUser(userData) {
const user = await db.users.insert(userData);
await this.sendWelcomeEmail(user.email);
await this.logEvent('user_created', user.id);
await cache.set(`user:${user.id}`, user);
return user;
}
async sendWelcomeEmail(email) {
const template = this.loadEmailTemplate('welcome');
await emailService.send(email, template);
}
async logEvent(event, userId) {
await analytics.track(event, { userId, timestamp: Date.now() });
}
}
Por qué está mal: Esta clase gestiona operaciones de base de datos, envío de correos electrónicos, registro y caché. Los cambios en las plantillas de correo electrónico, los formatos de registro o la estrategia de caché requieren modificar esta clase. Probar la creación de usuarios implica simular servicios de correo electrónico, análisis y caché, lo que hace que las pruebas sean lentas y frágiles.
✅ Conforme:
class UserRepository {
async create(userData) {
return await db.users.insert(userData);
}
}
class EmailNotificationService {
async sendWelcomeEmail(email) {
const template = await this.templateLoader.load('welcome');
return await this.emailSender.send(email, template);
}
}
class UserEventLogger {
async logCreation(userId) {
return await this.analytics.track('user_created', {
userId,
timestamp: Date.now()
});
}
}
class UserService {
constructor(repository, emailService, eventLogger, cache) {
this.repository = repository;
this.emailService = emailService;
this.eventLogger = eventLogger;
this.cache = cache;
}
async createUser(userData) {
const user = await this.repository.create(userData);
await Promise.all([
this.emailService.sendWelcomeEmail(user.email),
this.eventLogger.logCreation(user.id),
this.cache.set(`user:${user.id}`, user)
]);
return user;
}
}
¿Por qué esto importa? Cada clase tiene una responsabilidad clara: persistencia de datos, envío de correos electrónicos, registro de eventos u orquestación. Los cambios en las plantillas de correo electrónico solo afectan EmailNotificationService. La prueba de creación de usuarios puede utilizar stubs simples para las dependencias. Las clases pueden reutilizarse de forma independiente en diferentes funcionalidades.
Conclusión
El Principio de Responsabilidad Única no se trata de hacer las clases lo más pequeñas posible, sino de asegurar que cada clase tenga una única razón clara para cambiar. Cuando una clase empieza a manejar múltiples responsabilidades, refactorice extrayendo cada responsabilidad en su propia clase con una interfaz enfocada. Esto facilita la prueba, el mantenimiento y la evolución del código sin cambios en cascada en funcionalidades no relacionadas.
.avif)
