¿Qué es el Principio de Responsabilidad Única?
"Una clase debe tener una sola razón para cambiar"
-Robert C. Martin.
De la forma sencilla se traduce como: cada clase o modulo debe tener una sola razón de ser u ocuparse de una sola cosa y hacerla bien.
¿Qué significa "una razón para cambiar"?
Una razón para cambiar significa que actores podrían solicitar campos, por ejemplo, Equipo de:
- Ventas
- Finanzas
- Usuarios Finales
- Equipo de seguridad
- Etc.
Una analogía del mundo real sería EL RESTAURANTE:
Mesero que:
- Toma ordenes
- Sirve comida
- Cobra
- Limpia
- Gestiona inventario
Esto destroza en su totalidad este principio, ya que sin muchas funciones delegas a una sola entidad.
podríamos tener:
- Mesero: Solo toma órdenes y sirve
- Chef: Solo cocina
- Personal de limpieza: Limpia
- Cajero: solo cobra
- Gerente: Gestiona Inv.
Esto respeta totalmente el principio ya que cada actor o entidad está cumpliendo una única labor y está enfocada en cumplir bien esa labor
Beneficios:
- Es más mantenible y gestionable
- Pequeñas actividades tienen menor margen de error
- La colaboración distribuida hace más reutilizable una entidad
`class Usuario:
def init(self, nombre, email, contraseña):
self.nombre = nombre
self.email = email
self.contraseña = contraseña
def validar_email(self):
"""Valida formato de email"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, self.email) is not None
def encriptar_contraseña(self):
"""Encripta la contraseña"""
import hashlib
return hashlib.sha256(self.contraseña.encode()).hexdigest()
def guardar_en_base_datos(self):
"""Guarda usuario en BD"""
import sqlite3
conn = sqlite3.connect('usuarios.db')
cursor = conn.cursor()
cursor.execute(
"INSERT INTO usuarios VALUES (?, ?, ?)",
(self.nombre, self.email, self.encriptar_contraseña())
)
conn.commit()
conn.close()
def enviar_email_bienvenida(self):
"""Envía email de bienvenida"""
import smtplib
msg = f"Bienvenido {self.nombre}!"
# código para enviar email...
print(f"Email enviado a {self.email}")
def generar_reporte_pdf(self):
"""Genera reporte PDF del usuario"""
from reportlab.pdfgen import canvas
# código para generar PDF...
print(f"PDF generado para {self.nombre}")`
En este código tenemos la clase usuario que hace distintas actividades o mencionado en la analogía anterior del restaurante tenemos el actor usuario que está ejecutando distintas funciones, al igual que la primera representación del mesero.
Aquí podemos identificar que nuestro mesero (clase usuario)
tiene las siguientes actividades:
- Gestionar datos del usuario
- Validar datos del usuario
- Manejar la contraseña del usuario
- Notificaciones
- Persistencia o Guardar en base de datos
- Reportes
Razones para cambiar (Actores):
- Que se cambie el proceso de notificación
- Que modifiquemos el proveedor del email
- Que los reportes tengan formatos distintos
- Que cambiemos la encriptación de la contraseña
- Que migremos la base de datos de SQLite a PostgreSQL
`
1. Clase Usuario: SOLO datos y lógica básica
class Usuario:
def init(self, nombre, email, contraseña):
self.nombre = nombre
self.email = email
self.contraseña = contraseña
def get_datos(self):
return {
'nombre': self.nombre,
'email': self.email
}
2. Validador: SOLO validación
class ValidadorEmail:
@staticmethod
def validar(email):
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
3. Servicio de Seguridad: SOLO encriptación
class ServicioSeguridad:
@staticmethod
def encriptar_contraseña(contraseña):
import hashlib
return hashlib.sha256(contraseña.encode()).hexdigest()
4. Repositorio: SOLO persistencia
class RepositorioUsuario:
def init(self, conexion_bd):
self.conexion = conexion_bd
def guardar(self, usuario):
cursor = self.conexion.cursor()
contraseña_hash = ServicioSeguridad.encriptar_contraseña(usuario.contraseña)
cursor.execute(
"INSERT INTO usuarios VALUES (?, ?, ?)",
(usuario.nombre, usuario.email, contraseña_hash)
)
self.conexion.commit()
def buscar_por_email(self, email):
cursor = self.conexion.cursor()
cursor.execute("SELECT * FROM usuarios WHERE email = ?", (email,))
return cursor.fetchone()
5. Servicio de Email: SOLO notificaciones
class ServicioEmail:
def init(self, config_smtp):
self.config = config_smtp
def enviar_bienvenida(self, usuario):
mensaje = f"¡Bienvenido {usuario.nombre}!"
# Código real de envío de email
print(f"Email enviado a {usuario.email}: {mensaje}")
6. Generador de Reportes: SOLO PDFs
class GeneradorReportesPDF:
def generar_reporte_usuario(self, usuario):
# Código para generar PDF
print(f"PDF generado para {usuario.nombre}")
return f"reporte_{usuario.nombre}.pdf"
USO:
def registrar_usuario(nombre, email, contraseña):
# Validar
if not ValidadorEmail.validar(email):
raise ValueError("Email inválido")
# Crear usuario
usuario = Usuario(nombre, email, contraseña)
# Guardar
import sqlite3
conn = sqlite3.connect('usuarios.db')
repo = RepositorioUsuario(conn)
repo.guardar(usuario)
# Notificar
servicio_email = ServicioEmail(config_smtp={})
servicio_email.enviar_bienvenida(usuario)
# Generar reporte
generador = GeneradorReportesPDF()
generador.generar_reporte_usuario(usuario)
conn.close()
return usuario
`
BENEFICIOS DE LA REFACTORIZACIÓN:
- Usuarios: Solo se encarga de gestionar lo mínimo del usuario
- ValidadorEmail: puedes reutilizarse en otros contextos
- ServicioSeguridad:Fácil cambiar de SHA256 a bcrypt
- El repositorio de usuario es fácil moverlo de DB
- La reporteria puede ser reutilizable y flexible para cambiar de libreria
- Facil de cambiar el proveedor de email
Ahora cada clase tiene una sola razon para cambiar
Top comments (0)