Entendo que instanciar diretamente dependências dentro de uma classe é considerada uma prática ruim. Isso faz sentido, pois isso acopla firmemente tudo o que torna os testes muito difíceis.
Quase todas as estruturas que encontrei parecem favorecer a injeção de dependência com um contêiner em vez de usar localizadores de serviço. Ambos parecem conseguir a mesma coisa, permitindo que o programador especifique qual objeto deve ser retornado quando uma classe exigir uma dependência.
Qual a diferença entre os dois? Por que eu escolheria um sobre o outro?
dependency-injection
ioc-containers
service-locator
tom6025222
fonte
fonte
Respostas:
Quando o próprio objeto é responsável por solicitar suas dependências, em vez de aceitá-las por meio de um construtor, está ocultando algumas informações essenciais. É apenas um pouco melhor do que o caso muito acoplado de uso
new
para instanciar suas dependências. Reduz o acoplamento, porque você pode alterar as dependências obtidas, mas ainda tem uma dependência que não pode ser alterada: o localizador de serviço. Isso se torna aquilo em que tudo depende.Um contêiner que fornece dependências por meio de argumentos do construtor fornece mais clareza. Vemos logo de cara que um objeto precisa de um
AccountRepository
e de umPasswordStrengthEvaluator
. Ao usar um localizador de serviço, essas informações são menos aparentes imediatamente. Você veria imediatamente um caso em que um objeto tem, oh, 17 dependências e diria a si mesmo: "Hum, isso parece muito. O que está acontecendo lá?" As chamadas para um localizador de serviço podem se espalhar pelos vários métodos e ocultar-se atrás da lógica condicional, e você pode não perceber que criou uma "classe Deus" - uma que faz tudo. Talvez essa classe possa ser refatorada em três classes menores, mais focadas e, portanto, mais testáveis.Agora considere o teste. Se um objeto usa um localizador de serviço para obter suas dependências, sua estrutura de teste também precisará de um localizador de serviço. Em um teste, você configurará o localizador de serviço para fornecer as dependências para o objeto em teste - talvez um
FakeAccountRepository
e umVeryForgivingPasswordStrengthEvaluator
e, em seguida, execute o teste. Mas isso é mais trabalho do que especificar dependências no construtor do objeto. E sua estrutura de teste também se torna dependente do localizador de serviço. É outra coisa que você precisa configurar em todos os testes, o que torna os testes de gravação menos atraentes.Procure "Localizador de serivação é um antipadrão" para o artigo de Mark Seeman sobre o assunto. Se você está no mundo .Net, pegue o livro dele. É muito bom.
fonte
constructor supplied dependencies
vsservice locator
é que o anterior pode ser verfied tempo de compilação, enquanto o último pode única ser verificado em tempo de execução.But that's more work than specifying dependencies in the object's constructor.
eu gostaria de contestar. Com um localizador de serviço, você só precisa especificar as 3 dependências necessárias para o seu teste. Com um DI baseado em construtor, você precisa especificar TODOS 10, mesmo que 7 não sejam utilizados.Imagine que você é um trabalhador em uma fábrica que fabrica sapatos .
Você é responsável por montar os sapatos e, portanto, precisará de muitas coisas para fazer isso.
E assim por diante.
Você está trabalhando na fábrica e está pronto para começar. Você tem uma lista de instruções sobre como proceder, mas ainda não possui nenhum dos materiais ou ferramentas.
Um localizador de serviço é como um capataz que pode ajudá-lo a obter o que precisa.
Você pergunta ao localizador de serviço toda vez que precisar de algo e ele sai para encontrá-lo. O Localizador de serviços foi informado com antecedência sobre o que você provavelmente solicitará e como encontrá-lo.
É melhor você esperar que não peça algo inesperado. Se o Locator não tiver sido informado com antecedência sobre uma ferramenta ou material específico, ele não poderá obtê-lo e apenas encolherá os ombros.
Um Contêiner de Injeção de Dependência (DI) é como uma caixa grande que é preenchida com tudo o que todos precisam no início do dia.
Quando a fábrica inicia, o Big Boss, conhecido como Raiz da Composição, agarra o contêiner e entrega tudo aos Gerentes de Linha .
Os gerentes de linha agora têm o que precisam para desempenhar suas funções naquele dia. Eles pegam o que têm e passam o que é necessário para seus subordinados.
Esse processo continua, com dependências escorrendo pela linha de produção. Eventualmente, um contêiner de materiais e ferramentas aparece para o seu Foreman.
Agora o seu Foreman distribui exatamente o que você precisa para você e outros trabalhadores, sem que você precise solicitá-los.
Basicamente, assim que você aparece para trabalhar, tudo o que você precisa já está em uma caixa esperando por você. Você não precisava saber nada sobre como obtê-los.
fonte
Alguns pontos extras que encontrei ao vasculhar a web:
fonte
Estou chegando atrasado para esta festa, mas não consigo resistir.
Às vezes, nenhuma. O que faz a diferença é o que sabe sobre o quê.
Você sabe que está usando um localizador de serviço quando o cliente que procura a dependência conhece o contêiner. Um cliente que sabe como encontrar suas dependências, mesmo quando passa por um contêiner para obtê-las, é o padrão do localizador de serviço.
Isso significa que, se você deseja evitar o localizador de serviço, não pode usar um contêiner? Não. Você apenas precisa impedir que os clientes saibam sobre o contêiner. A principal diferença é onde você usa o contêiner.
Vamos dizer
Client
necessidadesDependency
. O recipiente tem umDependency
.Acabamos de seguir o padrão do localizador de serviço porque
Client
sabe como encontrá-loDependency
. Certamente, ele usa um código embutido,ClassPathXmlApplicationContext
mas mesmo se você injetar que ainda tem um localizador de serviço por causa deClient
chamadasbeanfactory.getBean()
.Para evitar o localizador de serviço, você não precisa abandonar esse contêiner. Você só precisa sair,
Client
paraClient
que não saiba.Observe como
Client
agora não faz ideia que o contêiner existe:Mova o contêiner para fora de todos os clientes e cole-o no principal, onde ele pode criar um gráfico de objetos com todos os seus objetos de vida longa. Escolha um desses objetos para extrair e chame um método e inicie o gráfico inteiro.
Isso move toda a construção estática para o XML dos contêineres, mas mantém todos os seus clientes alegremente ignorantes sobre como encontrar suas dependências.
Mas main ainda sabe como localizar dependências! Sim. Mas, ao não espalhar esse conhecimento, você evitou o problema principal do localizador de serviço. A decisão de usar um contêiner agora é tomada em um local e pode ser alterada sem reescrever centenas de clientes.
fonte
Penso que a maneira mais fácil de entender a diferença entre os dois e por que um contêiner de DI é muito melhor do que um localizador de serviço é pensar por que fazemos inversão de dependência em primeiro lugar.
Fazemos inversão de dependência para que cada classe indique explicitamente exatamente do que depende para operação. Fazemos isso porque isso cria o acoplamento mais flexível que podemos alcançar. Quanto mais flexível o acoplamento, mais fácil é testar e refatorar (e geralmente exige menos refatoração no futuro porque o código é mais limpo).
Vejamos a seguinte classe:
Nesta classe, estamos declarando explicitamente que precisamos de um IOutputProvider e nada mais para fazer essa classe funcionar. Isso é totalmente testável e depende de uma única interface. Posso mover essa classe para qualquer lugar do meu aplicativo, incluindo um projeto diferente e tudo o que precisa é de acesso à interface IOutputProvider. Se outros desenvolvedores quiserem adicionar algo novo a essa classe, o que requer uma segunda dependência, eles deverão ser explícitos sobre o que precisam no construtor.
Dê uma olhada na mesma classe com um localizador de serviço:
Agora eu adicionei o localizador de serviço como a dependência. Aqui estão os problemas que são imediatamente óbvios:
Então, por que não fazemos do localizador de serviço uma classe estática? Vamos dar uma olhada:
Isso é muito mais simples, certo?
Errado.
Digamos que o IOutputProvider seja implementado por um serviço Web de execução muito longa que grava a string em quinze bancos de dados diferentes ao redor do mundo e leva muito tempo para ser concluído.
Vamos tentar testar esta classe. Precisamos de uma implementação diferente do IOutputProvider para o teste. Como escrevemos o teste?
Bem, para fazer isso, precisamos fazer algumas configurações sofisticadas na classe estática ServiceLocator para usar uma implementação diferente do IOutputProvider quando estiver sendo chamado pelo teste. Até escrever essa frase foi doloroso. Implementá-lo seria torturante e seria um pesadelo de manutenção . Nunca devemos modificar uma classe especificamente para teste, especialmente se essa classe não for a classe que realmente estamos tentando testar.
Portanto, agora você fica com a) um teste que está causando alterações intrusivas de código na classe ServiceLocator não relacionada; ou b) nenhum teste. E você também tem uma solução menos flexível.
Assim, a classe localizador de serviço tem de ser injetado no construtor. O que significa que ficamos com os problemas específicos mencionados anteriormente. O localizador de serviço exige mais código, diz a outros desenvolvedores que precisa de coisas que não precisa, incentiva outros desenvolvedores a escreverem códigos piores e nos oferece menos flexibilidade para avançar.
Simplificando , os localizadores de serviço aumentam o acoplamento em um aplicativo e incentivam outros desenvolvedores a escrever códigos altamente acoplados .
fonte