Haz de tus aplicaciones una roca sólida | Open-Closed

Recapitulemos

Ya hemos hecho una introducción a SOLID en el primer artículo de la serie explicando la importancia y la necesidad de cumplir con estos principios a la hora de construír aplicaciones más robustas y esta vez le toca a la O, el principio de Open-Closed. Sin más preámbulos, comencemos.


¿De qué hablamos cuando decimos Open-Closed?

Tal cual lo hicimos en la presentación de los principios, lo más conveniente para comenzar a responder esta pregunta es empezar por la definición planteada en aquel entonces:

El principio de Open Closed dice que «un artefacto debe ser abierta para la extensión y cerrada para la modificación

En otras palabras, necesitamos definir nuestros artefactos de tal manera que no necesitemos modificarlos para agregar funcionalidad o para extender su funcionalidad. Quizás esto les resulte algo sencillo de cumplir, y de hecho lo es. Sin embargo, por lo menos en mi experiencia, es más común de lo que se piensa no cumplir con este principio. Los podemos encontrar en el código legacy de nuestras aplicaciones o incluso en el código en el que trabajamos actualmente. Vamos a hacer foco en el código que tengamos más reciente, en ese que no nos cueste tanto modificar ya que en principio puede suponer un menor esfuerzo para mejorar nuestro diseño. ¿Quién recuerda lo que hizo meses o años atrás? ¿Código legaqué?

Así como lo hicimos con el principio de Single Reponsibility, es posible aplicar este principio a varios niveles. Un artefacto, puede ser una clase o un módulo completo. Se imaginan cómo podríamos llegar a esto?


Módulos

Asumamos que estamos trabajando en una aplicaicón Android que está compuesta por distintos módulos (ya no hacemos aplicaciones monolíticas, ¿no es así?). Ya aprendimos en el artículo anterior que cada módulo debería tener sólo un motivo de cambio, debería hacer sólo una cosa. Prestemos especial atención al módulo notification.

Este módulo está dedicado al envío de notificaciones a otros módulos desde cualquier parte de la aplicación. Al momento de analizar nuestra aplicación, todo funciona correctamente y parecería que no tenemos ningún problema en absoluto. Sin embargo, el problema aparece cuando necesitamos poder notificar a un nuevo módulo.

Si investigamos internamente en el módulo de notificaciones, veremos que la clase NotificationSender tiene la siguiente estructura.

Si nuestro equipo es el encargado de realizar la modificación, debería venir aquí y agregar otra sentencia en el when del método send. El equipo está prácticamente seguro que este es el lugar en dónde tiene que hacerlo y el trabajo no llevaría más que un par de horas como mucho. Si pensamos un momento, ¿podríamos decir que esta estructura respeta OCP? Bueno… la verdad es que no y ahora veremos por qué.

— ¿Por qué decís que esto está mal? ¿No es éste el lugar en donde hay que realizar la modificación?
— Sí, lo es. Pero el hecho es que no respeta un principio.
— Pero, pero… el módulo completo fue refactorizado y actualmente está respetando SRP. ¡Te leímos!
— Sin embargo, no respeta OCP.
— ???

Pues bien, el artefacto que construímos no está cerrado a modificaciones. Para completar la tarea, el equipo necesita agregar código aquí, no sólo modificar la clase NotificationSender sino también hacer que este módulo conozca todos los demás módulos. Una mejor estrategia sería que este módulo sea capaz de enviar notificaciones a destinos que no conozca.

Esto se logra haciendo que el módulo sea capaz de recibir un número de objetos notificables sin saber a ciencia cierta a qué módulo realmente está notificando. Lo que es aún mejor, esta estrategia permite que se deje de notificar un módulo en algún momento determinado sin que el módulo lo sepa. Esto sin dudas le da una cierta flexibilidad a nuestro código que al final del día, hace que construyamos una aplicación más mantenible.

Discutiremos cómo lograr esto en la próxima sección donde hablemos sobre OCP a nivel de clase.


Clases

Las clases son, sin duda alguna, el lugar más común donde encontrar este tipo de problemas. En la sección anterior, hablamos sobre el principio de Open Closed a nivel de módulo pero para finalmente poder resolver el problema que teníamos a ese nivel, necesitamos eliminar la violación de este principio a nivel de clases. Veamos cómo funciona:

La clase NotificationSender sólo sabe que es capaz de notificar objetos notifiable (que en nuestro contrato significa módulos notifiable). Realmente no sabe sobre qué módulo le han encargado para notificar y cuál no.

¿Qué deberíamos hacer o dónde deberíamos agregar un nuevo módulo para que sea notificado? Exacto! Adivinaron, no aquí.

Sólo es necesario agregar una nueva clase notifiable en la lista de objetos que le pasamos al NotificationSender. Si necesitan que esta clase sea más flexible aún, pueden cambiar el estado de la lista permitiendo agregar o quitar módulos para ser notificados desde distintos lugares en vez de sólo en la inicialización del módulo.


¿Qué hay del ejemplo para Android?

Lo sé, lo sé. No me había olvidado. Hagámoslo realidad.

Planteemos un escenario de ejemplo en donde tenemos una Custom View en algún archivo XML en donde necesita dibujar objetos dependiendo de cuántos reciba. El número de items a dibujar y por supuesto, los items mismos vienen como respuesta de una API. El cliente (la aplicación Android) sólo será responsable de dibujarlos en el orden definido y aceptando sólo la cantidad de ítems recibidos y nada más.

Asumiendo que el contenedor de nuestros ítems es un ConstraintLayout y que necesitamos acomodar los objetos dependiendo de cuántos vengan, nuestra Custom View podría quedar de la siguiente manera:

La función initializeWithItems no puede ser extendida porque es necesario modificarla, en este caso agregando un nuevo case al when de manera que acepte más cantidad de items. Es necesario cambiar la implementación para lograr que sea posible extender la funcionalidad de este método y así evitar modificarla en un futuro.

Lo primero que debemos hacer es extraer una interfaz de tal manera que nos permita decirle a nuestra clase que necesitamos agregar items a nuestra Custom View dinámicamente.

Esta es una versión simplificada de la interfaz. Seguramente necesiten pasar un ContraintSet desde el padre para poder ubicar los objetos pero a fines ilustrativos, estamos bien.

Luego de esto, inyectando en nuestra Custom View una lista de los distintos objectos ItemOrder y modificando el código de la función addItems podemos lograr nuestro objetivo de hacer esta clase abierta a la extensión y cerrada para la modificación.


Conclusión

Logramos comprender juntos los beneficios de utilizar el Principio de Open-Closed prestando especial atención a su aplicación a distintos niveles. Cada uno de los artefactos que construyamos deben ser pensados no sólo para cumplir con un único objetivo sino también para poder extender fácilmente su funcionalidad.

Romper con Open-Close es un escenario muy común en la vida real por lo que no importa si encuentran este problema en el código que haya escrito un colega o somos nosotros mismos quienes creamos o estemos a punto de crear el problema, debemos pensar en los futuros desarrolladores que pasen por el mismo código antes de dejarlo así como está.

En el próximo capítulo discutiremos el principio de Liskov Substiturion, a mi entender, uno de los más difíciles de tratar. Hasta la próxima!

Sigamos conectados 😄
Happy Coding! ✍️

Cover photo by @quimono

0

Deja un comentario