Estou tentando entender as injeções de dependência (DI) e, mais uma vez, falhei. Parece bobo. Meu código nunca é uma bagunça; Eu quase não escrevo funções e interfaces virtuais (embora faça uma vez na lua azul) e toda a minha configuração seja serializada magicamente em uma classe usando json.net (às vezes usando um serializador XML).
Não entendo bem o problema que resolve. Parece uma maneira de dizer: "oi. Ao executar essa função, retorne um objeto desse tipo e use esses parâmetros / dados".
Mas ... por que eu usaria isso? Note que nunca precisei usá-lo object
também, mas entendo para que serve isso.
Quais são algumas situações reais na criação de um site ou aplicativo de desktop em que alguém usaria DI? Posso apresentar casos facilmente por que alguém pode querer usar interfaces / funções virtuais em um jogo, mas é extremamente raro (raro o suficiente para não me lembrar de uma única instância) usá-lo em códigos que não são de jogos.
fonte
Respostas:
Primeiro, quero explicar uma suposição que faço para essa resposta. Nem sempre é verdade, mas muitas vezes:
(Na verdade, existem interfaces que também são substantivos, mas quero generalizar aqui.)
Então, por exemplo, uma interface pode ser algo como
IDisposable
,IEnumerable
ouIPrintable
. Uma classe é uma implementação real de uma ou mais dessas interfaces:List
ouMap
ambas podem ser implementações deIEnumerable
.Para entender a questão: freqüentemente suas aulas dependem uma da outra. Por exemplo, você pode ter uma
Database
classe que acessa seu banco de dados (hah, surpresa! ;-)), mas você também deseja que essa classe faça log de acesso ao banco de dados. Suponha que você tenha outra classeLogger
, e entãoDatabase
tenha uma dependênciaLogger
.Por enquanto, tudo bem.
Você pode modelar essa dependência dentro de sua
Database
classe com a seguinte linha:e está tudo bem. Tudo bem até o dia em que você percebe que precisa de vários criadores de log: às vezes deseja fazer logon no console, às vezes no sistema de arquivos, às vezes usando TCP / IP e um servidor de log remoto, e assim por diante ...
E é claro que você NÃO deseja alterar todo o seu código (enquanto isso tem bilhões) e substituir todas as linhas
de:
Primeiro, isso não é divertido. Segundo, isso é propenso a erros. Terceiro, este é um trabalho estúpido e repetitivo para um macaco treinado. Então, o que você faz?
Obviamente, é uma boa idéia introduzir uma interface
ICanLog
(ou similar) implementada por todos os vários registradores. Portanto, a etapa 1 do seu código é:Agora, a inferência de tipo não muda mais, você sempre tem uma única interface para desenvolver. O próximo passo é que você não deseja ter
new Logger()
repetidamente. Portanto, você coloca a confiabilidade para criar novas instâncias em uma única classe central de fábrica e obtém códigos como:A própria fábrica decide que tipo de criador de logs criar. Seu código não se importa mais e, se você quiser alterar o tipo de logger em uso, altere-o uma vez : dentro da fábrica.
Agora, é claro, você pode generalizar esta fábrica e fazê-la funcionar para qualquer tipo:
Em algum lugar esse TypeFactory precisa de dados de configuração que classe real instanciar quando um tipo de interface específico é solicitado, portanto, você precisa de um mapeamento. Claro que você pode fazer esse mapeamento dentro do seu código, mas uma alteração de tipo significa recompilar. Mas você também pode colocar esse mapeamento dentro de um arquivo XML, por exemplo. Isso permite alterar a classe realmente usada, mesmo após o tempo de compilação (!), Ou seja, dinamicamente, sem recompilar!
Para dar um exemplo útil disso: pense em um software que não faça logon normalmente, mas quando seu cliente ligar e pedir ajuda por causa de um problema, tudo o que você enviar para ele será um arquivo de configuração XML atualizado e agora ele terá log ativado e seu suporte pode usar os arquivos de log para ajudar seu cliente.
E agora, quando você substitui um pouco os nomes, acaba com uma implementação simples de um Localizador de Serviços , que é um dos dois padrões de Inversão de Controle (desde que você inverte o controle sobre quem decide qual classe exata instanciar).
Tudo isso reduz as dependências do seu código, mas agora todo o seu código depende do localizador central de serviços único.
Agora, a injeção de dependência é o próximo passo nesta linha: Livre-se dessa única dependência no localizador de serviços: em vez de várias classes solicitarem ao localizador de implementação uma implementação para uma interface específica, você - mais uma vez - reverte o controle sobre quem instancia o que .
Com a injeção de dependência, sua
Database
classe agora tem um construtor que requer um parâmetro do tipoICanLog
:Agora, seu banco de dados sempre tem um agente de log para usar, mas não sabe mais de onde esse agente de log vem.
E é aqui que uma estrutura de DI entra em jogo: você configura seus mapeamentos mais uma vez e, em seguida, solicita à sua estrutura de DI que instancia seu aplicativo para você. Como a
Application
classe requer umaICanPersistData
implementação, uma instância deDatabase
é injetada - mas, para isso, é necessário primeiro criar uma instância do tipo de agente para o qual está configuradoICanLog
. E assim por diante ...Portanto, para resumir uma longa história: a injeção de dependência é uma das duas maneiras de remover dependências no seu código. É muito útil para alterações de configuração após o tempo de compilação e é ótimo para testes de unidade (pois facilita a injeção de stubs e / ou zombarias).
Na prática, há coisas que você não pode fazer sem um localizador de serviço (por exemplo, se você não sabe quantas instâncias precisa de uma interface específica: uma estrutura DI sempre injeta apenas uma instância por parâmetro, mas você pode chamar um localizador de serviço dentro de um loop, é claro); portanto, na maioria das vezes, cada estrutura de DI também fornece um localizador de serviço.
Mas basicamente é isso.
PS: O que eu descrevi aqui é uma técnica chamada injeção de construtor , também há injeção de propriedade onde não há parâmetros de construtor, mas propriedades estão sendo usadas para definir e resolver dependências. Pense na injeção de propriedade como uma dependência opcional e na injeção de construtor como dependências obrigatórias. Mas a discussão sobre isso está além do escopo desta questão.
fonte
Acho que muitas vezes as pessoas ficam confusas sobre a diferença entre injeção de dependência e uma estrutura de injeção de dependência (ou um contêiner, como costuma ser chamado).
Injeção de dependência é um conceito muito simples. Em vez deste código:
você escreve código assim:
E é isso. A sério. Isso oferece muitas vantagens. Dois importantes são a capacidade de controlar a funcionalidade de um local central (a
Main()
função), em vez de espalhá-la por todo o programa, e a capacidade de testar mais facilmente cada classe isoladamente (porque você pode passar zombarias ou outros objetos falsos para o construtor) de um valor real).A desvantagem, é claro, é que agora você tem uma mega função que conhece todas as classes usadas pelo seu programa. É com isso que as estruturas de DI podem ajudar. Mas se você estiver com problemas para entender por que essa abordagem é valiosa, recomendo começar primeiro com a injeção manual de dependência, para que você possa apreciar melhor o que as várias estruturas existentes podem fazer por você.
fonte
Como as outras respostas declararam, a injeção de dependência é uma maneira de criar suas dependências fora da classe que a usa. Você os injeta pelo lado de fora e assume o controle sobre a criação deles do lado de dentro da sua classe. É também por isso que a injeção de dependência é uma realização do princípio da Inversão do controle (IoC).
IoC é o princípio, onde DI é o padrão. A razão pela qual você pode "precisar de mais de um criador de logs" nunca é realmente encontrada, tanto quanto a minha experiência, mas a verdadeira razão é que você realmente precisa, sempre que testar algo. Um exemplo:
Meu Recurso:
Você pode testar isso assim:
Então, em algum lugar no
OfferWeasel
, ele cria uma oferta de objeto como este:O problema aqui é que esse teste provavelmente sempre falhará, pois a data que está sendo definida será diferente da data em que foi declarada, mesmo se você apenas inserir
DateTime.Now
o código de teste, pode ser desativado em alguns milissegundos e, portanto, sempre falham. Uma solução melhor agora seria criar uma interface para isso, que permita controlar o horário que será definido:A interface é a abstração. Uma é a coisa REAL, e a outra permite que você gaste algum tempo onde for necessário. O teste pode ser alterado assim:
Assim, você aplicou o princípio "inversão de controle" injetando uma dependência (obtendo a hora atual). A principal razão para fazer isso é para facilitar o teste de unidade isolada, existem outras maneiras de fazê-lo. Por exemplo, uma interface e uma classe aqui são desnecessárias, pois as funções C # podem ser passadas como variáveis; portanto, em vez de uma interface, você pode usar a
Func<DateTime>
para obter o mesmo. Ou, se você adotar uma abordagem dinâmica, basta passar qualquer objeto que possua o método equivalente ( digitação de pato ) e não precisará de nenhuma interface.Você quase nunca precisará de mais de um logger. No entanto, a injeção de dependência é essencial para código digitado estaticamente, como Java ou C #.
E ... Também deve-se observar que um objeto só pode cumprir adequadamente seu objetivo em tempo de execução, se todas as suas dependências estiverem disponíveis, portanto, não há muito uso na configuração de injeção de propriedade. Na minha opinião, todas as dependências devem ser satisfeitas quando o construtor está sendo chamado; portanto, a injeção de construtor é a coisa certa.
Espero que tenha ajudado.
fonte
Eu acho que a resposta clássica é criar um aplicativo mais dissociado, que não tem conhecimento de qual implementação será usada durante o tempo de execução.
Por exemplo, somos um provedor central de pagamentos, trabalhando com muitos provedores de pagamento em todo o mundo. No entanto, quando uma solicitação é feita, não tenho idéia de qual processador de pagamento vou ligar. Eu poderia programar uma classe com vários casos de switch, como:
Agora imagine que agora você precisará manter todo esse código em uma única classe porque ele não está dissociado adequadamente; você pode imaginar que, para cada novo processador suportado, será necessário criar um novo caso // switch para Em todo método, isso só fica mais complicado, no entanto, usando a Injeção de Dependência (ou Inversão de Controle - como é chamada às vezes), o que significa que quem controla a execução do programa é conhecido apenas em tempo de execução e não é complicado, você pode conseguir algo muito arrumado e sustentável.
** O código não será compilado, eu sei :)
fonte
new ThatProcessor()
pouco, em vez de usar uma estruturaO principal motivo para usar o DI é que você deseja colocar a responsabilidade do conhecimento da implementação onde o conhecimento existe. A idéia de DI está muito alinhada com o encapsulamento e o design por interface. Se o front-end solicitar alguns dados pelo back-end, não é importante para o front-end como o back-end resolve essa questão. Isso depende do manipulador de pedidos.
Isso já é comum no OOP há muito tempo. Muitas vezes, criando partes de código como:
A desvantagem é que a classe de implementação ainda é codificada, portanto, possui como front-end o conhecimento de qual implementação é usada. O DI leva o design por interface um passo adiante, que a única coisa que o front end precisa saber é o conhecimento da interface. Entre o DYI e o DI está o padrão de um localizador de serviço, porque o front end precisa fornecer uma chave (presente no registro do localizador de serviço) para permitir que sua solicitação seja resolvida. Exemplo de localizador de serviço:
Exemplo de DI:
Um dos requisitos do DI é que o contêiner deve ser capaz de descobrir qual classe é a implementação de qual interface. Portanto, um contêiner de DI requer um design fortemente tipado e apenas uma implementação para cada interface ao mesmo tempo. Se você precisar de mais implementações de uma interface ao mesmo tempo (como uma calculadora), precisará do localizador de serviço ou do padrão de design de fábrica.
D (b) I: Injeção de Dependência e Projeto por Interface. Essa restrição não é um problema prático muito grande. O benefício de usar D (b) I é que ele serve para comunicação entre o cliente e o provedor. Uma interface é uma perspectiva sobre um objeto ou um conjunto de comportamentos. O último é crucial aqui.
Prefiro a administração de contratos de serviço junto com D (b) I na codificação. Eles deveriam ir juntos. O uso de D (b) I como uma solução técnica sem administração organizacional de contratos de serviço não é muito benéfico no meu ponto de vista, porque o DI é apenas uma camada extra de encapsulamento. Mas quando você pode usá-lo junto com a administração organizacional, pode realmente fazer uso do princípio organizador D (b) I oferecido. Pode ajudá-lo a longo prazo a estruturar a comunicação com o cliente e outros departamentos técnicos em tópicos como teste, versão e desenvolvimento de alternativas. Quando você tem uma interface implícita como em uma classe codificada, é muito menos comunicável ao longo do tempo quando explicitada usando D (b) I. Tudo se resume à manutenção, que é ao longo do tempo e não de cada vez. :-)
fonte