Recentemente, li o artigo de Mark Seemann sobre o antipadrão do Service Locator.
O autor aponta duas razões principais pelas quais o ServiceLocator é um antipadrão:
Problema de uso da API (com o qual estou perfeitamente bem)
Quando a classe emprega um localizador de serviço, é muito difícil ver suas dependências, pois, na maioria dos casos, a classe possui apenas um construtor PARAMETERLESS. Ao contrário do ServiceLocator, a abordagem de DI expõe explicitamente dependências por meio de parâmetros do construtor, para que as dependências sejam facilmente vistas no IntelliSense.Problema de manutenção (o que me intriga)
Considere o seguinte exemplo
Temos uma classe 'MyType' que emprega uma abordagem de localizador de serviço:
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
}
}
Agora queremos adicionar outra dependência à classe 'MyType'
public class MyType
{
public void MyMethod()
{
var dep1 = Locator.Resolve<IDep1>();
dep1.DoSomething();
// new dependency
var dep2 = Locator.Resolve<IDep2>();
dep2.DoSomething();
}
}
E é aqui que começa meu mal-entendido. O autor diz:
Torna-se muito mais difícil dizer se você está introduzindo uma mudança de ruptura ou não. Você precisa entender todo o aplicativo em que o Localizador de Serviço está sendo usado e o compilador não irá ajudá-lo.
Mas espere um segundo, se estivéssemos usando a abordagem DI, introduziríamos uma dependência com outro parâmetro no construtor (no caso de injeção do construtor). E o problema ainda estará lá. Se podemos esquecer de configurar o ServiceLocator, podemos esquecer de adicionar um novo mapeamento em nosso contêiner de IoC e a abordagem DI teria o mesmo problema de tempo de execução.
Além disso, o autor mencionou dificuldades de teste de unidade. Mas, não teremos problemas com a abordagem de DI? Não precisamos atualizar todos os testes que estavam instanciando essa classe? Vamos atualizá-los para passar uma nova dependência zombada apenas para tornar nosso teste compilável. E não vejo nenhum benefício dessa atualização e do tempo gasto.
Não estou tentando defender a abordagem do Localizador de serviços. Mas esse mal-entendido me faz pensar que estou perdendo algo muito importante. Alguém poderia dissipar minhas dúvidas?
ATUALIZAÇÃO (RESUMO):
A resposta para minha pergunta "O Service Locator é um antipadrão" depende realmente das circunstâncias. E eu definitivamente não sugeriria riscá-lo da sua lista de ferramentas. Pode se tornar muito útil quando você começa a lidar com código legado. Se você tiver a sorte de estar no início do seu projeto, a abordagem de DI pode ser uma escolha melhor, pois possui algumas vantagens sobre o Service Locator.
E aqui estão as principais diferenças que me convenceram a não usar o Service Locator para meus novos projetos:
- O mais óbvio e importante: o Localizador de Serviço oculta as dependências de classe
- Se você estiver utilizando algum contêiner de IoC, provavelmente verificará todo o construtor na inicialização para validar todas as dependências e fornecer feedback imediato sobre os mapeamentos ausentes (ou configuração incorreta); isso não é possível se você estiver usando seu contêiner de IoC como localizador de serviço
Para detalhes, leia excelentes respostas que são dadas abaixo.
Respostas:
Se você definir padrões como antipadrões, apenas porque existem algumas situações em que ele não se encaixa, então SIM é um antipadrão. Mas com esse raciocínio, todos os padrões também seriam antipadrões.
Em vez disso, precisamos verificar se há usos válidos dos padrões e, para o Service Locator, existem vários casos de uso. Mas vamos começar examinando os exemplos que você deu.
O pesadelo de manutenção com essa classe é que as dependências estão ocultas. Se você criar e usar essa classe:
Você não entende que ele possui dependências se elas estiverem ocultas usando o local do serviço. Agora, se usarmos a injeção de dependência:
Você pode identificar diretamente as dependências e não pode usar as classes antes de satisfazê-las.
Em um aplicativo de linha de negócios típico, você deve evitar o uso do local do serviço por esse mesmo motivo. Deve ser o padrão a ser usado quando não houver outras opções.
O padrão é um antipadrão?
Não.
Por exemplo, a inversão de contêineres de controle não funcionaria sem o local do serviço. É assim que eles resolvem os serviços internamente.
Mas um exemplo muito melhor é o ASP.NET MVC e o WebApi. O que você acha que torna possível a injeção de dependência nos controladores? É isso mesmo - localização do serviço.
Suas perguntas
Existem dois problemas mais sérios:
Com a injeção de construtores usando um contêiner, você obtém isso de graça.
Isso é verdade. Mas com a injeção de construtor, você não precisa varrer toda a classe para descobrir quais dependências estão faltando.
E alguns contêineres melhores também validam todas as dependências na inicialização (varrendo todos os construtores). Portanto, com esses contêineres, você obtém o erro de tempo de execução diretamente, e não em algum momento temporal posterior.
Não. Como você não depende de um localizador de serviço estático. Você tentou obter testes paralelos trabalhando com dependências estáticas? Não é divertido.
fonte
Gostaria também de salientar que, se você está refatorando um código legado, o padrão do Localizador de Serviço não é apenas um antipadrão, mas também uma necessidade prática. Ninguém jamais acenará com uma varinha mágica por milhões de linhas de código e, de repente, todo esse código estará pronto para o DI. Portanto, se você deseja iniciar a introdução de DI em uma base de código existente, geralmente é possível que você mude as coisas para se tornar serviços de DI lentamente, e o código que faz referência a esses serviços geralmente NÃO será um serviço de DI. Portanto, esses serviços precisarão usar o localizador de serviços para obter instâncias desses serviços que foram convertidos para usar DI.
Portanto, ao refatorar grandes aplicativos legados para começar a usar os conceitos de DI, eu diria que não apenas o Service Locator NÃO é um antipadrão, mas também a única maneira de aplicar gradualmente os conceitos de DI à base de código.
fonte
Do ponto de vista de teste, o Localizador de Serviço é ruim. Veja a boa explicação de Misko Hevery no Google Tech Talk com exemplos de código http://youtu.be/RlfLCWKxHJ0 a partir do minuto 8:45. Gostei da analogia dele: se você precisar de US $ 25, peça dinheiro diretamente, em vez de dar sua carteira de onde o dinheiro será retirado. Ele também compara o Service Locator com um palheiro que possui a agulha necessária e sabe como recuperá-lo. As classes que usam o Localizador de serviços são difíceis de reutilizar por causa disso.
fonte
Existem 2 razões diferentes pelas quais o uso do localizador de serviço é ruim nesse sentido.
Puro e simples: Uma classe com um localizador de serviço é mais difícil de reutilizar do que uma classe que aceita suas dependências por meio de seu construtor.
fonte
Meu conhecimento não é bom o suficiente para julgar isso, mas, em geral, acho que se algo é útil em uma situação específica, isso não significa necessariamente que não pode ser um antipadrão. Especialmente, quando você lida com bibliotecas de terceiros, não tem controle total sobre todos os aspectos e pode acabar usando a solução não muito melhor.
Aqui está um parágrafo do Adaptive Code Via C # :
fonte
O autor argumenta que "o compilador não irá ajudá-lo" - e é verdade. Ao designar uma classe, você desejará escolher cuidadosamente sua interface - entre outros objetivos, para torná-la tão independente quanto ... quanto faz sentido.
Ao fazer com que o cliente aceite a referência a um serviço (a uma dependência) por meio de interface explícita, você
Você está certo de que o DI tem seus problemas / desvantagens, mas as vantagens mencionadas os superam de longe ... IMO. Você está certo, que com o DI há uma dependência introduzida na interface (construtor) - mas essa é, com sorte, a mesma dependência de que você precisa e que deseja tornar visível e verificável.
fonte
Sim, o localizador de serviço é um antipadrão que viola o encapsulamento e é sólido .
fonte