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
- 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.