Bom, pessoal, hoje vamos implementar do zero o pattern de arquitetura Registry.

O Registry nada mais é do que uma espécie de buscador de objetos, ou seja, um localizador. O Registry sabe onde se encontram os objetos que você precisa, esta é a função dele. Você pode utilizá-lo quando houver a necessidade de ter um objeto que sabe procurar e achar os objetos que precisam ser localizados e utilizados. O registry também é importante por que com ele você pode obter inversão de controle, ou seja, você passa a ele o controle e responsabilidade de encontrar os objetos. Você apenas solicita a ele e o mesmo retorna o objeto para você. Mas chega de blá blá blá e vamos meter a mão na massa. Observe o diagrama abaixo:

Diagrama de classes do padrão Registry

Figura 1: Diagrama de classes do padrão Registry

Vamos começar a “dissecar” a solução. Veja que temos uma classe Registry que será chamada por qualquer camada, haja vista, o registry também pode ser utilizado como um service locator, e desta maneira pode ser acessado por qualquer camada da aplicação. Ele faz uso de um container, que é quem armazena os objetos que implementam as abstrações. O Registry irá solicitar ao container que resolva uma determinada abstração (inteface ou classe abstrata). Note que restamos aqui utilizando a inversão de controle, o registry não faz referência a uma implementação, e sim a uma abstração de um contaner. Existem vários containers no mercado, structured map, castle windsor, unity da Microsoft, enfim, uma infinidade de containers de injeção de dependência. O fato de apontarmos para uma abstração faz com que, independente do container, o que importa é existir alguém a quem o registry possa pedir para resolver a abstração. Temos ainda uma classe para nos auxiliar a obter as abstrações e suas respectivas abstrações.

O Registry

Veremos o código do registry abaixo:

Listagem 1: Implementação da classe Registry

public class Registry
{
	public Registry()
	{
		this.Container = new UnityDIContainer();
	}

	private IDIContainer Container;

	public TAbstraction getServiceImplementation<TAbstraction>()
	{
		return this.Container.Resolve<TAbstraction>();
	}
}

Note que o container aponta para uma abstração de um container, ou seja, não importa quem ele é, o importante é ele ser capaz de resolver a dependência/abstração. Dentro do construtor, é instanciada uma implementação da interface IDIContainer, que utiliza o Unity da Microsoft para container de injeção de dependência. É responsabilidade do container armazenar as abstrações (interfaces/classes abstratas) para que possam ser recuperadas quando necessário.

IDIContainer

Como dito anteriormente, a função do container é simplesmente armazenar as abstrações e suas respectivas implementações. Veja o código abaixo:

Listagem 2: Interface IDIContainer

public interface IDIContainer
{
	void ResgiterType(Type pAbstraction, Type pImplementation);
	TAbstraction Resolve<TAbstraction>();
}

Veja que o contrato é simples, um método para registrar a abstração a alguma classe que a implemente, e um método para recuperar alguma classe que resolva, ou seja, implemente uma determinada abstração.

Implementando o IDIContainer

A implementação não tem nenhum mistério, precisamos apenas obter as classes e abstrações referenciados no projeto em que se encontra o container. Veja o código da implementação para o Unity abaixo:

Listagem 3: Implementação do Container

public class UnityDIContainer : IDIContainer
{
	private IUnityContainer _fContainer;

	public UnityContainer()
	{
		this._fContainer = new UnityContainer();
		this.registraClasses();
	}

	private void registraClasses()
	{
		var objHelper = new AssemblerHelper();
		foreach(var vAbstracao in objHelper
		.obterAbstracoesNosAssempliesReferenciados())
		{
			 foreach(var vClasseImplAbstracao in objHelper.
listaImplementacoesAbstracao(vAbstracao))
			{
				this._fContainer.RegisterType(vAbstracao, vClasseImplAbstracao);
			}
		}
	}

	public void RegisterType(Type pAbstraction, Type pImplementacao)
	{
		this._fContainer.RegisterType(pAbstraction, pImplementation);
	}

	public TAbstraction Resolve<TAbstraction>()
	{
		return this._fContainer.Resolve<TAbstraction>();
	}
}

A implementação consiste apenas em encontrar as classes e abstrações e mapear no container, no caso desta implementação do Unity. Veja que no construtor é chamado o método registraClasses, que usa a classe AssemblyHelper para obter todas as abstrações referenciadas no projeto, mapeando para as respectivas implementações.

Métodos da interface

  • Register Type: Registra a abstração para uma implementação.
  • Resolve: Retorna uma implementação de uma determinada abstração.

AssemblyHelper

A classe Assemblyhelper recupera todas as abstrações e implementações referenciadas no projeto em que se encontra a implementação do container. É importante, para que o registry possa utilizar o container, que você referencie as DLLs em que se encontram as classes e interfaces que você deseja mapear. Código do Assemblyhelper abaixo:

Listagem 5: Classe AssemblyHelper

public class AssemblyHelper
{
    public IList<Assembly> obterAssemblies()
    {
        var listAssemblyRetorno = new List<Assembly>();
        var objDirInfoRelease = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory);
        var objDirInfoBin = objDirInfoRelease.Parent;

        var arquivoDLL = objDirInfoRelease.GetFiles();

        foreach (var fileInfo in arquivoDLL)
        {
            if (fileInfo.FullName.Contains(".dll"))
            {
                Assembly objAssembly = Assembly.LoadFrom(fileInfo.FullName);
                listAssemblyRetorno.Add(objAssembly);
            }
        }

        return listAssemblyRetorno;
    }

    public IList<Type> obterClassesPorAssembly(Assembly pAssembly)
    {
        IList<Type> listaClassesAssembly = new List<Type>();

        foreach (var vType in pAssembly.GetTypes())
        {
            if (vType.IsClass)
            {
                listaClassesAssembly.Add(vType);
            }
        }

        return listaClassesAssembly;
    }

    public IList<Type> obterInterfacesPorAssembly(Assembly pAssembly)
    {
        IList<Type> listaInterfacesAssembly = new List<Type>();

        foreach (var vType in pAssembly.GetTypes())
        {
            if (vType.IsInterface)
            {
                listaInterfacesAssembly.Add(vType);
            }
        }

        return listaInterfacesAssembly;
    }

    public IList<Type> obterAbstracoesPorAssembly(Assembly pAssembly)
    {
        IList<Type> listaInterfacesAssembly = new List<Type>();

        foreach (var vType in pAssembly.GetTypes())
        {
            if (vType.IsInterface)
            {
                listaInterfacesAssembly.Add(vType);
            }
            else if (vType.IsClass && vType.IsAbstract)
            {
                listaInterfacesAssembly.Add(vType);
            }
        }

        return listaInterfacesAssembly;
    }

    public IList<Type> listaImplementacoesAbstracao(Type pTipoInterface)
    {
        IList<Type> listaImplInterface = new List<Type>();

        foreach (var vAssemblies in this.obterAssemblies())
        {
            foreach (var vClasse in this.obterClassesPorAssembly(vAssemblies))
            {
                if (vClasse.GetInterface(pTipoInterface.Name) != null)
                    listaImplInterface.Add(vClasse);
            }
        }

        return listaImplInterface;
    }

    public IList<Type> obterAbstracoesNosAssembliesReferenciados()
    {
        IList<Type> listaAbstracoes = new List<Type>();

        foreach (var vAssemblies in this.obterAssemblies())
        {
            foreach (var vInterface in this.obterAbstracoesPorAssembly(vAssemblies))
            {
                listaAbstracoes.Add(vInterface);
            }
        }

        return listaAbstracoes;
    }

    public bool TypeFilter(Type pTipo, object pObjetoFiltro)
    {
        return pTipo.Name == pObjetoFiltro.GetType().Name;
    }
}

Um detalhe importante na implementação deste pattern é que além de ter um objeto localizador, devemos buscar separar as responsabilidades. Se você notar, cada classe faz o seu papel e pede que as outras colaborem quando necessário. É aí que vemos como princípios como o da responsabilidade única, inversão de controle e injeção de dependência tem seu valor. Se amanhã mudarmos de container e quisermos utilizar o Castle Windsor, por exemplo, basta apenas criar uma classe que implemente a interface IDIContainer e mudar uma linha de código no construtor do registry, por exemplo:

Listagem 6: Alterando o tipo de container

public Registry()
{
   this.Container = new WindsorDIContainer();
}

Bom, pessoal, vamos ficando por aqui. Espero que vocês tenham gostado do artigo e me coloco a disposição para quaisquer esclarecimentos. Abraços e até a próxima.