Haz de tus aplicaciones una roca sólida | Dependencias

Inversion de dependencias

Un poco de historia

Hemos llegado al final! Yay!!

Llegamos al último capítulo de la serie de SOLID que empezamos hace un tiempo atrás y adivinen qué? Es el momento de hablar de uno de los principios más poderosos y últiles, el principio de inversión de dependencias (DIP por sus siglas en inglés). La famosa D del acrónico SOLID.

Antes de comenzar a hablar del principio, hagamos un pequeño repaso sobre SOLID y sobre dónde podemos encontrar más información sobre esto. Si estás descubriendo la serie de posts desde aquí, el último capítulo, deberías considerar leer los primeros capítulos antes aunque por supuesto no es ningún impedimento para continuar leyendo este. Aquí abajo encontrarás los links a las demás publicaciones de los respectivos capítulos:

Inversión de Dependencias

Después de haber leído y practicado DIP mucho, no puedo imaginarme a mi mismo no usándolo. Sin importar si eres un desarrollador Android experimentado o no, es importante que aprendas este principio y lo pongas en práctica desde el inicio de tus desarrollos. Pero no te preocupes, siempre puedes aplicar este principio, no importa si estás avanzado en tu proyecto, siempre de a un paso a la vez, con pequeños refactors. Una vez que lo hayas incorporado, saldrá de ti mismo como si fuera una canción que escuchaste en la radio y no puedes parar de cantar.

Ahora bien, antes de hablar de inversión, hagamos foco en qué son las dependencias en sí.

Dependencias

Pongámoslo de esta manera, al menos alguna vez en la vida te has topado con algún gráfico de dependencias en tu máquina (si no es así deberías considerar analizar tu aplicación para entender el estado de tu proyecto). Ese gráfico es como una foto de cómo tus clases, librerías y módulos cooperan juntas para lograr los objetivos de tu aplicación.

Entonces, una dependencia es algo en que nosotros dependemos. El algo que nosotros necesitamos para completar una tarea, hacer algún cálculo, enviar un mail, leer un archivo y así sucesivamente. Es importante para tí no sólo identificar las dependencias en tu proyecto sino entenderlas. Esto te forzará sin dudas a crear aplicaciones más flexibles y mantenibles.

El ejemplo anterior representa una dependencia en nustra clase Calculator. Para mostrar el resultado en la pantalla, nuestra clase necesita llamar a la clase ScreenDisplay. La calculadora conoce el tipo de display con el que está trabajando.

Agreguemos una nueva funcionalidad a nuestra Calculator que nos permita guardar los útlimos x valores que calculamos! 💪

Extendiendo la funcionalidad

La primera versión de nuestra nueva feature o funcionalidad necesita mantener el último valor calculado sólo para la sesión actual en el cliente. En ese caso, guardarlo en memoria sería suficiente.

Ahora nuestro Calculator depende de CalculatorRepository. Esto significa que nosotros conocemos exactamente qué instancia y qué tipo de repositorio estamos implementando.

Las dependencias no son malas por sí mismas, sólo necesitamos manejarlas con cautela y de la manera correcta.

Después de algunos días, empezamos a pensar que quizás sea mejor si guardamos estos valores en otro lugar donde le permita al usuario verificar o utilizar el último valor calculado más tarde incluso habiendo cerrado la aplicación.

Somos ingenieros Android, verdad? Usemos entonces algunas herramientas de Android para resolver nuestro problema… digamos, SharePreferences:

Bueno, bastante fácil no es así? Pero necesitamos cambiar la Calculator en sí para poder guardar el resultado. Este cambio no tiene nada que ver con la lógica de realizar el cálculo. Los import en nuestra clase cambiaron también, la firma de nuestro método no, (pero podría haberlo hecho) y dejamos de conocer al CalculatorReposity para tomar conocimiento del new SharedRepository.

¿Pueden ver cuál es el problema aquí?

Pensemos en la posibilidad de que nuestro Calculator se transforme en una aplicación muy famosa y descargada en el PlayStore y decidamos que es momento de cambiar el repositorio otra vez. Esta vez por uno hecho con Room. En ese caso, la puesta en marcha, la definición de las clases necesarias para lograrlo serán más que una «simple clase» más. Tenemos un gran problema aquí de acomplamiento.

El acoplamiento puede ser bueno siempre y cuando sepamos a qué acomplarnos y a qué no.


Necesitamos encontrar una manera de evitar estar acoplados a una dependencia que puede cambiar en el futuro a fin de evitar estar cambiando código que no debería cambiar cuando tomemos estas decisiones.

Interfaces para desacoplar

Ya hemos aprendido algo sobre los buenos usos de las interfaces en capítulos anteriores. Uno de estos usos es el de desacoplar. Qué pensarías si te dijera que los repositorios pueden cambiarse sin que Calculator se de cuenta?

Lo primero que necesitamos para lograr esto es crear una interfaz para la comunicación con el Calculator. Llamémosla CalculatorRepository.

A su vez, cambiamos nuestro Calculator de la siguiente manera:

Lo que hicimos fue inyectar la interfaz del CalculatorRepository y pasarle al Calculator una instancia de la clase SharedRepository. Esto es llamado inyección de dependencias y no lo cubriremos profundamente en este artículo.

Si quieren profundizar sobre Inyección de Dependencias y especialmente en Android pueden consultar la librería Hilt, incluída recientemente en Jetpack. Pueden mirar el video de la charla sobre Hilt a cargo de Manuel Vivo para la comunidad de GDG de Buenos Aires.

Nuestra Calculator no conoce qué tipo de herramienta de persistencia se está utilizando para guardar el valor necesitado. Con esta estrategia, dejamos de depender de un repositorio específico. Esto evita la necesidad de estar cambiando la clase Calculator cuando decidimos cambiar nuestra persistencia, con la posibilidad de incorporar bugs. Y sólo necesitamos una interface!

Definición de la arquitectura de nuestra calculator
Arquitectura de nuestra Calculator

Entonces podemos decir que nuestra capa de dominio conttiene las clases Calculator y CalculatorRepository porque estas con las clases que definen nuestras reglas de dominioi, las entidades y todas las clases que colaborarán para cumplir el propósito de nuestra aplicación. Nosotros no deberíamos cambiar las reglas de negocio debido a un cambio en la tecnología usada en la persistencia… O cualquier otra herramienta, librería, módulo, etc.

En otras palabras, esto significa que la clase Calculator depende de su propio dominio (dentro del package core) es decir el CalculatorRepository y la herramienta de persistencia (por ejemplo, el SharedRepository que está fuera de nuestro dominio) depende de la interfaz de CalculatorRepository que está dentro del dominio.

Hemos invertido nuestras dependencias. Recuerden, nosotros decidimos en qué depender y en qué no.


Desacoplándonos de módulos y librerías externas

La misma estrategia que usamos cuando desacoplamos el dominio principal de la infraestructura es también muy útil para desacoplar módulos internos dentro de tu aplicación o el uso de librerías externas.

Si tu aplicación es multi-módulo, es algo deseable que cada uno de los módulos tenga su propio dominio de tal manera que un cambio en uno de los módulos no afecte necesariamente a los demás módulos. Lo que es más, si tenemos que reemplazar un módulo por otro ( por ejemplo tenemos un módulo que usamos de adapter para una determinada librería ) no tendrás que modificar el dominio en el módulo que «depende» del que vamos a cambiar.

«Depender» es algo relativo debido a DIP. El módulo que depende del módulo que cambia no lo hace directamente sino que a través de su propia interfaz de dominio.

Esta estrategia que utilizamos también aplica para las librerías externas.

Android Styling

Estos principios están presentes en una gran parte de las soluciones en el mundo real (desafortunadamente, en muchas partes no). Exploraremos el sistema de styling de Android para entender cómo este particular principio puede ayudarnos a hacer nuestros estilos extensibles y fáciles de cambiar.

Sabes lo que es un theme en Android verdad? Pero… sabes cómo realmente funciona? Cómo es posible cambiar cómo luce nuestra aplicación con sólo cambiar el theme?

Nos convertimos en premium

Imaginemos que estamos definiendo el color primary que usaremos en nuestro Calculator usando el atributo colorPrimary de los temas y nuestra aplicación tiene dos modos, uno free con una calculadora básica y la versión premium donde usamos una subscripción mensual para convertir nuestra calculadora en científica (con un hermoso cambio de color incluído).

Como estamos usando themes, tendremos al menos dos; uno para la aplicación free y otra para la aplicación premium. Entonces, si el color del botón equal ( = ) debe cambiar de acuerdo a si somos premium o no, es necesario que incluyan algo de este estilo en el widget:

Y en el archivo styles, definiremos el tema de la siguiente manera:

Una vez suscriptos, sólo nos resta reiniciar el CalculatorActivity y aplicar el nuevo theme Theme.Calculator.Subscription para cambiar el color de fondo del botón igual por el que hayamos elegido para premium.

Si aún no se han dado cuenta, esto es lo mismo que tener una interface llamada Theme con un método colorPrimary implementado en ambos temas Free y Subscription. Nuestra aplicación no depende directamente del resource de color que hayamos definido en el backgroundColor del botón equal. Dependemos de una «interface», hemos invertido la dependencia nuevamente.

Si tienen intenciones de comprender aún más sobre estilos y temas en Android, les recomiendo que pasen por la serie de artículos de  Nick Butcher y Chris Banes que comienza aquí.


Conclusión

Después de algunas semanas (y artículos de por medio) hemos llegado al último capítulo de esta serie de SOLID para aprender sobre el principio de Inversión de Dependencias. Es, como comentamos, de los más usados y los más interesantes a la hora de obtener aplicaciones mantenibles y robustas. Hemos cubierto cómo puedes usar este principio para desacoplar tus clases, módulos y de librerías externas.

Este tipo de prácticas van a llevarte a ser un mejor ingeniero Android. Está muy bien seguir las últimas tendencias sobre la implementación de alguna librería, la migración a AndroidX, el uso de los Architecture Components (que definitivamente deben probar) y cualquier otra práctica o tecnología sobre Android, pero son aún más importantes las buenas prácticas de desarrollo como SOLID, patrones de diseño, etc. Existen muchos, cientos de posts, artículos, libros sobre estos temas pero no siempre están conectados con ejemplos de la vida real, problemas de la vida real. Esta serie tuvo como intención de conectar estas buenas prácticas con la programación en Android y realmente espero hayan podido encontrar estos artículos útiles y puedan lograr cambios positivos en sus prácticas y el desarrollo de sus soluciones.

Sigamos conectados 😄
Happy Coding! ✍️

Cover Photo by Markus Spiske on Unsplash

1

Deja un comentario