Ser "novo" ou não ser ...

Olá de novo. Antecipando o início dos cursos básicos e avançados de desenvolvimento para Android, preparamos para você outra tradução interessante.




A injeção de dependência exige que separemos os novos operadores e a lógica do aplicativo . Essa separação incentiva você a usar fábricas em seu código responsáveis ​​por vincular seu aplicativo . No entanto, em vez de escrever fábricas, preferimos usar a injeção automática de dependência , como o GUICE, para assumir o vínculo. Mas a injeção de dependência pode realmente nos salvar de todos os novos operadores?
Vamos olhar para dois extremos. Digamos que você tenha uma classe MusicPlayer que deve obter o AudioDevice. Aqui, queremos usar a injeção de dependência e solicitar um AudioDevice no construtor MusicPlayer. Isso nos permitirá adicionar um AudioDevice fácil de testar, que podemos usar para afirmar que o som correto sai do nosso MusicPlayer. Se usarmos o novo operador para criar uma instância do BuiltInSpeakerAudioDevice, teremos algumas dificuldades de teste. Então, vamos chamar objetos como AudioDevice ou MusicPlayer de "Injectable". Injetáveis ​​são os objetos que você solicitará nos construtores e espera que a estrutura para implementar dependências os forneça.

Agora, para o outro extremo. Suponha que você tenha um int int primitivo, mas deseja empacotá-lo automaticamente em Inteiro, o mais simples é chamar o novo Inteiro (5), e esse é o fim. Mas se a injeção de dependência é o novo "novo", por que chamamos de novo em linha? Isso prejudicará nossos testes? Acontece que as estruturas para injetar dependências não podem fornecer o número inteiro necessário, porque eles não entendem que tipo de número inteiro é. Este é um exemplo de brinquedo, então vamos ver algo mais complexo.

Suponha que um usuário tenha inserido um endereço de email no campo de login e você precise ligar para um novo email («a@b.com») Podemos deixar assim ou devemos solicitar e-mail em nosso construtor? Novamente, a estrutura de injeção de dependência não pode fornecer Email, porque você deve primeiro obter a String na qual o email está localizado. E há muitas String s para escolher. Como você pode ver, existem muitos objetos que a estrutura de injeção de dependência nunca pode fornecer. Vamos chamá-los de "Novo", pois você será forçado a chamar de novo manualmente.

Primeiro, vamos definir algumas regras básicas. Uma classe injetável pode consultar outro injetável em seu construtor. (Às vezes, chamo de Injetável como um Objeto de Serviço, mas esse termo está sobrecarregado.) Injetáveis ​​tendem a ter interfaces, pois há uma chance de termos que substituí-las por uma implementação que seja conveniente para o teste. No entanto, Injectable nunca pode consultar não-Injetável (Newable) em seu construtor. Isso ocorre porque a estrutura de injeção de dependência não sabe como criar um Newable. Aqui estão alguns exemplos de classes que eu esperaria da minha estrutura de injeção de dependência: CreditCardProcessor, MusicPlayer, MailSender, OfflineQueue. Da mesma forma, Newable pode ser solicitado por outro Newable em seu construtor, mas não injetável (às vezes eu chamo Newable como um objeto de valor, mas, novamente,este termo está sobrecarregado). Alguns exemplos de Newable: Email, MailMessage, User, CreditCard, Song. Se você seguir essas distinções, seu código será fácil de testar e trabalhar. Se você violar essas regras, seu código será difícil de testar.

Vejamos o exemplo do MusicPlayer e Song

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

Observe que a música consulta apenas objetos que são renováveis. Isso facilita muito a instanciação de uma música em um teste. O MusicPlayer é completamente injetável, como o argumento AudioDevice, e pode ser obtido a partir da estrutura para injeção de dependência.

Agora vamos ver o que acontece se o MusicPlayer quebrar a regra e solicitar Newable em seu construtor.

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

Aqui, a música ainda é renovável e é fácil criar no seu teste ou no seu código. O MusicPlayer já é um problema. Se você solicitar ao MusicPlayer de sua estrutura a injeção de dependência, ela falhará porque a estrutura não saberá sobre qual música é. A maioria das pessoas novas nas estruturas de injeção de dependência raramente comete esse erro, pois é fácil perceber: seu código não funcionará.

Agora vamos ver o que acontece se o Song quebra a regra e solicita o Injetável em seu construtor.

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);
  }
}

À primeira vista, está tudo bem. Mas pense em como as músicas serão criadas. Presumivelmente, as músicas são armazenadas em disco, por isso precisamos do SongReader. O SongReader precisará solicitar o MusicPlayer para que, ao chamar um novo Song, ele possa satisfazer as dependências da música no MusicPlayer. Você notou alguma coisa errada aqui? Que susto o SongReader precisa saber sobre o MusicPlayer? Isso é uma violação da lei de Demeter. O SongReader não deve saber sobre o MusicPlayer. Pelo menos porque o SongReader não chama métodos com o MusicPlayer. Ele só conhece o MusicPlayer porque o Song violou a separação Newable / Injectable. O SongReader paga pelo erro na música. Como o local onde o erro é cometido e onde as conseqüências são manifestadas não é a mesma coisa, esse erro é muito sutil e difícil de diagnosticar. Isso também significa que muitas pessoas provavelmente cometerão esse erro.

Em termos de teste, isso é uma verdadeira dor. Suponha que você tenha o SongWriter e deseje garantir a serialização da música no disco corretamente. Você precisa criar um MockMusicPlayer para poder passá-lo para o Song, para passá-lo ao SongWritter. Por que encontramos o MusicPlayer aqui? Vamos olhar para o outro lado. A música é o que você pode querer serializar, e a maneira mais fácil de fazer isso é usar a serialização Java. Assim, serializamos não apenas a música, mas também o MusicPlayer e o AudioDevice. Nem o MusicPlayer nem o AudioDevice devem ser serializados. Como você pode ver, pequenas alterações facilitam bastante a testabilidade.

Como você pode ver, trabalhar com código é mais fácil se separarmos esses dois tipos de objetos. Se você os misturar, será difícil testar seu código. Novos são os objetos que estão no final do gráfico de objetos do seu aplicativo. O recurso Newable pode depender de outro Newable, por exemplo, como o CreditCard pode depender do Endereço, que pode depender da Cidade - essas são folhas do gráfico do aplicativo. Como são folhas e não se comunicam com nenhum serviço externo (serviços externos são injetáveis), eles não precisam fazer stubs. Nada se parece mais com String do que a própria String. Por que devo fazer um stub para o Usuário, se posso apenas chamar um novo Usuário, por que fazer stubs para algo disso: Email, MailMessage, User, CreditCard, Song? Apenas chame de novo e termine.

Agora vamos prestar atenção a algo muito sutil. É normal que o Newable saiba sobre o Injetável. O que não é normal é que Newable tem uma referência a Injectable como um campo. Em outras palavras, a Song pode saber sobre o MusicPlayer. Por exemplo, é normal que o Injectable MusicPlayer seja passado pela pilha para Newable Song. Porque a passagem pela pilha é independente da estrutura para injeção de dependência. Como neste exemplo:

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

O problema ocorre quando a música possui um campo de link para o MusicPlayer. Os campos de link são definidos pelo construtor, o que causará uma violação da lei Demeter para o chamador e dificuldades para nossos testes.

Saiba mais sobre os cursos



All Articles