Ser "nuevo" o no ser ...

Hola de nuevo. En previsión del inicio de los cursos básicos y avanzados sobre desarrollo de Android, hemos preparado para usted otra traducción interesante.




La inyección de dependencia requiere que separemos los nuevos operadores y la lógica de la aplicación . Esta separación lo alienta a usar fábricas en su código que son responsables de vincular su aplicación . Sin embargo, en lugar de escribir fábricas, preferiríamos usar la inyección de dependencia automática , como GUICE, para asumir el enlace. Pero, ¿puede la inyección de dependencia realmente salvarnos de todos los operadores nuevos ?
Veamos dos extremos. Supongamos que tiene una clase MusicPlayer que debería obtener AudioDevice. Aquí queremos usar la inyección de dependencia y solicitar un dispositivo de audio en el constructor MusicPlayer. Esto nos permitirá agregar un dispositivo de audio fácil de probar, que podemos usar para afirmar que el sonido correcto sale de nuestro reproductor de música. Si usáramos el nuevo operador para crear una instancia de BuiltInSpeakerAudioDevice, tendríamos algunas dificultades de prueba. Entonces llamemos a objetos como AudioDevice o MusicPlayer "Injectable". Inyectables son los objetos que solicitará en los constructores y espera que el marco para implementar dependencias le proporcione estos.

Ahora al otro extremo. Supongamos que tiene un int int primitivo, pero desea empaquetarlo automáticamente en Integer, lo más simple es llamar al nuevo Integer (5), y ese es el final. Pero si la inyección de dependencia es la nueva "nueva", ¿por qué llamamos nueva en línea? ¿Hará daño a nuestras pruebas? Resulta que los marcos para inyectar dependencias no pueden darle el número entero que necesita, porque no entienden qué tipo de número entero es. Este es un ejemplo de juguete, así que veamos algo más complejo.

Supongamos que un usuario ha ingresado una dirección de correo electrónico en el campo de inicio de sesión y necesita llamar a un nuevo correo electrónico («a@b.com») ¿Podemos dejarlo así, o deberíamos solicitar un correo electrónico en nuestro constructor? Una vez más, el marco de inyección de dependencias no puede proporcionarle correo electrónico, porque primero debe obtener la cadena en la que se encuentra el correo electrónico. Y hay muchas cadenas para elegir. Como puede ver, hay muchos objetos que el marco de inyección de dependencia nunca puede proporcionar. Llamémoslos "Newable", ya que se verá obligado a llamarlos nuevos manualmente.

Primero, establezcamos algunas reglas básicas. Una clase inyectable puede consultar a otros inyectables en su constructor. (A veces llamo Injectable como objeto de servicio, pero este término está sobrecargado). Injectable tiende a tener interfaces, ya que existe la posibilidad de que tengamos que reemplazarlas con una implementación que sea conveniente para las pruebas. Sin embargo, Injectable nunca puede consultar no inyectable (Newable) en su constructor. Esto se debe a que el marco de inyección de dependencias no sabe cómo crear un Newable. Estos son algunos ejemplos de clases que esperaría de mi marco de inyección de dependencias: CreditCardProcessor, MusicPlayer, MailSender, OfflineQueue. Del mismo modo, Newable puede ser solicitado por otro Newable en su constructor, pero no Inyectable (a veces llamo a Newable como un Objeto de valor, pero de nuevo,este término está sobrecargado). Algunos ejemplos de Newable: Email, MailMessage, User, CreditCard, Song. Si sigue estas distinciones, su código será fácil de probar y trabajar. Si infringe estas reglas, su código será difícil de probar.

Veamos el ejemplo de MusicPlayer y Song.

class Song {
  Song(String name, byte[] content);
}
class MusicPlayer {
  @Injectable
  MusicPlayer(AudioDevice device);
  play(Song song);
}

Tenga en cuenta que Song solo consulta objetos que son renovables. Esto hace que sea muy fácil crear una instancia de una canción en una prueba. MusicPlayer es completamente inyectable, como su argumento AudioDevice, por lo que se puede obtener del marco para la inyección de dependencia.

Ahora veamos qué sucede si MusicPlayer rompe la regla y solicita Newable en su constructor.

class Song {
  String name;
  byte[] content;
  Song(String name, byte[] content);
}
class MusicPlayer {
  AudioDevice device;
  Song song;
  @Injectable
  MusicPlayer(AudioDevice device, Song song);
  play();
}

Aquí, Song todavía es renovable, y es fácil de crear en su prueba o en su código. MusicPlayer ya es un problema. Si le pide a MusicPlayer desde su marco para la inyección de dependencia, se bloqueará porque el marco no sabrá de qué canción se trata. La mayoría de las personas nuevas en los marcos de inyección de dependencia rara vez cometen este error, ya que es fácil de notar: su código no funcionará.

Ahora veamos qué sucede si Song rompe la regla y solicita Injectable en su constructor.

class MusicPlayer {
  AudioDevice device;
  @Injectable
  MusicPlayer(AudioDevice device);
}
class Song {
  String name;
  byte[] content;
  MusicPlayer palyer;
  Song(String name, byte[] content, MusicPlayer player);
  play();
}
class SongReader {
  MusicPlayer player
  @Injectable
  SongReader(MusicPlayer player) {
    this.player = player;
  }
  Song read(File file) {
    return new Song(file.getName(),
                    readBytes(file),
                    player);
  }
}

A primera vista, todo está bien. Pero piense en cómo se crearán las canciones. Presumiblemente, las canciones se almacenan en el disco, por lo que necesitamos SongReader. SongReader tendrá que solicitar MusicPlayer para que cuando llame a Song for New, pueda satisfacer las dependencias de Song en MusicPlayer. ¿Has notado algo mal aquí? ¿Qué miedo necesita saber SongReader sobre MusicPlayer? Esto es una violación de la ley de Demeter.. SongReader no debe saber sobre MusicPlayer. Al menos porque SongReader no llama a métodos con MusicPlayer. Solo sabe sobre MusicPlayer porque Song ha violado la separación Newable / Injectable. SongReader paga el error en Song. Dado que el lugar donde se comete el error y donde se manifiestan las consecuencias no es lo mismo, este error es muy sutil y difícil de diagnosticar. También significa que es probable que muchas personas cometan este error.

En términos de pruebas, este es un verdadero dolor. Suponga que tiene SongWriter y quiere asegurarse de que serializa Song al disco correctamente. Debe crear un MockMusicPlayer para poder pasarlo a Song para poder pasarlo a SongWritter. ¿Por qué nos encontramos con MusicPlayer aquí? Miremos de otra manera. Es posible que desee serializar una canción, y la forma más fácil de hacerlo es utilizar la serialización de Java. Por lo tanto, serializamos no solo Song, sino también MusicPlayer y AudioDevice. Ni MusicPlayer ni AudioDevice deben ser serializados. Como puede ver, los pequeños cambios facilitan enormemente la capacidad de prueba.

Como puede ver, trabajar con código es más fácil si separamos estos dos tipos de objetos. Si los mezcla, su código será difícil de probar. Se pueden renovar los objetos que se encuentran al final del gráfico de objetos de su aplicación. Newable puede depender de otro Newable, por ejemplo, cómo CreditCard puede depender de Address, que puede depender de City - estas cosas son hojas del gráfico de la aplicación. Como son hojas y no se comunican con ningún servicio externo (los servicios externos son inyectables), no necesitan hacer talones. Ya nada se parece a String más que String. ¿Por qué debería hacer un talón para el Usuario, si solo puedo llamar a un nuevo Usuario, por qué hacer talones para algo de esto: Correo electrónico, Mensaje de correo, Usuario, Tarjeta de crédito, Canción? Simplemente llame a nuevo y termínelo.

Ahora prestemos atención a algo muy sutil. Es normal que Newable sepa sobre Inyectable. Lo que no es normal es que Newable tiene una referencia a Inyectable como campo. En otras palabras, Song puede saber sobre MusicPlayer. Por ejemplo, es normal que Injectable MusicPlayer pase a través de la pila a Newable Song. Porque pasar por la pila es independiente del marco para la inyección de dependencia. Como en este ejemplo:

class Song {
  Song(String name, byte[] content);
  boolean isPlayable(MusicPlayer player);
}

El problema ocurre cuando Song tiene un campo de enlace a MusicPlayer. Los campos de enlace se establecen a través del constructor, lo que causará una violación de la ley Demeter para la persona que llama y dificultades para nuestras pruebas.

Aprende más sobre los cursos



All Articles