Estruturas de injeção de dependência como o Google Guice fornecem a seguinte motivação para seu uso ( fonte ):
Para construir um objeto, você primeiro cria suas dependências. Mas para criar cada dependência, você precisa de suas dependências e assim por diante. Portanto, quando você cria um objeto, você realmente precisa criar um gráfico de objetos.
Construir gráficos de objetos manualmente exige muito trabalho (...) e dificulta o teste.
Mas não compreendo este argumento: mesmo sem uma estrutura de injeção de dependência, posso escrever classes que são fáceis de instanciar e convenientes para testar. Por exemplo, o exemplo da página de motivação do Guice pode ser reescrito da seguinte maneira:
class BillingService
{
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
// constructor for tests, taking all collaborators as parameters
BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
{
this.processor = processor;
this.transactionLog = transactionLog;
}
// constructor for production, calling the (productive) constructors of the collaborators
public BillingService()
{
this(new PaypalCreditCardProcessor(), new DatabaseTransactionLog());
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard)
{
...
}
}
Portanto, pode haver outros argumentos para estruturas de injeção de dependência ( que estão fora do escopo desta pergunta !), Mas a criação fácil de gráficos de objetos testáveis não é uma delas, é?
new ShippingService()
.Respostas:
Há um velho e velho debate em andamento sobre a melhor maneira de fazer a injeção de dependência.
O corte original da mola instanciava um objeto simples e, em seguida, injetava dependências através dos métodos setter.
Mas então uma grande contingência de pessoas insistiu que injetar dependências através de parâmetros de construtores era a maneira correta de fazê-lo.
Então, ultimamente, à medida que o uso da reflexão se tornou mais comum, definir os valores de membros privados diretamente, sem setters ou construtores args, tornou-se a raiva.
Portanto, seu primeiro construtor é consistente com a segunda abordagem para injeção de dependência. Ele permite que você faça coisas legais, como injetar zombarias para testes.
Mas o construtor sem argumento tem esse problema. Como instancia as classes de implementação para
PaypalCreditCardProcessor
eDatabaseTransactionLog
, cria uma dependência difícil em tempo de compilação no PayPal e no banco de dados. É responsável por criar e configurar toda a árvore de dependências corretamente.Imagine que o processador PayPay é um subsistema realmente complicado e, além disso, atrai muitas bibliotecas de suporte. Ao criar uma dependência em tempo de compilação nessa classe de implementação, você está criando um link inquebrável para toda a árvore de dependências. A complexidade do seu gráfico de objetos acabou de aumentar por uma ordem de magnitude, talvez duas.
Muitos desses itens na árvore de dependência serão transparentes, mas muitos deles também precisarão ser instanciados. As probabilidades são de que você não poderá instanciar apenas a
PaypalCreditCardProcessor
.Além da instanciação, cada um dos objetos precisará de propriedades aplicadas a partir da configuração.
Se você tiver apenas uma dependência da interface e permitir que uma fábrica externa crie e injete a dependência, eles cortam toda a árvore de dependências do PayPal, e a complexidade do seu código para na interface.
Existem outros benefícios, como especificar classes de implementações em configuração (ou seja, em tempo de execução em vez de tempo de compilação) ou ter uma especificação de dependência mais dinâmica que varia, por exemplo, por ambiente (teste, integração, produção).
Por exemplo, digamos que o PayPalProcessor tenha três objetos dependentes e cada uma dessas dependências tenha mais dois. E todos esses objetos precisam extrair propriedades da configuração. O código como está assumirá a responsabilidade de criar tudo isso, definir propriedades a partir da configuração, etc. etc. - todas as preocupações que a estrutura de DI resolverá.
Pode não parecer óbvio a princípio do que você está se protegendo usando uma estrutura de DI, mas isso aumenta e se torna dolorosamente óbvio ao longo do tempo. (lol, falo da experiência de ter tentado fazê-lo da maneira mais difícil)
...
Na prática, mesmo para um programa realmente minúsculo, acho que acabo escrevendo no estilo DI e divido as classes em pares de implementação / fábrica. Ou seja, se eu não estiver usando uma estrutura de DI como o Spring, apenas reunirei algumas classes simples de fábrica.
Isso fornece a separação de preocupações, para que minha classe possa fazer suas coisas, e a classe factory assume a responsabilidade de criar e configurar coisas.
Não é uma abordagem necessária, mas o FWIW
...
De maneira mais geral, o padrão DI / interface reduz a complexidade do seu código fazendo duas coisas:
abstraindo dependências downstream em interfaces
"levantando" dependências upstream do seu código e para algum tipo de contêiner
Além disso, como a instanciação e configuração de objetos é uma tarefa bastante familiar, a estrutura de DI pode alcançar muitas economias de escala através de notação padronizada e usando truques como reflexão. A dispersão dessas mesmas preocupações nas aulas acaba adicionando muito mais confusão do que se poderia pensar.
fonte
Ao vincular seu gráfico de objeto no início do processo, ou seja, conectá-lo ao código, você exige que o contrato e a implementação estejam presentes. Se alguém (talvez até você) quiser usar esse código para uma finalidade um pouco diferente, precisará recalcular todo o gráfico do objeto e reimplementá-lo.
As estruturas de DI permitem pegar vários componentes e conectá-los em tempo de execução. Isso torna o sistema "modular", composto por vários módulos que funcionam nas interfaces um do outro, e não nas implementações um do outro.
fonte
A abordagem "instanciar meus próprios colaboradores" pode funcionar para árvores de dependência , mas certamente não funcionará bem para gráficos de dependência que são gráficos acíclicos direcionados gerais (DAG). Em um DAG de dependência, vários nós podem apontar para o mesmo nó - o que significa que dois objetos usam o mesmo objeto que o colaborador. De fato, este caso não pode ser construído com a abordagem descrita na pergunta.
Se alguns dos meus colaboradores (ou colaboradores) colaborarem com um determinado objeto, seria necessário instanciar esse objeto e passá-lo aos meus colaboradores. Então, de fato, eu precisaria saber mais do que meus colaboradores diretos, e isso obviamente não aumenta.
fonte
UnitOfWork
que possuiDbContext
repositórios únicos e múltiplos. Todos esses repositórios devem usar o mesmoDbContext
objeto. Com a proposta de "auto-instanciação" do OP, isso se torna impossível.Não usei o Google Guice, mas levei muito tempo migrando aplicativos antigos de camada N herdados em arquiteturas .Net para IoC, como Onion Layer, que dependem da injeção de dependência para dissociar as coisas.
Por que injeção de dependência?
O objetivo da Injeção de Dependência não é, na verdade, a testabilidade, é realmente aceitar aplicativos fortemente acoplados e afrouxar o acoplamento o máximo possível. (O que é o produto desejável de tornar seu código muito mais fácil de se adaptar para testes de unidade adequados)
Por que eu deveria me preocupar com o acoplamento?
O acoplamento ou dependências restritas podem ser uma coisa muito perigosa. (Especialmente em idiomas compilados) Nesses casos, você pode ter uma biblioteca, dll etc. muito raramente usada e com um problema que efetivamente coloca todo o aplicativo offline. (Todo o seu aplicativo morre porque um problema sem importância tem um problema ... isso é ruim ... REALMENTE ruim) Agora, quando você dissocia coisas, pode realmente configurar o aplicativo para que ele possa ser executado mesmo que a DLL ou a Biblioteca esteja faltando completamente! Certifique-se de que uma peça que precise dessa biblioteca ou DLL não funcione, mas o restante do aplicativo fica feliz como pode.
Por que preciso da injeção de dependência para testes adequados
Na verdade, você só quer um código fracamente acoplado, a injeção de dependência apenas permite isso. Você pode unir coisas sem IoC, mas normalmente é mais trabalhoso e menos adaptável (tenho certeza de que alguém por aí tem uma exceção)
No caso que você deu, acho que seria muito mais fácil configurar a injeção de dependência para que eu possa zombar do código que não estou interessado em contar como parte deste teste. Apenas diga o método "Ei, eu sei, eu disse para você ligar para o repositório, mas aqui estão os dados que" devem "retornar piscadelas " agora, porque esses dados nunca mudam, você sabe que você está testando apenas a parte que usa esses dados, não o recuperação real dos dados.
Basicamente, ao testar, você deseja Testes de Integração (funcionalidade) que testam uma parte da funcionalidade do início ao fim e testes de unidade completos que testam cada parte do código (geralmente no nível do método ou da função) de forma independente.
A idéia é que você deseja garantir que toda a funcionalidade esteja funcionando, caso contrário, você deseja saber a parte exata do código que não está funcionando.
Isso PODE ser feito sem injeção de dependência, mas geralmente à medida que o projeto cresce, torna-se cada vez mais complicado fazê-lo sem a injeção de dependência em vigor. (SEMPRE presuma que seu projeto crescerá! É melhor ter uma prática desnecessária de habilidades úteis do que encontrar um projeto acelerando rapidamente e requer séria refatoração e reengenharia depois que as coisas já estão decolando.)
fonte
Como mencionei em outra resposta , o problema aqui é que você deseja que a classe
A
dependa de alguma classeB
sem codificar qual classeB
é usada no código-fonte A. Isso é impossível em Java e C # porque a única maneira de importar uma classe é referenciá-lo por um nome globalmente exclusivo.Usando uma interface, você pode contornar a dependência de classe codificada, mas ainda precisa colocar uma mão na instância da interface e não pode chamar construtores, ou está de volta no quadrado 1. Então, agora, codifique que de outra forma poderia criar suas dependências, transfere essa responsabilidade para outra pessoa. E suas dependências estão fazendo a mesma coisa. Portanto, agora toda vez que você precisa de uma instância de uma classe, acaba criando manualmente toda a árvore de dependências, enquanto no caso em que a classe A depende diretamente de B, você pode simplesmente chamar
new A()
e fazer essa chamada de construtornew B()
, e assim por diante.Uma estrutura de injeção de dependência tenta contornar isso, permitindo que você especifique os mapeamentos entre classes e construa a árvore de dependência para você. O problema é que, quando você estraga os mapeamentos, você descobre em tempo de execução, não em tempo de compilação, como faria em idiomas que suportam módulos de mapeamento como um conceito de primeira classe.
fonte
Eu acho que isso é um grande mal-entendido aqui.
Guice é uma estrutura de injeção de dependência . Isso torna o DI automático . O argumento que eles mencionaram no trecho que você citou é sobre o Guice poder remover a necessidade de criar manualmente o "construtor testável" que você apresentou no seu exemplo. Não tem absolutamente nada a ver com a injeção de dependência em si.
Este construtor:
já usa injeção de dependência. Você basicamente disse que usar DI é fácil.
O problema que o Guice resolve é que, para usar esse construtor, agora você deve ter um código de construtor de gráfico de objeto em algum lugar , passando manualmente os objetos já instanciados como argumentos desse construtor. O Guice permite que você tenha um único local onde é possível configurar quais classes de implementação reais correspondem a essas
CreditCardProcessor
eTransactionLog
interfaces. Após essa configuração, toda vez que você criarBillingService
usando o Guice, essas classes serão passadas para o construtor automaticamente.É isso que a estrutura de injeção de dependência faz. Mas o próprio construtor que você apresentou já é uma implementação do princípio de injeção de dependência . Os contêineres IoC e as estruturas DI são meios para automatizar os princípios correspondentes, mas não há nada que o impeça de fazer tudo manualmente, esse era o ponto.
fonte