Primeiro, quero explicar uma suposição que faço para essa resposta. Nem sempre é verdade, mas muitas vezes:
Interfaces são adjetivos; classes são substantivos.
(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
ou IPrintable
. Uma classe é uma implementação real de uma ou mais dessas interfaces: List
ou Map
ambas podem ser implementações de IEnumerable
.
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 classe Logger
, e então Database
tenha uma dependência Logger
.
Por enquanto, tudo bem.
Você pode modelar essa dependência dentro de sua Database
classe com a seguinte linha:
var logger = new Logger();
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
var logger = new Logger();
de:
var logger = new TcpLogger();
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 é:
ICanLog logger = new Logger();
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:
ICanLog logger = LoggerFactory.Create();
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:
ICanLog logger = TypeFactory.Create<ICanLog>();
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 tipo ICanLog
:
public Database(ICanLog logger) { ... }
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 uma ICanPersistData
implementação, uma instância de Database
é injetada - mas, para isso, é necessário primeiro criar uma instância do tipo de agente para o qual está configurado ICanLog
. 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.