Injeção de Dependência: Injeção em Campo vs Injeção de Construtor?

56

Eu sei que este é um debate acalorado e as opiniões tendem a mudar ao longo do tempo quanto à melhor abordagem prática.

Eu costumava usar exclusivamente injeção de campo para minhas aulas, até que comecei a ler em blogs diferentes (ex: petrikainulainen e schauderhaft e fowler ) sobre os benefícios de injeção de construtor. Desde então, mudei minhas metodologias para usar injeção de construtor para dependências necessárias e injeção de setter para dependências opcionais.

No entanto, recentemente entrei em um debate com o autor de JMockit - uma estrutura de zombaria - em que ele vê construtor & injecção setter como má prática e indica que a comunidade JEE concorda com ele.

No mundo de hoje, existe uma maneira preferida de fazer injeção? A injeção de campo é preferida?

Tendo mudado para injeção de construtor a partir de injeção de campo nos últimos dois anos, acho muito mais claro para usar, mas estou querendo saber se eu deveria reexaminar meu ponto de vista. O autor do JMockit (Rogério Liesenfeld) , é claramente bem versado em DI, então me sinto obrigado a rever minha abordagem, dado que ele se sente tão strongmente contra a injeção construtor / setter.

    
por Eric B. 23.10.2015 / 18:47
fonte

5 respostas

47

Injeção de campo é um pouco "ação assustadora à distância" para o meu gosto.

Considere o exemplo que você forneceu na sua postagem do Grupos do Google:

public class VeracodeServiceImplTest {


    @Tested(fullyInitialized=true)
    VeracodeServiceImpl veracodeService;
    @Tested(fullyInitialized=true, availableDuringSetup=true)
    VeracodeRepositoryImpl veracodeRepository;
    @Injectable private ResultsAPIWrapper resultsApiWrapper;
    @Injectable private AdminAPIWrapper adminApiWrapper;
    @Injectable private UploadAPIWrapper uploadApiWrapper;
    @Injectable private MitigationAPIWrapper mitigationApiWrapper;

    static { VeracodeRepositoryImpl.class.getName(); }
...
}

Então, basicamente, o que você está dizendo é que "tenho essa classe com estado privado, à qual anexei as anotações @injectable , o que significa que o estado pode ser preenchido automaticamente por algum agente externo, mesmo que meu estado foi tudo declarado privado. "

Eu entendo as motivações para isso. É uma tentativa de evitar grande parte da cerimônia que é inerente à criação de uma aula corretamente. Basicamente, o que se está dizendo é que "estou cansado de escrever todo esse clichê, então vou anotar todo o meu estado e deixar que o contêiner DI cuide de configurá-lo para mim".

É um ponto de vista perfeitamente válido. Mas também é uma solução alternativa para recursos de linguagem que, indiscutivelmente, não devem ser contornados. Além disso, por que parar aí? Tradicionalmente, o DI conta com cada classe tendo uma interface complementar. Por que não eliminar todas essas interfaces com anotações também?

Considere a alternativa (isso será C #, porque eu sei disso melhor, mas provavelmente há um equivalente exato em Java):

public class VeracodeService
{        
    private readonly IResultsAPIWrapper _resultsApiWrapper;
    private readonly IAdminAPIWrapper _adminApiWrapper;
    private readonly IUploadAPIWrapper _uploadApiWrapper;
    private readonly IMitigationAPIWrapper _mitigationApiWrapper;

    // Constructor
    public VeracodeService(IResultsAPIWrapper resultsApiWrapper, IAdminAPIWrapper adminApiWrapper, IUploadAPIWrapper uploadApiWrapper,         IMitigationAPIWrapper mitigationApiWrapper)
    {
         _resultsAPIWrapper = resultsAPIWrapper;
         _adminAPIWrapper = adminAPIWrapper;
         _uploadAPIWrapper = uploadAPIWrapper;
         _mitigationAPIWrapper = mitigationAPIWrapper;
    }
}

Já sei algumas coisas sobre essa aula. É uma classe imutável; estado só pode ser definido no construtor (referências, neste caso particular). E como tudo deriva de uma interface, posso trocar implementações no construtor, que é onde seus mocks aparecem.

Agora, tudo que meu contêiner de DI precisa fazer é refletir sobre o construtor para determinar quais objetos ele precisa para ser instalado. Mas essa reflexão está sendo feita em um membro público, de uma maneira de primeira classe; Ou seja, os metadados já fazem parte da classe, tendo sido declarados no construtor, um método cuja finalidade expressa é fornecer à classe as dependências necessárias.

Com certeza isso é muito padronizado, mas é assim que a linguagem foi projetada. As anotações parecem um truque sujo para algo que deveria ter sido incorporado à própria linguagem.

    
por 23.10.2015 / 21:11
fonte
25

O argumento de menos boiletplate de inicialização de teste é válido, mas há outras preocupações que devem ser levadas em consideração. Primeiro de tudo, você tem que responder uma pergunta:

Eu quero que minha classe seja instanciável apenas com reflexão?

Usar injeções de campo significa restringir a compatibilidade de uma classe a ambientes de injeção de dependência que instanciam objetos usando reflexão e suporte a esses anotações de injeção. Algumas plataformas baseadas na linguagem Java nem sequer suportam reflexão ( GWT ), portanto, as classes injetadas em campo não serão compatíveis com elas.

A segunda questão é o desempenho . Uma chamada de construtor (direta ou por reflexão) é sempre mais rápida que um grupo de atribuições de campo de reflexão. Estruturas de injeção de dependência devem usar a análise de reflexão para construir a árvore de dependência e criar construtores de reflexão. Esse processo causa um impacto adicional no desempenho.

O desempenho afeta a capacidade de manutenção . Se cada suíte de testes precisar ser executada em algum tipo de contêiner de injeção de dependência, uma execução de teste de alguns milhares de testes de unidade poderá durar dezenas de minutos. Dependendo do tamanho de uma base de código, isso pode ser um problema.

Isso tudo levanta muitas novas perguntas:

  1. Qual é a probabilidade de usar partes do código em outra plataforma?
  2. Quantos testes de unidade serão escritos?
  3. Com que rapidez preciso preparar cada nova versão?
  4. ...

Geralmente, quanto maior e mais importante um projeto, mais significativos são esses fatores. Além disso, em geral, gostaríamos de manter o código com alta compatibilidade, testabilidade e facilidade de manutenção. Do ponto de vista filosófico, a injeção de campo quebra o encapsulamento , que é um dos quatro fundamentos de programação orientada a objeto , que é o principal paradigma de Java.

Muitos, muitos argumentos contra injeção de campo.

    
por 24.10.2015 / 06:39
fonte
18

Injeção de campo recebe um voto "Não" definitivo de mim.

Como Robert Harvey, é um pouco automagia demais para o meu gosto. Eu prefiro o código explícito implícito, e tolero a indireção apenas como / quando ele fornece benefícios claros, pois torna o código mais difícil de entender e raciocinar.

Como Maciej Chałapuk, eu não gosto de precisar de reflexão ou de um framework DI / IoC para instanciar uma aula. É como dirigir a Roma para chegar a Paris de Amsterdã.

Lembre-se que eu adoro DI e todas as vantagens que ela traz. Apenas não tenho certeza sobre a necessidade dos frameworks para instanciar uma classe. Especialmente não o efeito que os contêineres IoC ou outras estruturas de DI podem ter no código de teste.

Eu gosto que meus testes sejam bem diretos. Eu não gosto de ter que passar pela indirecção de configurar um contêiner IoC ou outra estrutura de DI para que eles configurem minha classe em teste. Apenas parece estranho.

E isso faz com que meus testes dependam do estado global do framework. Isso quer dizer que não posso mais executar dois testes em paralelo que exijam que uma instância da classe X seja configurada com dependências diferentes.

Pelo menos com a injeção de construtor e setter, você tem a opção de não usar as estruturas e / ou reflexão em seus testes.

    
por 25.10.2015 / 12:23
fonte
4

Bem, isso parece uma discussão polêmica. A primeira coisa que precisa ser resolvida é que Injeção de campo é diferente da injeção Setter . Eu já trabalho com algumas pessoas que pensam que a injeção de Field e Setter é a mesma coisa.

Então, para ser claro:

Injeção de campo:

@Autowired
private SomeService someService;

Injeção de setter:

@Autowired
public void setSomeService(SomeService someService) {
    this.someService = someService;
}

Mas, para mim, eles compartilham as mesmas preocupações. E, meu favorito, a injeção de construtor:

@AutoWired
public MyService(SomeService someService) {
    this.someService = someService;
}

Eu tenho os seguintes motivos para acreditar que a injeção de construtor é melhor que a injeção setter / field (vou citar alguns links Spring para demonstrar o meu ponto):

  1. Evitar dependências circulares : o Spring é capaz de detectar dependências circulares entre os Beans, conforme explicado pelo @Tomasz. Veja também a Equipe de Primavera dizendo .
  2. Dependências da classe são óbvias : as dependências da classe são óbvias no construtor mas escondido com injeção de campo / setter. Eu sinto minhas aulas como uma "caixa preta" com injeção de campo / setter. E há (pela minha experiência) uma tendência para os desenvolvedores não se incomodarem com a classe com muitas dependências, isso é óbvio quando você tenta zombar da classe usando injeção de construtor (veja o próximo item). Um problema que incomoda os desenvolvedores é mais provável de ser resolvido. Além disso, uma turma com muitas dependências provavelmente viola o Princípio de Responsabilidade Única (SRP - Single Responsibility Principle).
  3. Fácil e confiável para zombar : comparado à injeção de campo , é mais fácil de zombar em um teste de unidade, porque você não precisa de qualquer simulação de contexto ou técnica de reflexão para alcançar o bean privado declarado dentro da classe e você não será enganado por ele . Você apenas instancia a classe e passa os feijões ridicularizados. Simples de entender e fácil.
  4. Ferramentas de qualidade de código podem ajudá-lo : Ferramentas como o Sonar podem avisá-lo sobre a complexidade da classe, porque uma classe com muitos parâmetros é provavelmente uma classe muito complexa e precisa de algum refatorador. Essas ferramentas não identificam o mesmo problema quando a classe está usando a injeção Field / Setter.
  5. Código melhor e independente : com menos @AutoWired espalhado entre seu código, seu código é menos dependente da estrutura. Eu sei que a possibilidade de mudar simples a estrutura de DI em um projeto não justifica isso (quem faz isso, certo?). Mas isso mostra, para mim, um código melhor (eu aprendi isso de uma maneira difícil com difícil EJB e pesquisas). E com o Spring você pode até remover a anotação @AutoWired usando injeção de construtor.
  6. Mais anotações nem sempre são a resposta certa : Para algumas reasons , eu realmente tento usar anotações apenas quando elas são realmente úteis.
  7. Você pode tornar as injeções imutáveis . A imutabilidade é um recurso muito bem-vindo .

Se você estiver procurando mais informações, eu recomendo este artigo antigo (mas ainda relevante) do Spring Blog nos dizendo por que eles usam tanto injetor setter e a recomendação de usar injeção de construtor:

setter injection is used a lot more often than you would expect, is the fact that frameworks like Spring in general, are much more suited to be configured by setter injection than by constructor injection

E esta nota sobre por que eles acreditam que o construtor é mais adequado para o código do aplicativo:

I think constructor injection is much more usable for application code than it is for framework code. In application code, you inherently have a lesser need for optional values that you need to configure

Considerações finais

Se você não está realmente certo sobre a vantagem do construtor, talvez a idéia de misturar setter (não campo) com injeção de construtor seja uma opção, como explicado pelo Equipe da Primavera :

Since you can mix both, Constructor- and Setter-based DI, it is a good rule of thumb to use constructor arguments for mandatory dependencies and setters for optional dependencies

Mas lembre-se que a injeção de campo é, provavelmente, a que deve ser evitada entre os três.

    
por 19.02.2018 / 19:26
fonte
-5

É provável que a sua dependência mude durante o tempo de vida da aula? Em caso afirmativo, use injeção de campo / propriedade. Caso contrário, use injeção de construtor.

    
por 23.10.2015 / 21:15
fonte