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


62

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

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

No entanto, recentemente entrei em um debate com o autor do JMockit - uma estrutura de zombaria - na qual ele vê a injeção de construtores e setters como uma prática ruim 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 o injetor de construtor da injeção de campo nos últimos dois anos, acho muito mais claro o uso, mas estou pensando se devo reexaminar meu ponto de vista. O autor de JMockit (Rogério Liesenfeld) é claramente bem versado em DI, então me sinto obrigado a revisar minha abordagem, pois ele se sente tão fortemente contra a injeção de construtor / setter.



2
Se você não tem permissão para usar injeção de construtor ou injeção de setter, como deve entregar suas dependências para a classe? Talvez seja melhor você se vincular ao debate, a menos que sua conversa com o cara da Mockit seja privada.
Robert Harvey

2
@DavidArno: Em uma classe imutável, o estado é definido uma vez ... no construtor.
Robert Harvey

11
@ Davidk o que você está descrevendo é o modelo de domínio anêmico. Certamente tem proponentes, mas não é mais realmente orientado a objetos; onde os dados e as funções que atuam nesses dados vivem juntos.
Richard Tingle

3
@ David Não há nada de errado com a programação funcional. Mas o java é fundamentalmente projetado para ser orientado a objetos e você estará constantemente combatendo e acabará com o modelo de domínio anêmico, se você não for muito cuidadoso. Não há nada errado com a função orientada código, mas uma ferramenta adequada deve ser usado para ele, que java não (embora lambda de ter adicionado elementos de programação baseado em função) é
Richard Tingle

Respostas:


48

A injeção em campo é um pouco "ação assustadora à distância" para o meu gosto.

Considere o exemplo que você forneceu na sua postagem dos 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 "eu tenho essa classe com estado privado, ao qual anexei @injectableanotações, o que significa que o estado pode ser automagicamente preenchido por algum agente de fora, mesmo que meu estado tenha sido declarado privado. "

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

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

Considere a alternativa (isso será C #, porque eu o conheço 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; O estado pode ser definido apenas no construtor (referências, neste caso em particular). E como tudo deriva de uma interface, eu posso trocar as implementações no construtor, que é onde suas zombarias entram.

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

Concedido isso é muito clichê, mas é assim que a linguagem foi projetada. As anotações parecem um truque sujo para algo que deveria ter sido incorporado ao próprio idioma.


A menos que eu leia sua postagem incorretamente, você não está realmente vendo o exemplo corretamente. Minha implementação de VeracodeServiceé quase idêntica à que você escreveu (embora em Java vs C #). Na VeracodeServiceImplTestverdade, é uma classe de Teste de Unidade. Os @Injectablecampos são essencialmente objetos simulados sendo inseridos no contexto. O @Testedcampo é o objeto / classe com o DI Constructor definido. Concordo com o seu ponto de vista que prefere a injeção do Constructor à injeção de campo. No entanto, como já referi, o autor JMockit sente o oposto e eu estou tentando entender por que
Eric B.

Se você olhar para o primeiro post dessa discussão, verá que tenho quase exatamente a mesma definição na minha classe Repo.
Eric B.

Se você está falando apenas de classes de teste, isso pode não ter importância. Porque, teste classes.
Robert Harvey

Não - não estou falando de aulas de teste. Eu estou falando sobre aulas de produção. Acontece que o exemplo no grupo de discussão é um teste de unidade, pois a estrutura é uma estrutura de simulação / teste. (Toda a discussão gira em torno de se a estrutura de zombaria deve suportar a injeção de construtores)
Eric B. Eric

3
+1: Sim, não há mágica na versão C #. É uma classe padrão, tem dependências, todas são injetadas em um ponto, antes da classe ser usada. A classe pode ser usada com um contêiner de DI ou não. Nada na classe diz IOC ou estrutura "Injeção Automática de Dependência".
Preocupante binário 30/10

27

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

Quero que minha turma seja instanciada 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 suportam essas anotações de injeção específicas. Algumas plataformas baseadas na linguagem Java nem suportam reflexão ( GWT ), portanto, a classe injetada em campo não será compatível com elas.

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

O desempenho afeta a manutenção . Se cada conjunto de testes precisar ser executado 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 questões novas:

  1. Qual é a probabilidade de usar partes do código em outra plataforma?
  2. Quantos testes de unidade serão gravados?
  3. Com que rapidez eu preciso preparar cada novo lançamento?
  4. ...

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

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


3
+1 Você atinge um nervo. Não gosto de precisar de reflexão ou de uma estrutura de DI / IoC para instanciar uma classe. É como dirigir para Roma para chegar a Paris de Amsterdã. Lembre-se, eu amo DI. Só não tenho certeza sobre as estruturas.
Marjan Venema

11
Ótimos argumentos. Ele verbaliza muitos dos pensamentos que tive, mas estou feliz em ver os outros se sentindo da mesma maneira. Por mais incrível que eu encontrasse a injeção em campo, acho que adorei muito simplesmente porque era "mais fácil". Acho a injeção de construtores muito mais clara agora.
Eric B.

19

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

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

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

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

Eu gosto que meus testes sejam muito diretos. Não gosto de passar pelo indireto de configurar contêineres de IoC ou outra estrutura de DI para que eles configurem minha classe em teste. Parece estranho.

E isso torna meus testes dependentes do estado global da estrutura. Ou seja, não posso mais executar dois testes em paralelo que exigem que uma instância da classe X seja configurada com dependências diferentes.

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


Se você usa, por exemplo, o Mockito mockito.org , não precisa de uma estrutura de DI / IoC, mesmo ao usar a injeção de campo, e pode executar o teste em paralelo e assim por diante.
Hans-Peter Störr 27/10/2015

11
@hstoerr Nice. No entanto, isso deve significar que Mockito está usando reflexão (ou qualquer que seja o equivalente de Java do que é chamado) ...
Marjan Venema

5

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

Então, para ficar claro:

Injeção em campo:

@Autowired
private SomeService someService;

Injeção de incubadora:

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

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

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

Eu tenho as seguintes razões para acreditar que a injeção do construtor é melhor do que a injeção do setter / field (citarei alguns links do Spring para demonstrar meu argumento):

  1. Impedir dependências circulares : o Spring é capaz de detectar dependências circulares entre os Beans, como o @Tomasz explicou. Veja também a Equipe da Primavera dizendo isso.
  2. As dependências da classe são óbvias : as dependências da classe são óbvias no construtor, mas ocultas com a injeção de campo / setter. 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 preocuparem 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 provavelmente será resolvido. Além disso, uma classe com muitas dependências provavelmente viola o Princípio de Responsabilidade Única (SRP).
  3. Fácil e confiável de zombar : comparado à injeção de campo , é mais fácil zombar em um teste de unidade, porque você não precisa de nenhuma técnica de simulação ou reflexão de contexto para alcançar o Bean privado declarado dentro da classe e não será enganado por ele . Você apenas instancia a classe e passa os grãos de zombaria. Simples de entender e fácil.
  4. As ferramentas de qualidade de código podem ajudá-lo : Ferramentas como o Sonar podem alertá-lo sobre a classe ficar muito complexa, porque uma classe com muitos parâmetros provavelmente é uma classe muito complexa e precisa de algum refator. Essas ferramentas não podem identificar o mesmo problema quando a classe está usando a injeção de campo / setter.
  5. Código melhor e independente : com menos @AutoWiredpropagação entre seu código, seu código depende menos da estrutura. Eu sei que a possibilidade de alterar simplesmente 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 maneira difícil com o EJB e as pesquisas). E com o Spring você pode até remover a @AutoWiredanotação usando injeção de construtor.
  6. Mais anotações nem sempre são a resposta certa : por alguns motivos , 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, recomendo este artigo antigo (mas ainda relevante) do Spring Blog nos dizendo por que eles usam tanta injeção de setter e a recomendação para usar a injeção de construtor:

A injeção de setter é usada com muito mais frequência do que você esperaria, é o fato de que estruturas como o Spring em geral são muito mais adequadas para serem configuradas por injeção de setter do que por injeção de construtor

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

Eu acho que a injeção de construtor é muito mais útil para o código do aplicativo do que para o código da estrutura. No código do aplicativo, você tem uma necessidade menor de valores opcionais que você precisa configurar

Pensamentos finais

Se você não tem certeza da vantagem do construtor, talvez a idéia de misturar o setter (não o campo) com a injeção do construtor seja uma opção, conforme explicado pela equipe do Spring :

Como você pode misturar DI, com base em construtor e setter, é uma boa regra usar argumentos de construtor para dependências obrigatórias e setters para dependências opcionais

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


-5

É provável que sua dependência mude durante a vida útil da classe? Nesse caso, use injeção de campo / propriedade. Caso contrário, use injeção de construtor.


2
Isso realmente não explica nada. Por que injetar campos particulares quando você pode fazer isso da maneira correta?
Robert Harvey

Onde diz algo sobre campos particulares? Eu estou falando sobre a interface pública de uma classe /
Darren Young

Não há argumento sobre injeção de método x injeção de construtor nesta questão. O autor do JMockit vê os dois como ruins. Veja o título da pergunta.
Robert Harvey

A pergunta não diz injeção de campo versus injeção de construtor?
Darren Young

11
A injeção em campo é um animal completamente diferente. Você está colocando valores em membros privados .
Robert Harvey
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.