Dagger2 es un framework preferido por la comunidad de desarrolladores Android. Pero intentar entender la integración de Dagger2 en nuestros proyectos de Android no es muy fácil en un inicio.
La cuestión es que para poder usar Dagger2 es necesario tener buenos conocimientos sobre arquitectura de software, y muchos tutoriales existentes en internet omiten esta parte, enseñando Dagger2 sin este conocimiento básico. En esta seria de articulos, mi objetivo es compartir mi conocimento empírico para mostrar todos lo conceptos necesarios para la adopción de Dagger2 usando como base el proyecto Android con Kotlin de este curso (auqnue será en post posteriores, ya que este primero me encofo en explicar los conceptos).
¿Que es inyección de dependencias?
En informática, inyección de dependencias (en inglés Dependency Injection, DI) es un patrón de diseño orientado a objetos, en el que se suministran objetos a una clase en lugar de ser la propia clase la que cree el objeto
~Wikipedia
¿Razones para usar esta técnica?
Dagger2 es un framework para Inyección de Dependencias o Dependency Injection(DI). Para trabajar una arquitectura limpia, DI es generalmente usado en conjunto con el Principio de Inversión o Dependency Inversion Principle (DIP). DI y DIP son dos padrones distintos que a continuación voy a ejemplificar para entender su diferencia con un caso de uso de una persona Player
que lanza un dado Dice
.
classPlayer{
funthrowDice(): Int {
val r = Random()
return r.nextInt(42) + 1
}
// other functions
}
En la clase anterior, Player
depende de la clase Random
. Además de eso, esta controlando como se crea la instancia de Random
. El código anterior se puede refactorar usando DI para <ins>inyectar</ins> esa depencia y quedaria de la siguiente manera:
classPlayerDI(privateval r: Random) {
funthrowDice(): Int {
returnr.nextInt(42) + 1
}
// other functions}
La clase PlayerDI
no tiene mas control sobre como es que la clase Random
es instanciada. Y esto es el concepto básico de inyección de dependencias DI.
Sin embargo, depender de una clase de "bajo nível"como lo es Random
en este ejemplo, es problematico en lo general. Lo ideial, sería que la clase PlayerDI
usase una clase en el mismo nivel de abstracción. Y eso, sería DIP.
interfaceDice{
funthrow(): Int
}
classPlayerDIP(privateval dice: Dice) {
funthrowDice(): Int {
return dice.throw()
}
// other functions
}
La clase PlayerDIP
esta usando DIP para abstraer la operación del método throw
o lanzar el dado, que ahora fue delegado para otra clase que implemente la interface ‘Dice’.
Es importante resaltar que PlayerDIP
no precisa saber como es que la clase Dice
es implementada, dejando el código más fácil de mantener. Esa seria la idea del Principio de Inversión o Dependency Inversion Principle (DIP).
¿Para que me sirve DIP?
Existen diversas controverisas con relación al uso y beneficios de DIP. Dejare algunas situaciones concretas, en donde en el mundo real aplican.
¿Se podría testear la clase Player
? Probandola con valores aleatorios generados por Random
sería muy dificil. Entonces, no se podria.
Con la clase, PlayerDI
, se podria inicializar una instancia de Random
con un valor estático o un objeto mock. ¿Eso nos permitiria testes previsibles? Talvez, pero ¿ la clase PlayerDI
necesita generar números aleatorios variables del tipo float, double, boolean, etc.? No, entonces no es óptimo para testear ya que sería complicado apra todos esos casos.
En conclusión, con la clase PlayerDIP
, es mas fácil testear. Podemos mockar la interface Dice
, asi retornamos lo que realmenten necesitamos durantes las pruebas.
La clase Player
esta apenas interesada en lanzar dados. La generación de número aleatorios es un detalle que la clase Player
no tendria por que saber.
Cuando se muda a una abstracción, como lo es la interface Dice
, para la clase PlayerDIP
, se esta simplificando el código. Ya que al abstraer los detalles, no es necesario preocuparnos como es que el dado es lanzado, permitiendonos asi concentrarnos en resolver solo el problema en ella.
Dice
, es una interface, esto nos permite realizar varias implementaciones de acuerdo a nuestras necesidades. Una implementración puede usar la clase Random
como se ilustra en los ejemplos anteriores, pero tambien pudiera tener otra implementación que llama a un servicio REST que desacopla esa logica y la clase tendria una petición HTTP request con cualquier liberia para ello (tal ves Retrofit o Fuel). O que pasa si se lee un valor existente en la base de datos usando SQLite o ROOM.
Entonces, una buena práctica es abstraerla en una clase repositorio. Esto, como lo mencione anteriorment, nos permite ademas de implementar varias veces la interface Dice
adaptandola a nuestras necesidades.
Bueno, hasta aqui termina el primer articulo para entender posteriormente como integrar Dagger2 a PlatziStore. Espero te sea de mucha utilidad, cualquier cosa, dejalo en los comentarios.
Referencias