Qual é a diferença entre os padrões de Injeção de Dependência e Localizador de Serviço?

304

Ambos os padrões parecem uma implementação do princípio de inversão de controle. Ou seja, que um objeto não deve saber como construir suas dependências.

A Injeção de Dependência (DI) parece usar um construtor ou setter para "injetar" suas dependências.

Exemplo de uso de injeção de construtor:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

O Localizador de Serviço parece usar um "contêiner", que conecta suas dependências e fornece a ele sua barra.

Exemplo de uso de um localizador de serviço:

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo()
  {
    this.bar = Container.Get<IBar>();
  }

  //...
}

Como nossas dependências são apenas objetos, essas dependências têm dependências, que têm ainda mais dependências, e assim por diante. Assim, nasceu o Inversion of Control Container (ou DI Container). Exemplos: Castelo Windsor, Ninject, Mapa da Estrutura, Primavera, etc.)

Mas um contêiner IOC / DI se parece exatamente com um localizador de serviço. Está chamando de Container DI um nome ruim? Um contêiner IOC / DI é apenas outro tipo de localizador de serviço? A nuance é o fato de usarmos DI Containers principalmente quando temos muitas dependências?

Charles Graham
fonte
13
Inversão de controle significa que "um objeto não deve saber como construir suas dependências"?!? Esse é novo para mim. Não, realmente, não é isso que significa "inversão de controle". Veja martinfowler.com/bliki/InversionOfControl.html Esse artigo até fornece referências para a etimologia do termo, que remonta à década de 1980.
Rogério
1
Mark Seemann argumenta que o Service Locator é um antipadrão ( blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern ). Além disso, achei o diagrama (encontrado aqui, stackoverflow.com/a/9503612/1977871 ) útil para entender a situação de DI e SL. Espero que isto ajude.
VivekDev

Respostas:

181

A diferença pode parecer pequena, mas mesmo com o ServiceLocator, a classe ainda é responsável por criar suas dependências. Ele apenas usa o localizador de serviço para fazer isso. Com o DI, a classe recebe suas dependências. Não sabe nem se importa de onde eles vêm. Um resultado importante disso é que o exemplo de DI é muito mais fácil para o teste de unidade - porque você pode passar por implementações simuladas de seus objetos dependentes. Você pode combinar os dois - e injetar o localizador de serviço (ou uma fábrica), se desejar.

tvanfosson
fonte
20
Além disso, você pode usar os dois ao construir uma classe. O construtor padrão pode usar o SL para recuperar as dependências e passá-las ao construtor "real" que recebe essas dependências. Você obtém o melhor dos dois mundos.
Grant Palin
6
Não, o ServiceLocator é o responsável por instanciar a implementação correta para uma determinada dependência (plugin). No caso do DI, o "contêiner" do DI é o responsável por isso.
Rogério
5
@ Rogerio sim, mas a classe ainda precisa saber sobre o Localizador de Serviços ... são duas dependências. Além disso, na maioria das vezes, vi o Localizador de Serviço delegar no contêiner de DI para pesquisa, principalmente para objetos transitórios que precisam de suporte de serviço.
Adam Gent
2
@ Adam Não disse que o localizador de serviço delegaria para um contêiner de DI. Esses são dois padrões mutuamente exclusivos, conforme descrito no artigo "oficial" . Para mim, o Localizador de Serviço tem uma enorme vantagem sobre o DI na prática: o uso de um contêiner de DI convida a abusos (que eu já vi várias vezes), enquanto o uso de um Localizador de Serviço não.
Rogério
3
"Um resultado importante disso é que o exemplo de DI é muito mais fácil para o teste de unidade - porque você pode passar por implementações simuladas de seus objetos dependentes". Não é verdade. Nos testes de unidade, uma chamada para uma função de registro no contêiner do localizador de serviço pode ser usada para adicionar zombarias facilmente ao registro.
precisa saber é o seguinte
93

Quando você usa um localizador de serviço, todas as classes dependem do localizador de serviço. Este não é o caso da injeção de dependência. O injetor de dependência normalmente será chamado apenas uma vez na inicialização para injetar dependências em alguma classe principal. As classes das quais essa classe principal depende recursivamente terão suas dependências injetadas, até que você tenha um gráfico de objeto completo.

Uma boa comparação: http://martinfowler.com/articles/injection.html

Se o seu injetor de dependência se parece com um localizador de serviço, onde as classes chamam o injetor diretamente, provavelmente não é um injetor de dependência, mas um localizador de serviço.

Joel
fonte
17
Mas como você lida com o caso em que é necessário criar objetos durante o tempo de execução? Se você criá-los manualmente com "novo", não poderá usar o DI. Se você ligar para a estrutura de DI para obter ajuda, está quebrando o padrão. Então, quais opções restam?
Boris
9
@ Boris Eu tive o mesmo problema e decidi injetar fábricas específicas de classe. Não é bonito, mas fez o trabalho. Adoraria ver uma solução mais bonita.
Charlie Rudenstål
Link direto para comparação: martinfowler.com/articles/…
Teoman shipahi
2
@ Boris Se eu precisasse construir novos objetos em tempo real, injetaria uma Abstract Factory para esses objetos. O que seria semelhante a injetar um localizador de serviço nesse caso, mas fornece uma interface concreta, uniforme e em tempo de compilação para criar os objetos relevantes e tornar as dependências explícitas.
LivePastTheEnd
51

Os localizadores de serviço ocultam dependências - você não pode dizer, olhando para um objeto, se ele atinge um banco de dados ou não (por exemplo) quando obtém conexões de um localizador. Com injeção de dependência (pelo menos injeção de construtor), as dependências são explícitas.

Além disso, os localizadores de serviço quebram o encapsulamento porque fornecem um ponto global de acesso às dependências de outros objetos. Com o localizador de serviço, como em qualquer singleton :

torna-se difícil especificar as condições pré e pós para a interface do objeto cliente, porque o funcionamento de sua implementação pode ser interferido externamente.

Com a injeção de dependência, depois que as dependências de um objeto são especificadas, elas estão sob controle do próprio objeto.

Jeff Sternal
fonte
3
Eu prefiro "Singletons Considerados Estúpidos", steve.yegge.googlepages.com/singleton-considered-stupid
Charles Graham #
2
Adoro o Steve Yegge e o título desse artigo é ótimo, mas acho que o artigo que citei e "Singletons são mentirosos patológicos" de Miško Hevery ( misko.hevery.com/2008/08/17/singletons-are-pathological- mentirosos ) fazem um argumento melhor contra o diabo particular do localizador de serviço.
22610 Jeff Sternal
Essa resposta é a mais correta, pois define melhor um localizador de serviço: "Uma classe que oculta suas dependências". Observe que criar uma dependência internamente, embora muitas vezes não seja uma coisa boa, não faz da classe um localizador de serviço. Além disso, a dependência de um contêiner é um problema, mas não "o" problema que define mais claramente um localizador de serviço.
21418 Sam
1
With dependency injection (at least constructor injection) the dependencies are explicit.. Por favor explique.
FindOutIslamNow
Como acima, eu não posso ver como SL faz dependências menos explícita do que DI ...
Michał Powłoka
38

Martin Fowler afirma :

Com o localizador de serviço, a classe do aplicativo solicita explicitamente por uma mensagem para o localizador. Com a injeção, não há solicitação explícita, o serviço aparece na classe do aplicativo - daí a inversão de controle.

Resumindo: Localizador de Serviço e Injeção de Dependência são apenas implementações do Princípio de Inversão de Dependência.

O princípio importante é "Dependa de abstrações, não de concretizações". Isso tornará seu design de software "pouco acoplado", "extensível", "flexível".

Você pode usar o que melhor se adapta às suas necessidades. Para um grande aplicativo, com uma enorme base de código, é melhor usar um Localizador de Serviço, porque a Injeção de Dependência exigiria mais alterações em sua base de código.

Você pode verificar esta publicação: Inversão de Dependências: Localizador de Serviço ou Injeção de Dependência

Também o clássico: Inversão de contêineres de controle e o padrão de injeção de dependência de Martin Fowler

Design de classes reutilizáveis por Ralph E. Johnson & Brian Foote

No entanto, o que abriu meus olhos foi: ASP.NET MVC: resolver ou injetar? Essa é a questão… de Dino Esposito

Nathan
fonte
Resumo fantástico: "O Localizador de Serviço e a Injeção de Dependência são apenas implementações do Princípio de Inversão de Dependência".
Hans
E ele também afirma: A principal diferença é que, com um localizador de serviço, todos os usuários de um serviço dependem do localizador. O localizador pode ocultar dependências para outras implementações, mas você precisa ver o localizador. Portanto, a decisão entre o localizador e o injetor depende se essa dependência é um problema.
programasths
1
ServiceLocator e DI não têm nada a ver com o "princípio de inversão de dependência" (DIP). O DIP é uma maneira de tornar um componente de alto nível mais reutilizável, substituindo uma dependência em tempo de compilação por um componente de baixo nível por uma dependência de um tipo abstrato definido junto com o componente de alto nível, que é implementado pelo componente de baixo nível. componente de nível; dessa maneira, a dependência em tempo de compilação se inverte, pois agora é a de baixo nível que depende da de alto nível. Observe também que o artigo de Martin Fowler explica que DI e IoC não são a mesma coisa.
Rogério
23

Uma classe que usa o construtor DI indica para o código de consumo que existem dependências a serem satisfeitas. Se a classe usar o SL internamente para recuperar essas dependências, o código de consumo não estará ciente das dependências. Na superfície, isso pode parecer melhor, mas é realmente útil conhecer quaisquer dependências explícitas. É melhor do ponto de vista arquitetônico. E ao fazer o teste, você precisa saber se uma classe precisa de determinadas dependências e configurar o SL para fornecer versões falsas apropriadas dessas dependências. Com o DI, apenas passe as falsificações. Não é uma diferença enorme, mas está lá.

DI e SL podem trabalhar juntos, no entanto. É útil ter um local central para dependências comuns (por exemplo, configurações, registrador, etc). Dada uma classe usando esses deps, você pode criar um construtor "real" que recebe os deps e um construtor padrão (sem parâmetro) que recupera do SL e encaminha para o construtor "real".

EDIT: e, é claro, quando você usa o SL, está introduzindo algum acoplamento a esse componente. O que é irônico, uma vez que a idéia de tal funcionalidade é incentivar abstrações e reduzir o acoplamento. As preocupações podem ser equilibradas e depende de quantos lugares você precisaria para usar o SL. Se feito como sugerido acima, apenas no construtor de classe padrão.

Grant Palin
fonte
Interessante! Estou usando DI e SL, mas não com dois construtores. Três ou quatro dependências mais chatas frequentemente necessárias (configurações, etc ...) são obtidas no SL em tempo real. Tudo o resto é injetado pelo construtor. É um pouco feio, mas pragmático.
Maaartinus
10

Ambos são técnicas de implementação de IoC. Existem também outros padrões que implementam Inversão de controle:

  • Padrão de fábrica
  • Localizador de serviço
  • Recipiente DI (IoC)
  • Injeção de dependência (injeção de construtor, injeção de parâmetro (se não for necessário), injeção de setter de injeção de interface) ...

O localizador de serviço e o Container DI parecem mais semelhantes, ambos usam um container para definir dependências, que mapeia a abstração para a implementação concreta.

A principal diferença é como as dependências estão localizadas, no Service Locator, o código do cliente solicita as dependências, no DI Container usamos um contêiner para criar todos os objetos e injeta dependência como parâmetros (ou propriedades) do construtor.

Nininea
fonte
7

No meu último projeto eu uso os dois. Uso injeção de dependência para testar a unidade. Eu uso o localizador de serviço para ocultar a implementação e estar dependente do meu contêiner de IoC. e sim! Depois de usar um dos contêineres IoC (Unity, Ninject, Castelo de Windsor), você depende dele. E uma vez desatualizado ou por algum motivo, se você desejar trocá-lo, precisará / poderá alterar sua implementação - pelo menos a raiz da composição. Mas o localizador de serviço abstrai essa fase.

Como você não dependeria do seu contêiner de IoC? Você precisará agrupá-lo por conta própria (o que é uma má idéia) ou usar o Service Locator para configurar seu contêiner de IoC. Então, você instruirá o localizador de serviço a obter qual interface você precisa e chamará o contêiner IoC configurado para recuperar essa interface.

No meu caso, eu uso o ServiceLocator, que é um componente da estrutura. E use o Unity para contêiner de IoC. Se, no futuro, eu precisar trocar meu contêiner de IoC para o Ninject, tudo o que preciso fazer é configurar o localizador de serviço para usar o Ninject em vez do Unity. Migração fácil.

Aqui está um ótimo artigo explica esse cenário; http://www.johandekoning.nl/index.php/2013/03/03/dont-wrap-your-ioc-container/

Teoman shipahi
fonte
O link do artigo johandekoning está quebrado.
precisa saber é
6

Eu acho que os dois trabalham juntos.

Injeção de dependência significa que você envia uma classe / interface dependente para uma classe consumidora (geralmente para o construtor). Isso desacopla as duas classes por meio de uma interface e significa que a classe consumidora pode trabalhar com muitos tipos de implementações de "dependência injetada".

O papel do localizador de serviço é reunir sua implementação. Você configura um localizador de serviço através de algumas correias de inicialização no início do seu programa. Bootstrapping é o processo de associar um tipo de implementação a um resumo / interface específico. Que é criado para você em tempo de execução. (com base na sua configuração ou inicialização). Se você não tivesse implementado a injeção de dependência, seria muito difícil utilizar um localizador de serviço ou um contêiner de IOC.

NoelAdy
fonte
6

Um motivo a acrescentar, inspirado em uma atualização de documentação que escrevemos para o projeto MEF na semana passada (eu ajudo a criar o MEF).

Depois que um aplicativo é composto de potencialmente milhares de componentes, pode ser difícil determinar se algum componente específico pode ser instanciado corretamente. Por "instanciado corretamente", quero dizer que, neste exemplo, com base no Foocomponente, uma instância de IBare estará disponível e que o componente que o fornece:

  • tem suas dependências necessárias,
  • não se envolver em nenhum ciclo de dependência inválido e
  • no caso do MEF, seja fornecido com apenas uma instância.

No segundo exemplo que você deu, em que o construtor vai ao contêiner de IoC para recuperar suas dependências, a única maneira de testar se uma instância de Foopoderá instanciar corretamente com a configuração real do tempo de execução do seu aplicativo é realmente construir isso .

Isso tem todos os tipos de efeitos colaterais desagradáveis ​​no momento do teste, porque o código que funcionará no tempo de execução não necessariamente funcionará sob um equipamento de teste. A zombaria não funciona, porque a configuração real é a coisa que precisamos testar, não uma configuração em tempo de teste.

A raiz desse problema é a diferença já mencionada pelo @Jon: a injeção de dependências por meio do construtor é declarativa, enquanto a segunda versão usa o imperativo padrão do Localizador de Serviços.

Um contêiner de IoC, quando usado com cuidado, pode analisar estaticamente a configuração de tempo de execução do seu aplicativo sem realmente criar nenhuma instância dos componentes envolvidos. Muitos contêineres populares fornecem algumas variações disso; O Microsoft.Composition , que é a versão do MEF direcionada para aplicativos da Web .NET e estilo Metro 4.5, fornece um CompositionAssertexemplo na documentação do wiki. Usando-o, você pode escrever código como:

 // Whatever you use at runtime to configure the container
var container = CreateContainer();

CompositionAssert.CanExportSingle<Foo>(container);

(Veja este exemplo ).

Ao verificar as Raízes da composição do seu aplicativo no momento do teste, é possível detectar alguns erros que, de outra forma, podem passar por testes posteriormente no processo.

Espero que esta seja uma adição interessante a este conjunto abrangente de respostas sobre o assunto!

Nicholas Blumhardt
fonte
5

Nota: não estou respondendo exatamente à pergunta. Mas acho que isso pode ser útil para os novos alunos do padrão de Injeção de Dependência, que estão confusos com o padrão do Localizador de Serviço (anti-), que por acaso tropeça nesta página.

Conheço a diferença entre o Service Locator (parece ser um antipadrão agora) e os padrões de Injeção de Dependência e posso entender exemplos concretos de cada padrão, mas fiquei confuso com exemplos mostrando um localizador de serviço dentro do construtor (suponha que ' está fazendo injeção de construtor).

O "Service Locator" é frequentemente usado como o nome de um padrão e como o nome para se referir ao objeto (assuma também) usado nesse padrão para obter objetos sem usar o novo operador. Agora, esse mesmo tipo de objeto também pode ser usado na raiz da composição para executar a injeção de dependência, e é aí que entra a confusão.

O ponto importante é que você pode estar usando um objeto localizador de serviço dentro de um construtor de DI, mas não está usando o "padrão Localizador de Serviço". É menos confuso se o referirmos como um objeto de contêiner de IoC, pois você deve ter adivinhado que eles essencialmente fazem a mesma coisa (me corrija se eu estiver errado).

Se é referido como um localizador de serviço (ou apenas localizador), ou como um contêiner de IoC (ou apenas contêiner), não faz diferença, como você deve imaginar, pois provavelmente estão se referindo à mesma abstração (me corrija se eu estiver errado) ) É apenas que chamá-lo de localizador de serviço sugere que alguém está usando o antipadrão do Localizador de Serviço junto com o padrão de Injeção de Dependência.

O IMHO, nomeando-o um 'localizador' em vez de 'local' ou 'localizando', também pode levar às pessoas a pensar que o localizador de serviço em um artigo está se referindo ao contêiner do Localizador de Serviço e não ao padrão (anti-) do Localizador de Serviço , especialmente quando há um padrão relacionado chamado Injeção de dependência e não Injetor de dependência.

blizpasta
fonte
4

Nesse caso simplificado demais, não há diferença e eles podem ser usados ​​de forma intercambiável. No entanto, problemas do mundo real não são tão simples. Apenas suponha que a própria classe Bar possuísse outra dependência denominada D. Nesse caso, o localizador de serviço não seria capaz de resolvê-la e você teria que instancia-la na classe D; porque é da responsabilidade de suas classes instanciar suas dependências. Ficaria ainda pior se a própria classe D tivesse outras dependências e, em situações do mundo real, ela geralmente se torna ainda mais complicada do que isso. Em tais cenários, o DI é uma solução melhor que o ServiceLocator.

Daniel
fonte
4
Hmm, eu discordo: o localizador de serviço ex. mostra claramente que ainda existe uma dependência lá ... o localizador de serviço. Se a barprópria classe tiver uma dependência, bartambém haverá localizador de serviço, esse é o objetivo de usar DI / IoC.
GFoley83
2

Qual é a diferença (se houver) entre injeção de dependência e localizador de serviço? Ambos os padrões são bons em implementar o princípio da Inversão de Dependências. O padrão do Localizador de Serviço é mais fácil de usar em uma base de código existente, pois torna o design geral mais flexível, sem forçar alterações na interface pública. Por esse mesmo motivo, o código baseado no padrão Service Locator é menos legível que o código equivalente baseado na injeção de dependência.

O padrão de Injeção de Dependência deixa claro desde a assinatura quais dependências uma classe (ou método) terá. Por esse motivo, o código resultante é mais limpo e mais legível.

Yogesh
fonte
1

Seguir uma concepção simples me deu uma compreensão mais clara da diferença entre o Service Locator e o DI Container:

  • O Localizador de Serviço é usado no consumidor e extrai serviços por ID de algum armazenamento, mediante solicitação direta do consumidor

  • O DI Container está localizado em algum lugar externo e recebe serviços de algum armazenamento e os envia ao consumidor (não importa via construtor ou método)

No entanto, podemos falar sobre a diferença entre eles apenas no contexto do uso concreto do consumidor. Quando o Service Locator e o DI Container são usados ​​na raiz da composição, eles são quase semelhantes.

romano
fonte
0

O contêiner DI é um localizador de superconjunto de serviços. Pode ser usado para localizar um serviço , com capacidade adicional de montar (conectar) as injeções de dependência .

Glenn Mohammad
fonte
-2

Para o registro

//Foo Needs an IBar
public class Foo
{
  private IBar bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

A menos que você realmente precise de uma interface (a interface é usada por mais de uma classe), NÃO DEVE USÁ-LO . Nesse caso, o IBar permite utilizar qualquer classe de serviço que a implemente. No entanto, geralmente, essa interface será usada por uma única classe.

Por que é uma má idéia usar uma interface? Porque é realmente difícil depurar.

Por exemplo, digamos que a instância "bar" falhou, pergunta: qual classe falhou ?. Qual código devo corrigir? Uma visão simples, leva a uma interface e é aqui que minha estrada termina.

Em vez disso, se o código usar uma dependência grave, é fácil depurar um erro.

//Foo Needs an IBar
public class Foo
{
  private BarService bar;

  public Foo(IBar bar)
  {
    this.bar = bar;
  }

  //...
}

Se "bar" falhar, devo verificar e disparar a classe BarService.

Magallanes
fonte
1
Uma classe é um blueprint para construir um objeto específico. Por outro lado, uma interface é uma contracte apenas define um comportamento e não a ação. Em vez de repassar o objeto real, apenas a interface é compartilhada para que o consumidor não acesse o restante do seu objeto. Também para testes de unidade, ajuda a testar apenas a parte que precisa ser testada. Acho que com o tempo você entenderia sua utilidade.
Gunhan 29/04