Implementando o Registry Pattern

O pattern Registry, é um pattern que pode ser utilizado como service locator e fazer desta forma, a inversão de controle. Veja como implementar um de forma práticam e utilizar este pattern dentro da sua aplicação.

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:


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

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.

Artigos relacionados