C#  -  Padrão de projeto : Service Locator


No artigo de hoje veremos o padrão de projeto Service Locator e sua implementação na linguagem C#.

Antes de iniciar o artigo responda à pergunta...

Você saberia me dizer o que significa para um projeto ter um baixo acoplamento ?

Se você sabe a resposta, muito bem, mas se não sabe, deixe-me explicar de forma bem resumida, o que vem a ser esse tal de acoplamento :

- Acoplamento é o nível de dependência/conhecimento que pode existir entre as classes;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ele tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- Uma classe com acoplamento forte é mais difícil de manter, de entender e de ser reusada;

Assim o cenário ideal para o software é ter baixo acoplamento, e, um software que possui um arquitetura desacoplada é o que toda empresa espera.

Uma forma de conseguir um baixo acoplamento e realizar a inversão de controle, e isso nos traz de volta ao assunto do artigo.

Funciona assim : Se você tem uma classe e cria um objeto de outra classe dentro desta classe você esta iniciando um forte acoplamento entre essas classes.

Para resolver isso invertemos a comunicação tradicional entre os objetos o que resulta no nome inversão de controle.

E para implementar a inversão de controle podemos usar: o Service Locator ou a Injeção de dependência.

Neste artigo vamos tratar da inversão de controle usando o padrão Service Locator.

O padrão Service Locator

O Service Locator é um padrão de projeto comum que permite desacoplar clientes de serviços (descritos por uma interface pública) da classe concreta que implementa esses serviços. Martin Fowler tem uma ótima introdução sobre o tema neste artigo :  Inversion of Control Containers and Dependency Injection Pattern.

Ele é considerado um antipadrão sendo usado para  encapsular os processos envolvidos na obtenção de um serviço com uma camada de abstração. Esse padrão usa um registro central conhecido como "localizador de serviço" que, mediante solicitação, retorna as informações necessárias para executar uma determinada tarefa.

Vamos entender o cenário:

Na figura abaixo temos a ClasseA que usa o ServicoA e o ServicoB e para isso esta criando uma instância de cada classe que representa cada serviço:

           class ClasseA
	{
	    ServicoA sa = new ServicoA();
	    ServicoB sb = new ServicoB();
	}
	
	class ServicoA
	{
	   void Ola()
              {
		Console.WriteLine("ServicoA");
	   }
	}
	
	class ServicoB
	{
               void Ola()
                {
	  	Console.WriteLine("ServicoB");
	     }
         }

Aqui temos um forte acoplamento entre a ClasseA e os serviços ServicoA e ServicoB, ou seja, a classeA esta criando a dependência que ela precisa dos serviços. Assim qualquer alteração em qualquer um dos serviços vai 'quebrar' a classeA.

Para resolver esse problema podemos usar o Service Locator que vai representar a camada onde os serviços serão consumidos :

Agora, a ClasseA não conhece o mecanismo de criação de objetos para consumir o ServicoA e ServicoB.

Assim o Service Locator ajuda a localizar os serviços para sua utilização. Dessa forma podemos definir que você deve usar o Service Locator quando :

  1. Você deseja separar suas classes de suas dependências para que essas dependências possam ser substituídas ou atualizadas com pouca ou nenhuma alteração nas classes;

  2. Você deseja escrever uma lógica que depende de classes cuja implementação concreta não é conhecida no momento da compilação;

  3. Você deseja poder testar suas classes isoladamente, sem as dependências;

  4. Você não deseja que a lógica que localiza e gerencia as dependências esteja em suas classes;

  5. Você deseja dividir seu aplicativo em módulos fracamente acoplados que podem ser desenvolvidos, testados, versionados e implementados independentemente;

Quando formos implementar este padrão temos duas opções principais:

  1. Strong Type

  2. Generic

Vamos mostrar a implementação usando o um tipo Genérico.

Service Locator - Tipo Genérico

Primeiro vamos definir uma interface

public interface IServiceLocator
{
    T GetService<T>();
}

A seguir temos a implementação deste contrato:

class ServiceLocator : IServiceLocator
{
// mapa que contém pares de interfaces e
// referencias para as implementações concretas
private IDictionary<object, object> services;

internal ServiceLocator()
{
    services = new Dictionary<object, object>();

    // preenche o mapa
    this.services.Add(typeof(IServiceA), new ServiceA());
    this.services.Add(typeof(IServiceB), new ServiceB());
    this.services.Add(typeof(IServiceC), new ServiceC());

}

public T GetService<T>()
{
       try
    {
 
return (T)services[typeof(T)];
    }
    catch (KeyNotFoundException)
    {
  throw new ApplicationException("O serviço solicitado não esta registrado."
               }
}
}

Neste código temos que:

- O construtor da classe registra todos os serviços disponíveis em um dicionário. Em nosso exemplo, temos três serviços diferentes acessíveis pelas interfaces IServiceA, IServiceB e IServiceC. Supõe-se aqui que o ServiceA implementa IServiceA e assim por diante.

- O método GetService<T>() genérico retorna uma referência à implementação correta, buscando-a no dicionário; Aqui estamos usando um função genérica que toma  um argumento do tipo T e verifica se o tipo existe no dicionário,e , retorna um objeto da classe concreta.;

Como podemos usar ?


 IServiceLocator locator = new ServiceLocator();
 IServiceA meuServiceA = locator.GetService<IServiceA>();

Note que os clientes não conhecem as classes reais que implementam o serviço. Eles só precisam interagir com o localizador de serviço para obter uma implementação.

É claro que esta implementação é bem simples e pode ser melhorada mas ela já funciona e mostra como o padrão funciona.

E estamos conversados...

"O temor do Senhor é fonte de vida, para desviar dos laços da morte."
Provérbios 14:26,27
 


 

Referências:


José Carlos Macoratti