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:
public class MySpecialStringWriter
{
private readonly IOutputProvider outputProvider;
public MySpecialFormatter(IOutputProvider outputProvider)
{
this.outputProvider = outputProvider;
}
public void OutputString(string source)
{
this.outputProvider.Output("This is the string that was passed: " + source);
}
}
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:
public class MySpecialStringWriter
{
private readonly ServiceLocator serviceLocator;
public MySpecialFormatter(ServiceLocator serviceLocator)
{
this.serviceLocator = serviceLocator;
}
public void OutputString(string source)
{
this.serviceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
Agora eu adicionei o localizador de serviço como a dependência. Aqui estão os problemas que são imediatamente óbvios:
- O primeiro problema com isso é que é preciso mais código para obter o mesmo resultado. Mais código é ruim. Não é muito mais código, mas ainda é mais.
- O segundo problema é que minha dependência não é mais explícita . Eu ainda preciso injetar algo na classe. Exceto agora o que eu quero não é explícito. Está escondido em uma propriedade da coisa que solicitei. Agora, preciso acessar o ServiceLocator e o IOutputProvider se desejar mover a classe para um assembly diferente.
- O terceiro problema é que uma dependência adicional pode ser tomada por outro desenvolvedor que nem percebe que está usando quando adiciona código à classe.
- Finalmente, esse código é mais difícil de testar (mesmo que ServiceLocator seja uma interface) porque precisamos zombar de ServiceLocator e IOutputProvider em vez de apenas IOutputProvider
Então, por que não fazemos do localizador de serviço uma classe estática? Vamos dar uma olhada:
public class MySpecialStringWriter
{
public void OutputString(string source)
{
ServiceLocator.OutputProvider.Output("This is the string that was passed: " + source);
}
}
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 .