Estou lendo sobre injeção de dependência (DI). Para mim, é uma coisa muito complicada de fazer, pois eu estava lendo referenciando inversão de controle (IoC) e, por isso, senti que iria fazer uma jornada.
Este é o meu entendimento: em vez de criar um modelo na classe que também o consome, você passa (injeta) o modelo (já preenchido com propriedades interessantes) para onde é necessário (para uma nova classe que pode ser usada como parâmetro em o construtor).
Para mim, isso é apenas uma discussão. Devo ter saudades de entender o ponto? Talvez se torne mais óbvio com projetos maiores?
Meu entendimento é não-DI (usando pseudo-código):
public void Start()
{
MyClass class = new MyClass();
}
...
public MyClass()
{
this.MyInterface = new MyInterface();
}
E DI seria
public void Start()
{
MyInterface myInterface = new MyInterface();
MyClass class = new MyClass(myInterface);
}
...
public MyClass(MyInterface myInterface)
{
this.MyInterface = myInterface;
}
Alguém poderia lançar alguma luz, pois tenho certeza de que estou numa confusão aqui.
dependency-injection
MyDaftQuestions
fonte
fonte
Respostas:
Bem, sim, você injeta suas dependências, através de um construtor ou através de propriedades.
Uma das razões para isso é não sobrecarregar o MyClass com os detalhes de como uma instância do MyInterface precisa ser construída. MyInterface pode ser algo que possui uma lista inteira de dependências por si só, e o código do MyClass se tornaria feio muito rápido se você instanciasse todas as dependências do MyInterface dentro do MyClass.
Outra razão é para testar.
Se você possui uma dependência de uma interface do leitor de arquivos e a injeta por meio de um construtor, digamos, ConsumerClass, isso significa que durante o teste, você pode passar uma implementação em memória do leitor de arquivos para o ConsumerClass, evitando a necessidade de faça E / S durante o teste.
fonte
Como a DI pode ser implementada depende muito do idioma usado.
Aqui está um exemplo simples não-DI:
Isso é péssimo, por exemplo, quando para um teste eu quero usar um objeto simulado
bar
. Portanto, podemos tornar isso mais flexível e permitir que as instâncias sejam transmitidas pelo construtor:Essa já é a forma mais simples de injeção de dependência. Mas isso ainda é péssimo porque tudo precisa ser feito manualmente (também porque o chamador pode manter uma referência a nossos objetos internos e, assim, invalidar nosso estado).
Podemos introduzir mais abstração usando fábricas:
Uma opção é
Foo
gerar uma fábrica abstrataOutra opção é passar uma fábrica para o construtor:
Os problemas restantes com isso são que precisamos escrever uma nova
DependencyManager
subclasse para cada configuração e que o número de dependências que podem ser gerenciadas é bastante restrito (cada nova dependência precisa de um novo método na interface).Com recursos como reflexão e carregamento dinâmico de classe, podemos contornar isso. Mas isso depende muito do idioma usado. No Perl, as classes podem ser referenciadas por seus nomes, e eu poderia fazer
Em linguagens como Java, eu poderia fazer com que o gerenciador de dependências se comportasse de maneira semelhante a
Map<Class, Object>
:Para qual classe real a
Bar.class
solução é resolvida pode ser configurada em tempo de execução, por exemplo, mantendo umaMap<Class, Class>
interface que mapeia as implementações.Ainda há um elemento manual envolvido na escrita do construtor. Mas podemos tornar o construtor completamente desnecessário, por exemplo, acionando o DI por meio de anotações:
Aqui está um exemplo de implementação de uma pequena estrutura DI (e muito restrita): http://ideone.com/b2ubuF , embora essa implementação seja completamente inutilizável para objetos imutáveis (essa implementação ingênua não pode aceitar parâmetros para o construtor).
fonte
Nossos amigos no Stack Overflow têm uma boa resposta para isso . O meu favorito é a segunda resposta, incluindo a citação:
do blog de James Shore . Obviamente, existem versões / padrões mais complexos em camadas, mas é o suficiente para entender basicamente o que está acontecendo. Em vez de um objeto criar suas próprias variáveis de instância, elas são passadas de fora.
fonte
Digamos que você esteja fazendo o design de interiores da sua sala de estar: instale um lustre sofisticado no teto e conecte uma luminária de pé elegante. Um ano depois, sua adorável esposa decide que gostaria que o quarto fosse um pouco menos formal. Qual luminária será mais fácil de trocar?
A idéia por trás do DI é usar a abordagem "plug-in" em qualquer lugar. Isso pode não parecer grande coisa se você mora em uma cabana simples, sem drywall e com todos os fios elétricos expostos. (Você é um trabalhador braçal - pode mudar qualquer coisa!) E da mesma forma, em um aplicativo pequeno e simples, o DI pode adicionar mais complicações do que vale a pena.
Mas para uma aplicação grande e complexa, o DI é indispensável para manutenção a longo prazo. Ele permite que você "desconecte" módulos inteiros do seu código (o back-end do banco de dados, por exemplo) e os troque por módulos diferentes que realizam a mesma coisa, mas que o fazem de uma maneira totalmente diferente (um sistema de armazenamento em nuvem, por exemplo).
BTW, uma discussão sobre DI seria incompleta sem uma recomendação de Injection de Dependência no .NET , por Mark Seeman. Se você conhece o .NET e pretende se envolver no desenvolvimento de SW em larga escala, essa é uma leitura essencial. Ele explica o conceito muito melhor do que eu.
Deixe-me deixar uma última característica essencial do DI que seu exemplo de código ignora. O DI permite que você aproveite o vasto potencial de flexibilidade que está encapsulado no ditado " Programa para interfaces ". Para modificar um pouco o seu exemplo:
Mas AGORA, como
MyRoom
é projetado para a interface ILightFixture , eu posso facilmente entrar no próximo ano e alterar uma linha na função Principal ("injetar" uma "dependência" diferente)) e obter instantaneamente novas funcionalidades no MyRoom (sem precisar reconstrua ou reimplemente o MyRoom, se houver uma biblioteca separada, assumindo, é claro, que todas as suas implementações do ILightFixture foram projetadas com a Substituição Liskov em mente). Tudo, com apenas uma mudança simples:Além disso, agora posso testar a unidade
MyRoom
(você está testando a unidade, certo?) E usar uma luminária "simulada", que me permite testarMyRoom
totalmente independente daClassyChandelier.
Existem muitas outras vantagens, é claro, mas essas são as que venderam a ideia para mim.
fonte
A injeção de dependência está apenas passando um parâmetro, mas isso ainda leva a alguns problemas, e o nome deve se concentrar um pouco no por que a diferença entre criar um objeto e passar um é importante.
É importante porque (obviamente) qualquer objeto de tempo A chama o tipo B, A depende de como B funciona. O que significa que, se A tomar a decisão específica de qual tipo concreto de B ele usará, será perdida muita flexibilidade na maneira como A pode ser usado por classes externas, sem modificar A.
Menciono isso porque você fala sobre "perder o objetivo" da injeção de dependência, e esse é o principal motivo pelo qual as pessoas gostam de fazer isso. Pode significar apenas passar um parâmetro, mas se você fizer isso, pode ser importante.
Além disso, algumas das dificuldades na implementação efetiva de DI aparecem melhor em grandes projetos. Você geralmente moverá a decisão real de quais classes concretas usar até o topo, para que seu método inicial possa parecer:
mas com outras 400 linhas desse tipo de código fascinante. Se você pensa em manter isso com o tempo, o apelo dos contêineres DI se torna mais aparente.
Imagine também uma classe que, digamos, implementa IDisposable. Se as classes que o utilizam o obtêm por injeção, em vez de criá-lo, como eles sabem quando chamar Dispose ()? (Esse é outro problema que um contêiner de DI lida com.)
Portanto, com certeza, a injeção de dependência está apenas passando um parâmetro, mas, na verdade, quando as pessoas dizem que a injeção de dependência significa "passar um parâmetro para seu objeto versus a alternativa de fazer com que seu objeto instancia algo" - e lidar com todos os benefícios das desvantagens de tal abordagem, possivelmente com a ajuda de um contêiner DI.
fonte
No nível da turma, é fácil.
'Injeção de dependência' simplesmente responde à pergunta "como vou encontrar meus colaboradores" com "eles são empurrados contra você - você não precisa ir buscá-los". (É semelhante a - mas não é o mesmo que - 'Inversão de controle', onde a pergunta "como ordenarei minhas operações sobre minhas entradas?" Tem uma resposta semelhante).
O único benefício que seus colaboradores pressionam é que permite que o código do cliente use sua classe para compor um gráfico de objeto que atenda às suas necessidades atuais ... você não determinou arbitrariamente a forma e a mutabilidade do gráfico ao decidir em particular os tipos concretos e o ciclo de vida de seus colaboradores.
(Todos esses outros benefícios, de testabilidade, de acoplamento flexível, etc., decorrem amplamente do uso de interfaces e não tanto da dependência-injeção-ness, embora o DI promova naturalmente o uso de interfaces).
Vale a pena notar que, se você evitar instanciar seus próprios colaboradores, sua classe deverá, portanto, obter seus colaboradores de um construtor, uma propriedade ou um argumento de método (essa última opção geralmente é ignorada, a propósito ... nem sempre faz sentido que os colaboradores de uma classe façam parte de seu 'estado').
E isso é uma coisa boa.
No nível do aplicativo ...
Tanto para a visão por classe das coisas. Digamos que você tenha várias classes que seguem a regra "não instancia seus próprios colaboradores" e deseja fazer uma aplicação a partir delas. A coisa mais simples a fazer é usar um bom código antigo (uma ferramenta realmente útil para invocar construtores, propriedades e métodos!) Para compor o gráfico de objetos que você deseja e lançar alguma entrada nele. (Sim, alguns desses objetos em seu gráfico serão fábricas de objetos, que foram passadas como colaboradores para outros objetos de longa duração no gráfico, prontos para o serviço ... você não pode pré-construir cada objeto! )
... sua necessidade de 'flexivelmente' configurar o gráfico de objetos do seu aplicativo ...
Dependendo dos seus outros objetivos (não relacionados ao código), convém dar ao usuário final algum controle sobre o gráfico de objetos assim implantado. Isso o leva na direção de um esquema de configuração, de um tipo ou de outro, seja um arquivo de texto de seu próprio design, com alguns pares de nome / valor, um arquivo XML, uma DSL personalizada, uma linguagem declarativa de descrição gráfica, como YAML, uma linguagem de script imperativa, como JavaScript, ou outra coisa apropriada para a tarefa em questão. O que for necessário para compor um gráfico de objeto válido, de maneira a atender às necessidades de seus usuários.
... pode ser uma força de projeto significativa.
Na circunstância mais extrema desse tipo, você pode optar por adotar uma abordagem muito geral e fornecer aos usuários finais um mecanismo geral para 'conectar' o gráfico de objetos de sua escolha e até permitir que eles forneçam realizações concretas de interfaces para o tempo de execução! (Sua documentação é uma jóia brilhante, seus usuários são muito inteligentes, familiarizados com pelo menos o contorno geral do gráfico de objetos do seu aplicativo, mas não têm um compilador à mão). Teoricamente, esse cenário pode ocorrer em algumas situações da empresa.
Nesse caso, você provavelmente possui uma linguagem declarativa que permite que seus usuários expressem qualquer tipo, a composição de um gráfico de objetos desses tipos e uma paleta de interfaces com as quais o mítico usuário final pode misturar e combinar. Para reduzir a carga cognitiva de seus usuários, você prefere uma abordagem de 'configuração por convenção', para que eles só precisem intervir e substituir o fragmento de objeto-gráfico-interesse, em vez de lutar com a coisa toda.
Seu pobre idiota!
Como você não gostava de escrever tudo isso sozinho (mas sério, confira uma ligação YAML para o seu idioma), você está usando algum tipo de estrutura DI.
Dependendo da maturidade dessa estrutura, você pode não ter a opção de usar injeção de construtor, mesmo quando faz sentido (os colaboradores não mudam durante a vida útil de um objeto), forçando-o a usar a Injeção de Setter (mesmo quando os colaboradores não mudam durante o tempo de vida de um objeto, e mesmo quando não há realmente uma razão lógica pela qual todas as implementações concretas de uma interface devem ter colaboradores de um tipo específico). Nesse caso, você está atualmente em um inferno de fortes acoplamentos, apesar de ter diligentemente 'usado interfaces' em toda a sua base de código - horror!
Felizmente, você usou uma estrutura de DI que oferece a opção de injeção de construtor, e seus usuários ficam um pouco irritados com você por não gastar mais tempo pensando nas coisas específicas que eles precisavam para configurar e fornecendo a eles uma interface do usuário mais adequada para a tarefa à mão. (Apesar de ser justo, você provavelmente tentou pensar em uma maneira, mas o JavaEE decepcionou e você teve que recorrer a esse truque horrível).
Nota de inicialização
Em nenhum momento você está usando o Google Guice, que fornece ao codificador uma maneira de dispensar a tarefa de compor um gráfico de objetos com código ... escrevendo código. Argh!
fonte
Muitas pessoas disseram aqui que o DI é um mecanismo para terceirizar a inicialização de uma variável de instância.
Para exemplos óbvios e simples, você realmente não precisa de "injeção de dependência". Você pode fazer isso com um simples parâmetro passando para o construtor, conforme observado em questão.
No entanto, acho que um mecanismo dedicado de "injeção de dependência" é útil quando você fala sobre recursos no nível do aplicativo, onde o chamador e o destinatário não sabem nada sobre esses recursos (e não querem saber!).
Por exemplo: conexão com o banco de dados, contexto de segurança, recurso de log, arquivo de configuração etc.
Além disso, em geral todo singleton é um bom candidato para DI. Quanto mais você tiver e usar, mais chances precisará de DI.
Para esses tipos de recursos, é bastante claro que não faz sentido movê-los por meio de listas de parâmetros e, portanto, o DI foi inventado.
Última observação, você também precisa de um contêiner DI, que fará a injeção real ... (novamente, para liberar o chamador e o receptor dos detalhes da implementação).
fonte
Se você passar um tipo de referência abstrata, o código de chamada poderá passar qualquer objeto que estenda / implemente o tipo abstrato. Portanto, no futuro, a classe que usa o objeto pode usar um novo objeto que foi criado sem nunca modificar seu código.
fonte
Essa é outra opção, além da resposta de amon
Use Construtores:
É isso que eu uso para dependências. Eu não uso nenhuma estrutura DI. Eles atrapalham.
fonte