Haz de tus aplicaciones una roca sólida | Interfaces

Qué significa la segregación de interfaces o ‘Interface Segregation’?

Tomémonos unos momentos para pensar en las interfaces y por qué las usamos. Tendemos a usarlas para definir protocolos, comunicación entre objetos, desacoplamiento (hablaremos sobre esto más en profundidad en el próximo y último capítulo de esta serie) y por qué no; para definir comportamiento.

Si haz llegado hasta aquí y te preguntás que es SOLID, te recomiendo pasar por el primer post de esta serie aquí.

Comportamiento

Usamos clases y subclases para definir jerarquías de objetos o familias. Sin embargo, podemos usar interfaces para definir comportamiento sobre estas clases.

Sólo recuerda… no todas las aves pueden volar

Echemos un poco de luz a todo esto con un poco de código. Nuestro propósito de hoy es construír un juego en Android para un zoológico (esto es sólo un ejemplo, no quiero empezar un debate sobre tener animales fuera de su hábitat natural) y tenemos una categoría Bird para representar a uno de nuestros personajes en el juego. Si el usuario selecciona un ave automáticamente obtienen todas las habilidades especiales del ave, muy diferentes por ejemplo de las de un felino, super fácil:

Uno de nuestros primeros valientes testers selecciona una gallina como personaje y comienza el juego. Después de algún tiempo, dice a los stakeholders:

– El juego es muy divertido y hasta sencillo de jugar pero… mi gallina puede volar y hasta donde tengo entendido las gallinas no vuelan en absoluto.

¿Qué piensan que pasó aquí? El problema principal es que los desarrolladores utilizaron interfaces para categorizar en vez de usarlas para definir comportamiento.

No se preocupen, dijeron los desarrolladores. Podemos solucionarlo de manera muy sencilla realizando el siguiente cambio:

Después de volver a buildear y correr el juego, los tests pasaron según lo esperado. Si seleccionaban la gallina, ésta ya no volaba más.


Code Smells

Code Smell

Llamamos «code smell» a aquellas cosas que encontramos en el código y no nos dejan dormir de noche, para decirlo de alguna manera. Un «code smell» es algo que estamos bastante seguros de que será un posible problema en el futuro, es algo que dudamos de dejarlo de esa manera y no sabemos cómo corregirlo.

Dejar un método de una interfaz vacío, sin implementación es un «code smell». Particularmente en este caso, tenemos que dejar el método fly en la clase Chicken sin implementación debido a bueno, ya saben… la naturaleza. Las gallinas no pueden volar.

Dijimos anteriormente que el principal problema que tuvimos fue usar una interfaz para categorizar algo en vez de para definir comportamiento. Qué pasaría si dejamos la categorización de Bird como una abstract class (con algún método eat o el número de alas (?. ) y creamos la interface con la habilidad de volar (la cual no todas las aves tienen). Nos quedaría una interfaz de la siguiente manera:

La interfaz flyable sólo nos define el comportamiento. Una habilidad. La de volar, la cual por supuesto no será implementada por la clase Chicken. Quitando el método fly de la interfaz Bird es segregación de interfaces.


¿Necista mi interfaz ser «segregada»?

Bueno, digamos que todo depende de cómo esté definida esa interfaz y cuál sea su propósito final. Pensemos en un ejemplo para Android… es por ello que estamos aquí, ¿no es así?.

Estamos creando una aplicación Android para una compañía de streaming que necesita brindar a sus usuarios la mejor experiencia en búsqueda de películas para poder así mejorar la retención de usuarios y que éstos no migren a otras plataformas. Esta aplicación en particular tiene una regla de negocio muy particular, tu pagas por las películas que quieres ver y nada más.

Digamos que tenemos un Activity con alguna GridView (algo que podemos lograr sin ningún problema usando un RecyclerView + GridLayoutManager y que no será cubierto por este artículo). La primera cosa que la compañía nos pide es la necesidad de mostrar una preview de la película si el usuario hace click sobre la portada de la película. Para ello, creamos el siguiente listener:

Perfecto! Luego necesitaríamos mostrar un Dialog o algo similar para confirmar la compra cuando el usuario hace click en el ícono de compra en el slot de la película. Y además, necesitaríamos mostrar más o menos películas similares haciendo click en otros botones que se encuentran en el mismo slot de la película. Entonces, hacemos una pequeña actualización a nuestra interfaz y nos resulta de la siguiente manera:

La interfaz resultante logra con todos los objetivos que tenemos para convertirse en un listener de slots. Nada debería separarse. O segregarse. Pero qué pasaría si esta interfaz se convierte en…

Un listener para controlarlos a todos?

Estamos muy seguros sobre el diseño de nustro MoviewGridActivity y también estamos listo para iniciar una nueva sección de la app. Como estamos haciendo una aplicación de streaming de películas, la próxima sección será sobre… películas, por supuesto!

Los dueños de la compañía nos piden agregar una nueva sección en donde el usuarios pueda «sugerir» a la plataforma que agregue algunas películas a su catálogo así ellos mismos la pueden comprar.
Tendremos otro Grid con algunas películas las cuales los usuarios podrán hacer click y pedir que sean agregadas para la compra. Entonces, lo que podemos hacer es tomar nuestro MovieListener e implementarlo:

Estamos teniendo un problema aquí. La interfaz que estamos usando tiene un montón de métodos que no son necesarios en este contexto. Dejando un comentario en aquellas implementaciones que no necesitamos es un code smell. Qué pasaría si accidentalmente implementamos uno de esos métodos? Bueno… podríamos… podemos? Debemos? ¡¿Quién sabe?! El método puede ser llamado y podría haberse implementado por error el listener dentro del ViewHolder y transformar este inocente método vacío en un problema.

Para reducir la posibilidad de cometer un error (o peor aún, hacer que otros cometan el error) debemos «separar» la interfaz en al menos dos; una para el caso del click de las portadas y la otra para la sección de compra resultando en:

… el cual es una mejor estrategia debido a que no nos fuerza a dejar un método sin implementar si realmente no lo necesitamos generando a su vez, una gran confusión.


Conclusión

Hoy hemos aprendido no sólo que no todos los pájaros pueden volar, sino también que debemos mantener nuestras interfaces «enfocadas» en los problemas que tienen que resolver y nada más. Reutilizar las puede parecer barato y sencillo al inicio pero al final puede resultar realmente doloroso. Nuestro código queda difícil de leer, entender y por supuesto mantener.


Próximamante estaré subiendo el último capítulo de la serie sobre SOLID. Éste último espisodio será muy interesando debido que es un tema mul útil cuando trabajas en entornos multi-módulo en Android. Cómo evitar que un cambio en un módulo puede afectar directamente a otro? Cómo prevenís que vos mismo tengas que cambiar muchas clases para poder hacer un update de una librería o un módulo? Vamos a explorar el principio de Inversión de Dependencias o Dependency Inversion que nos ayudará a ser más flexibles.

Sigamos conectados 😄
Happy Coding! ✍️

Cover photo by https://jooinn.com/

4

Deja un comentarioCancelar respuesta