El otro día estaba en una reunión de “retrospectiva” con mi equipo de desarrollo. Ya sabes, esas reuniones donde analizamos qué ha ido bien, qué ha ido mal y cómo podemos mejorar. En medio de la discusión, María, una desarrolladora, soltó una frase que me dejó pensando: “Chicos, cada vez que añadimos una nueva funcionalidad, parece que estamos jugando al Jenga con el código. Movemos una pieza y todo se tambalea”.
Esa frase resume perfectamente por qué necesitamos el Principio Abierto/Cerrado (Open/Closed Principle), el segundo principio de SOLID. Así que hoy vamos a sumergirnos en la “O” de SOLID, pero no te preocupes, no vamos a jugar al Jenga con nuestro código.
¿Qué es eso de Abierto/Cerrado?
El Principio Abierto/Cerrado suena un poco a oxímoron, ¿verdad? Como decir “silencio ensordecedor” o “pequeño gigante”. Pero en realidad, es bastante lógico una vez que lo entiendes. Este principio establece que las entidades de software (clases, módulos, funciones, etc.) deben estar:
Abiertas para su extensión
Cerradas para su modificación
En otras palabras, deberíamos poder añadir nuevas funcionalidades sin tener que modificar el código existente. Suena bien, ¿no? Pero, ¿cómo se hace eso en la práctica?
Un ejemplo para entenderlo mejor
Imagina que estás desarrollando un sistema de notificaciones para una app. Empiezas con notificaciones por email:
def enviar_notificacion(self, mensaje):
# Código para enviar email
print(f“Enviando email: {mensaje}“)
Todo va bien hasta que tu jefe dice: “Necesitamos añadir notificaciones por SMS”. Así que modificas la clase:
def enviar_notificacion(self, tipo, mensaje):
if tipo == “email“:
# Código para enviar email
print(f“Enviando email: {mensaje}“)
elif tipo == “sms“:
# Código para enviar SMS
print(f“Enviando SMS: {mensaje}“)
Parece que funciona, ¿no? Pero, ¿qué pasa si luego necesitas añadir notificaciones push? ¿Y notificaciones en Slack? Cada vez que añadas un nuevo tipo de notificación, tendrás que modificar esta clase. Esto viola el principio Abierto/Cerrado.
Aplicando el Principio Abierto/Cerrado
Veamos cómo podríamos mejorar esto:
class Notificador(ABC):
@abstractmethod
def enviar(self, mensaje):
pass
class NotificadorEmail(Notificador):
def enviar(self, mensaje):
print(f“Enviando email: {mensaje}“)
class NotificadorSMS(Notificador):
def enviar(self, mensaje):
print(f“Enviando SMS: {mensaje}“)
# Si necesitamos añadir notificaciones push:
class NotificadorPush(Notificador):
def enviar(self, mensaje):
print(f“Enviando notificación push: {mensaje}“)
¿Ves la diferencia? Ahora, si necesitamos añadir un nuevo tipo de notificación, simplemente creamos una nueva clase que herede de Notificador. No necesitamos modificar ninguna de las clases existentes. Esto es estar abierto para extensión y cerrado para modificación.
¿Por qué es importante?
Menos riesgo de bugs: Al no tocar código existente (y presumiblemente ya probado), reduces el riesgo de introducir nuevos errores.
Código más mantenible: Es más fácil añadir nuevas funcionalidades sin tener que entender y modificar todo el código existente.
Mejor testabilidad: Puedes probar cada implementación de forma aislada.
Flexibilidad: Puedes añadir nuevos comportamientos sin afectar a los consumidores del código existente.
Cuidado con las trampas
Como todo en programación, el Principio Abierto/Cerrado no es una varita mágica. Aplicarlo en exceso puede llevar a un diseño innecesariamente complejo. No necesitas hacer que absolutamente todo sea extensible desde el principio. La clave está en identificar las áreas de tu código que es más probable que cambien o se extiendan en el futuro.
En la práctica
Aquí tienes algunos consejos para aplicar el Principio Abierto/Cerrado en tu día a día:
Usa la abstracción: Interfaces y clases abstractas son tus amigos.
Aplica el patrón Strategy: Es una excelente manera de hacer que el comportamiento sea extensible.
Inyección de dependencias: Permite cambiar el comportamiento sin modificar la clase que lo usa.
Composición sobre herencia: A menudo, la composición ofrece más flexibilidad que la herencia para extender comportamientos.
Conclusión
El Principio Abierto/Cerrado es como tener un buen sistema de enchufes en tu casa. No necesitas rewirear toda la instalación eléctrica cada vez que quieres añadir un nuevo electrodoméstico, simplemente lo enchufas. De la misma manera, un buen diseño de software te permite “enchufar” nuevas funcionalidades sin tener que reescribir todo el sistema.
Recuerda las palabras de María sobre el Jenga de código. Con el Principio Abierto/Cerrado, en lugar de jugar al Jenga, estarás construyendo con Lego. Cada nueva pieza encaja perfectamente sin desestabilizar la estructura existente.
La próxima vez que te encuentres modificando una clase una y otra vez para añadir nuevas funcionalidades, párate y piensa: “¿Cómo puedo diseñar esto para que esté abierto a extensiones pero cerrado a modificaciones?”. Tu yo del futuro (y tus compañeros de equipo) te lo agradecerán.