Unity Application Block: O padrão de injeção de dependência

Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Para efetuar o download você precisa estar logado. Clique aqui para efetuar o login
Confirmar voto
0
 (1)  (0)

Use o Unity Application Block para construir aplicações com baixo acoplamento.

O padrão de injeção de dependência

Usando o Unity Application Block para construir aplicações com baixo acoplamento

A grande maioria das aplicações está repleta de objetos e componentes customizados responsáveis por executar tarefas específicas, genéricas ou complementares tais como envio de e-mail, geração de arquivo texto, logging, autenticação, autorização, manipulação de exceções, extração de informações de uma fonte qualquer, manipulação de dispositivos, entre outros. Na medida em que aumentamos a comunicação entre os módulos de uma aplicação (seja através de componentes, classes ou métodos) geramos uma crescente dependência aumentando sensivelmente sua complexidade de entendimento e tornando a tarefa de aplicação de testes mais extensa. Um dos resultados indesejáveis deste cenário é o alto acoplamento entre os módulos (ou subsistemas), e uma das técnicas mais utilizadas para reverter esse quadro é o chamado Padrão de Injeção de Dependência (Dependency Injection Pattern ou apenas DI).

O problema do alto acoplamento

Podemos medir o quão fortemente as classes de uma aplicação estão conectadas, possuem conhecimento ou dependem de outra classe observando o acoplamento existente em cada caso. Quanto mais acoplada uma classe estiver de outra, maior o risco de termos uma combinação dos seguintes problemas:

·             Mudanças numa classe relacionada forçam alterações locais à classe para que o funcionamento seja mantido;

·             O reuso da classe torna-se mais difícil uma vez que sua implementação depende de outras classes;

·             A coesão da classe diminui na medida em que aumenta o acoplamento caso ela assuma parte das responsabilidades de outra classe;

·             A classe não pode ser testada isoladamente já que depende de testes em conjunto com outras classes, aumentando inclusive o esforço de entendimento.

Percebemos rapidamente que diminuindo ao máximo o acoplamento, além de evitarmos os problemas citados, tornaremos nossa aplicação mais flexível e fácil de ser mantida, especialmente em equipes com paralelismo de desenvolvimento de atividades correlatas.

Utilizando as corretas técnicas e ferramentas para injeção de dependência, diversos componentes podem ser desenvolvidos paralelamente e sua aplicação se tornará mais plugável e flexível, exigindo um esforço menor para modificações. Pode-se aproveitar ainda de recursos como Mock Objects para auxiliar nos testes unitários.

       

Nota do DevMan

Mock Ojects são objetos que simulam o comportamento de outros objetos ou controles complexos implementando a mesma interface das classes reais. Existem diversos frameworks de mock para .Net, dentre eles o NMock disponível em http://www.nmock.org. Esse tipo de objeto é muito utilizado em abordagens de desenvolvimento orientadas a testes, conhecidas como TDD (Test-Driven Development).

A .Net Magazine publicou uma série de artigos sobre testes e mocks nas edições 53, 54 e 55.

O conceito de Inversão de Controle (IoC)

Tomemos como exemplo uma classe chamada ClasseA que depende dos serviços Servico1 e Servico2, conforme a Figura 1.



Figura 1.
Dependência entre classes

Se em algum momento necessitarmos substituir ou atualizar alguma das suas dependências, a classe ClasseA sofrerá alterações em seu código, uma vez que está relacionada diretamente com as classes de serviço. Outro ponto é que as implementações concretas das dependências precisam estar disponíveis no momento da compilação da solução. Uma das soluções seria delegar a função de selecionar a implementação da classe concreta para um componente (ou um container) externo.

Em diversas literaturas podemos encontrar esta solução sendo descrita como Padrão de Inversão de Controle (Inversion of Control Pattern, ou simplesmente IoC), onde a Injeção de Dependência é uma das maneiras de a implementarmos (assim como o Service Locator Pattern), conforme observamos na Figura 2. Sendo assim, é correto afirmarmos que a DI é um caso particular de IoC.




Figura 2. O padrão de injeção de dependência

A classe ClasseA ao invés de fazer referência a classe concreta, passa a referenciar uma interface IServicoA que por sua vez é implementada pelo serviço Servico1. O objeto identificado na Figura 2 como Builder fica encarregado de resolver a classe concreta e injetar a dependência na classe ClasseA.

O Unity Application Block permite aplicarmos o padrão de injeção de dependência, conforme veremos a seguir.

Apresentando o Unity Application Block

Disponibilizado a partir da versão 4.0 da Enterprise Library, em maio de 2008, e baseado na biblioteca ObjectBuilder2, o Unity Application Block pode ser brevemente definido como um container leve e extensível de injeção de dependência (lightweight container) que possibilita a construção de aplicações com baixo acoplamento através de facilidades como:

·             Simplicidade na criação de estruturas de objetos hierárquicos;

·             Especificação das dependências entre objetos, tanto em tempo de execução como em arquivo de configuração;

·             Flexibilidade para configuração do container;

·             Armazenamento e persistência do container em cache, muito útil para aplicações Web quando é necessário persistir o container em Session ou Application.

Aqui cabe uma pequena explicação para o termo “container”, que na programação orientada a objetos significa um objeto que contém outros objetos que podem ser incluídos ou removidos dinamicamente, em tempo de execução.

Diferentemente dos demais blocos, o Unity Application Block pode ser utilizado para desenvolvimento sem a necessidade de instalação da Enterprise Library e sua configuração pode ser realizada tanto pelos arquivos de configuração (familiares para quem já utilizou outros blocos, como o Data Application Block, Logging Application Block, Exception Handling Application Block, etc.) ou programaticamente, registrando suas dependências em tempo de execução. A exemplo da própria Enterprise Library, o Unity Application Block também é disponibilizado junto com o seu código fonte, permitindo modificações e extensões, assim figura como mais uma alternativa a outras bibliotecas semelhantes como Spring.Net, Castle Windsor, StructureMap entre outras.

Em dezembro de 2008 foi disponibilizado um port do Unity Application Block 1.2 para Silverlight. A biblioteca na qual se baseia este artigo é a de versão 1.2 de outubro de 2008 e, até a publicação deste, a mais recente disponível.

Para instalar o Unity Application Block você pode fazer o download da Enterprise Library 4.1 no site da Microsoft no endereço http://msdn.microsoft.com/entlib. Ou faça o download da versão standalone diretamente do CodePlex no seguinte link: http://www.codeplex.com/unity. A documentação para o Visual Studio 2008 pode ser obtida em: http://tinyurl.com/ac53vn.

O processo de instalação é simples e os requisitos para utilização do Unity Application Block são explicados detalhadamente na documentação acima. Veremos a seguir como podemos iniciar o uso de injeção de dependência com o Unity a partir de um exemplo prático, a cada novo tópico desse artigo entenderemos alguns conceitos da biblioteca e do padrão de DI.

Cenário

Um exemplo bastante simplificado é o ideal para iniciarmos o uso do Unity Application Block, pois com um foco maior no cenário poderemos tirar algumas conclusões mais claramente sobre a injeção de dependência. Usarei um projeto do tipo Class Library que implementará uma lógica de extração de arquivo e outro projeto do tipo Web Application para realizar as chamadas ao primeiro projeto. O objetivo será utilizarmos o Unity para injetarmos a dependência da Class Library no projeto Web de forma que o acoplamento entre as classes seja baixo.

Tomemos uma classe chamada Extrator do projeto do tipo Class Library que realiza a descompactação de um arquivo. Como sua implementação é menos importante que os conceitos que quero passar, omitirei do código fonte os trechos que não nos interessam conforme observamos na Listagem 1. A princípio veremos a implementação tradicional, onde a classe concreta será referenciada diretamente pelo método que precisa executar a tarefa de descompactar um dado arquivo.

Listagem 1. Código inicial da classe Extrator

   1 using System;

   2

   3 namespace NetMag.DI.Core

   4 {

   5     public class Extrator

   6     {

   7         public string Arquivo          { get; set; }

   8         public string DiretorioDestino { get; set; }

   9

  10         public string Descompactar()

  11         {

  12             // Implementações de descompactação de arquivo...

  13

  14             return "Arquivo descompactado com sucesso.";

  15         }

  16     }

  17 }

O método ExecutarCarga, presente no projeto web (que referencia o projeto class library) e ilustrado no Listagem 2, realiza a chamada diretamente à classe concreta Extrator. Como vimos no início deste artigo, neste tipo de implementação, modificações no tipo de extrator a ser utilizado implicará em alterações no código da classe chamadora. Outro ponto, é que para testarmos este método precisamos da implementação da classe Extrator, ou ao menos deveremos utilizar algum artifício, como por exemplo, stub methods, para podermos executá-los.

Listagem 2. Código inicial do método que chamará a classe Extrator

  24 public string ExecutarCarga()

  25 {

  26     Extrator extrator = new Extrator();

  27     extrator.Arquivo = @"c:\arquivo.zip";

  28     extrator.DiretorioDestino = @"c:\destino\";

  29

  30     return extrator.Descompactar();            

  31 }

Aplicando o Unity

Para utilizarmos o Unity Application Block em nossa aplicação Web devemos adicionar no projeto as referências para as bibliotecas a seguir:

·                     Microsoft.Practices.ObjectBuilder2.dll

·                     Microsoft.Practices.Unity.dll

·                     Microsoft.Practices.Unity.Configuration.dll

Estes arquivos estão disponíveis no diretório de instalação da Enterprise Library, que por padrão é “C:\Program Files\Microsoft Enterprise Library 4.1 - October 2008\Bin”. Se tudo foi instalado corretamente eles vão também aparecer na lista de referências do Visual Studio.

Faremos agora a inclusão dos namespaces indicados na Listagem 3 na classe que contém o método ExecutarCarga. Devemos providenciar também uma interface da classe Extrator, com isso garantiremos a assinatura da classe para outras implementações de extração de arquivos (como um contrato de utilização), esse passo é fundamental para podermos manter um baixo acoplamento entre as classes e aplicar a inversão de controle. Podemos ver na Listagem 4 como ficou a nossa interface IExtrator, e na Listagem 5 a mesma sendo utilizada pela classe concreta Extrator.

Listagem 3. Adicionando os namespaces do Unity Application Block

  11 using Microsoft.Practices.Unity;

  12 using Microsoft.Practices.Unity.Configuration;

Nota

Para extrair rapidamente a interface da classe Extrator basta apontar para o código da classe e clicar com o botão direito do mouse na palavra Extrator (linha 5 da Listagem 1). Escolha a opção Refactor, em seguida clique em Extract Method..., será exibida uma janela sugerindo o nome IExtrator e solicitando que você escolha os membros públicos. Clique no botão Select All, em seguida confirme clicando no botão Ok.

Listagem 4. A interface IExtrator

   1 using System;

   2

   3 namespace NetMag.DI.Core

   4 {

   5     public interface IExtrator

   6     {

   7         string Arquivo { get; set; }

   8         string DiretorioDestino { get; set; }

   9         string Descompactar();        

  10     }

  11 }

Listagem 5. Utilizando a interface IExtrator

5 public class Extrator : NetMag.DI.Core.IExtrator

Embora precisemos apenas das referências ObjectBuider2 e Unity para os primeiros exemplos, mais adiante utilizaremos o Unity.Configuration para aplicarmos outros conceitos.

A partir deste ponto podemos voltar ao nosso método ExecutarCarga e alterá-lo para que faça a chamada ao container do Unity para que ele resolva a classe concreta que deverá ser chamada, conforme mostrado na Listagem 6.

Listagem 6. Modificando o método ExecutarCarga

  27 public string ExecutarCarga()

  28 {

  29     // Criamos o container que fará o registro

  30     // da interface e da classe concreta associada.

  31     IUnityContainer container = new UnityContainer();            

  32     container.RegisterType();

  33

  34     // O container resolve a classe concreta.

  35     IExtrator extrator = container.Resolve<IExtrator>();

  36

  37     extrator.Arquivo = @"c:\arquivo.zip";

  38     extrator.DiretorioDestino = @"c:\destino\";

  39

  40    return extrator.Descompactar();

  41 }

A essa altura algumas dúvidas sobre a efetividade desta técnica podem aparecer já que o código ficou maior. Devemos tem em mente que estamos aplicando um padrão de desenvolvimento para diminuirmos a dependência do método ExecutarCarga na classe concreta Extrator e tornar a aplicação Web mais plugável, é justo adicionarmos mais alguns passos, não é verdade? Mais adiante veremos como podemos melhorar o código acima, mas antes vamos entender o que o Unity fez, vejamos o que significam algumas das linhas modificadas da Listagem 6:

  31 IUnityContainer container = new UnityContainer();

32 container.RegisterType<IExtrator, Extrator>();


O método RegisterType registra o tipo desejado no container, no nosso exemplo queremos indicar que para a interface IExtrator estaremos utilizando a classe concreta Extrator, portanto o mapeamento será definido de IExtrator para Extrator. Quando solicitado, o container irá criar uma instância do tipo especificado, seja em resposta a injeção de dependência por meio de atributos de classe ou quando o método Resolve for chamado. O objeto também pode ter o seu tempo de vida configurado bastando indicá-lo como um dos parâmetros do método, mas caso não seja especificado o tipo será registrado como transitório, que significa que uma nova instância será criada em cada chamada do container (entre as opções existe a possibilidade de criar singletons). Veremos em outro tópico o funcionamento do tempo de vida do container ou lifetime com mais detalhes. Finalmente na linha seguinte o método Resolve define o valor da classe concreta para a dependência:


35 IExtrator extrator = container.Resolve<IExtrator>();


O objeto extrator funcionará da mesma forma daqui por diante, o Unity se encarregou de construir o objeto sem o ônus da dependência direta da classe concreta, contudo o mapeamento foi definido dentro do código, e isso pode não ser muito interessante quando o requisito de facilidade de configuração tem um peso maior. Veremos a seguir como resolver esse problema utilizando outro método de configuração.

Configuração em tempo de design

No tópico anterior vimos como criar o container e registrar os tipos desejados programaticamente, contudo podemos deixar esta configuração no arquivo web.config da solução, conforme a Listagem 7. Isso deixará todas as configurações centralizadas em um único ponto. Na linha 4 você vê a configuração da seção de configuração. Atente para a versão do Unity que você está rodando, neste caso a versão 1.1 está sendo usada. Além disso, ela pode estar assinada, o que geraria um public key token.

Para permitir a criação do container de maneira mais automática, podemos colocar o código mostrado na Listagem 8 no arquivo Global.asax da aplicação Web.

Listagem 7. Configuração do arquivo Web.config da aplicação Web

1  <?xml version=”1.0”?>

2  <configuration>

3    <configSections>

4      <section name=”unity” type=”Microsoft.Practices.Unity.

  Configuration.UnityConfigurationSection, Microsoft.

  Practices.Unity.Configuration, Version=1.1.0.0,

  Culture=neutral, PublicKeyToken=null” />

          <!-- outras seções omitidas -->

16  </configSections>

17  <unity>

18    <typeAliases>

19     <typeAlias alias=”IExtrator”

20       type=”NetMag.DI.Core.IExtrator, NetMag.DI.Core” />

21     <typeAlias alias=”Extrator”

22       type=”NetMag.DI.Core.Extrator, NetMag.DI.Core” />

23    </typeAliases>

24    <containers>

25      <container name=”containerExtrator”>

26        <types>

27         <type type=”IExtrator” mapTo=”Extrator” />

28        </types>

29       </container>

30     </containers>

31  </unity><!-- restante do arquivo omitido -->


Listagem 8. Criando o container quando a aplicação for iniciada no arquivo Global.asax

    // Os seguintes namespaces devem ser inseridos

   8 using System.Configuration;

   9 using Microsoft.Practices.Unity;

  10 using Microsoft.Practices.Unity.Configuration;

    // O método Application_Start deve ser modificado da seguinte forma

  17 protected void Application_Start(object sender, EventArgs e)

  18 {

  19     IUnityContainer container = new UnityContainer();

  20     UnityConfigurationSection section =

  21         (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

  22     section.Containers["containerExtrator"].Configure(container);

  23     Application["container"] = container;

  24 }

A Tabela 1 fornece uma descrição para os elementos indicados na Listagem 7 que serão utilizados pelo Unity para definir o mapeamento do container e os tipos utilizados. Existem outros itens de configuração possíveis além dos demonstrados, todos eles estão disponíveis na documentação referenciada neste artigo.

Elemento

Descrição

Unity

Especifica a configuração do Unity Application Block, é um elemento requerido.

typeAliases

Define uma coleção opcional de aliases (apelidos) que permitem especificar os tipos requeridos, lifetimes, instâncias e extensões do container.

typeAlias

Define um único tipo a ser referenciado na configuração.

containers

Define uma coleção de containers do Unity Application Block.

container

Define um container específico.

types

Indica uma coleção de mapeamentos registrados para um determinado container.

Type

Define um tipo de específico de mapeamento.

lifeTime

Contém os detalhes a cerca do tempo de vida a ser usado no mapeamento.

Tabela 1. Descrição dos elementos utilizados pelo Unity Application Block no arquivo Web.config.

       

Observando a Listagem 9 podemos perceber que a utilização de configuração em tempo de design trouxe alguns benefícios pois a criação do container será realizada uma única vez no arquivo Global.asax (vide Listagem 8) e os tipos de container podem ser configurados no arquivo Web.config ficando a cargo do método ExecutarCarga apenas solicitar ao container que resolva a dependência para a interface IExtrator quando necessário.

Listagem 9. Utilizando a configuração em tempo de design

  12 namespace NetMag.DI.Web

  13 {

  14     public partial class Pagina : System.Web.UI.Page

  15     {

  16         private IUnityContainer container;

  17

  18         protected void Page_Load(object sender, EventArgs e)

  19         {

  20             if (Application["container"] != null)

  21             {

  22                 container = (IUnityContainer)Application["container"];

  23             }

  24             else

  25             {

  26                 // Código para tratamento de exceções

  27             }

  28         }

  29

  35         public string ExecutarCarga()

  36         {            

  37             IExtrator extrator = container.Resolve<IExtrator>();

  38

  39             extrator.Arquivo = @"c:\arquivo.zip";

  40             extrator.DiretorioDestino = @"c:\destino\";

  41

  42             return extrator.Descompactar();

  43         }

  44     }

  45 }

Note que foram adicionadas algumas linhas no evento Page_Load para que seja verificado se o container armazenado na variável de aplicação “container” é nulo.

Iremos criar agora mais uma classe que implementa a interface IExtrator, deixaremos ela disponível para alterarmos a configuração do container pelo arquivo web.config sem a necessidade de modificação de código da aplicação Web. Para isso criaremos uma nova classe chamada NetMagExtrator conforme a Listagem 10.

Listagem 10. Nova classe NetMagExtrator que implementa a mesma interface IExtrator

   1 using System;

   2

   3 namespace NetMag.DI.Core

   4 {

   5     class NetMagExtrator : NetMag.DI.Core.IExtrator

   6     {

   7         #region IExtrator Members

   8         public string Arquivo { get; set; }

   9         public string DiretorioDestino { get; set; }

  10

  11         public string Descompactar()

  12         {

  13             // Implementações de descompactação de arquivo...

  14

  15             return "Arquivo descompactado com sucesso pelo NetMagExtrator.";

  16         }

  17         #endregion

  18     }

  19 }

Para alterarmos o mapeamento existente entre a interface IExtrator com a classe Extrator, adicionaremos a linha indicada na Listagem 11 (note que as linhas estão sendo puladas porque o código pré-existente continua presente) e faremos a atualização indicada na Listagem 12, linha 29 no arquivo web.config.

Listagem 11. Adicionando outro tipo de objeto

17  <unity>

18     <typeAliases>

23       <typeAlias alias=”NetMagExtrator”

24         type=”NetMag.DI.Core.NetMagExtrator, NetMag.DI.Core” />

25      </typeAliases>

33  </unity>

Listagem 12. Alterando o mapeamento do container para a nova classe

26  <containers>

27    <container name=”containerExtrator”>

28      <types>

29        <type type=”IExtrator” mapTo=”NetMagExtrator” />

30      </types>

31     </container>

32  </containers>

Se executarmos a aplicação e alterarmos o mapeamento apontado para a nova classe NetMagExtrator, constataremos que o Unity realizará a injeção de dependência em tempo de execução, uma vez que a interface IExtrator foi implementada por ambas as classes Extrator e NetMagExtrator. Pouco interessa ao método qual a implementação concreta, o que interessa é o contrato (interface) implementado.

Como gerenciar o tempo de vida do container (lifetime)

Podemos definir para um objeto do container alguns estilos de tempo de vida, ou lifetime. Como dito antes, o lifetime default é o transitório, ou seja, uma nova instância será criada em cada chamada do container. Através do gerenciador de lifetime poderíamos, por exemplo, definir uma instância do tipo singleton para um objeto, o que garantiria apenas uma instância do mesmo mantendo um ponto global de acesso a este objeto.

O Unity Application Block possui alguns gerenciadores de lifetime e permite a criação de outros tipos para implementar cenários específicos. Os gerenciados disponíveis são:

·                 ContainerControlledLifetimeManager – Retorna a mesma instância do objeto ou tipo registrado a cada chamada do método Resolve ou ResolveAll implementando o padrão Singleton.

·                 ExternallyControlledLifetimeManager – Permite registrar o mapeamento de objetos existentes mantendo uma referência fraca, caso o garbage collector identifique que outro código está mantendo uma referência forte ao objeto ele pode realizar o dispose da instância criada pelo gerenciador.

·                 PerThreadLifetimeManager – Implementa um comportamento singleton através de threads permitindo a criação de um novo objeto caso não esteja armazenado em uma thread específica.

Para utilizarmos o gerenciador de lifetime precisamos fazer a seguinte alteração no código de registro de tipos do container mostrado na Listagem 6:

31 IUnityContainer container = new UnityContainer();

32 container.RegisterType<IExtrator, Extrator>(new

    ContainerControlledLifetimeManager());


Ou ainda, para o arquivo de configuração ilustrado na Listagem 7 precisaríamos alterar o elemento typeAliases adicionando o conteúdo da Listagem 13 (somente o código incluído está listado, o código anterior está oculto).

Listagem 13. Configuração do lifetime no web.config

  19      

  20                 type="Microsoft.Practices.Unity.ContainerControlledLifetimeManager,

  21                 Microsoft.Practices.Unity" />

  22      

  23                 type="Microsoft.Practices.Unity.ExternallyControlledLifetimeManager,

  24                 Microsoft.Practices.Unity" />

  25      

  26                 type="Microsoft.Practices.Unity.PerThreadLifetimeManager,

  27                 Microsoft.Practices.Unity" />

  35    

  36      

  37        

  38          

  39            

  40          

  41        

  42      

  43    

Dependendo do cenário, o gerenciamento do lifetime em conjunto com a persistência de containers em cache pode auxiliar na performance da aplicação, já que implementações de objetos singleton reduzem o consumo de recursos de memória.

Constructor Injection

Outro cenário interessante para aplicação de DI é na resolução de dependências do construtor de uma classe onde um mecanismo de construção automática de objetos pode facilitar a criação de cadeias de dependência. Imagine que a classe NetMagExtrator possua algumas dependências com outras classes, podemos usar o Unity para injetar automaticamente essas dependências especificando como parâmetros do construtor da classe os tipos de objetos dependentes, como exemplificado na Listagem 14.

Listagem 14. Especificando as dependências através de parâmetros no construtor da classe NetMagExtrator

       public class NetMagExtrator

       {

           public NetMagExtrator(IExtrator Extrator, BaseExtrator baseExtrator)

           {

               // Aqui as classes concretas seriam utilizadas.

           }

       }

A classe NetMagExtrator usa dois tipos de objeto em seu construtor, uma interface e outra classe base (crie uma classe vazia apenas para testar) que terão seu mapeamento registrado em um container do Unity que por sua vez fará a resolução destes tipos para os tipos concretos corretos. A idéia é definir na classe de destino um construtor que aponte os tipos dependentes que em tempo de execução serão resolvidos pelo Unity na classe alvo, como mostrado na Listagem 15. O container uContainer irá instanciar os tipos concretos de cada uma das classes mapeadas (ServicoA e ServicoB, no exemplo, outras classes derivadas) e injetar-lhes a classe de destino.

Listagem 15. Injetando as dependências da classe NetMagExtrator

   IUnityContainer uContainer = new UnityContainer()

      .RegisterType()

      .RegisterType();

   NetMagExtrator extrator = uContainer.Resolve();

Além do Constructor Injection o Unity também suporta a injeção de dependência especificada através de atributos de decoração nos membros da classe alvo, conhecida como Setter Injection, que será explicada no tópico a seguir.

Setter Injection

Neste tipo de implementação usamos um atributo aplicado na declaração de uma propriedade que define os requisitos de injeção de dependência, através de um mapeamento registrado pelo Unity o atributo pode especificar parâmetros a fim de controlar o seu comportamento. Essa técnica é chamada de injeção de propriedade (ou ainda Setter Injection) e diferentemente do Constructor Injection é necessário aplicar o atributo adequado na classe de destino para fazer a injeção.

O exemplo do tópico anterior poderia ser redefinido usando o Setter Injection como demonstrado nas Listagens 16 e 17. Na Listagem 16, note que estamos utilizando o atributo Dependency, o que significa que você precisa adicionar a dependência ao assembly do Unity.

Listagem 16. Injetando as dependências da classe NetMagExtrator através de suas propriedades

   public class NetMagExtrator

   {

       [Microsoft.Practices.Unity.Dependency]

       public IExtrator InterfaceObject { get; set; }

       [Microsoft.Practices.Unity.Dependency]

       public BaseExtrator BaseObject { get; set; }

   }

Listagem 17. Resolvendo as dependências com o Unity

   IUnityContainer uContainer = new UnityContainer()

         .RegisterType()

         .RegisterType();

   NetMagExtrator extrator = uContainer.Resolve();

   // Acessando as propriedades que contêm a dependência.

   IExtrator depServiceA = extrator.InterfaceObject;

   BaseExtrator depServiceB = extrator.BaseObject;

Este tipo de abordagem implementa injeção obrigatória de objetos dependentes, portanto sua utilização é recomendada quando:

·             Temos muitos construtores para um objeto pai, ou os construtores exigem um grande número de parâmetros, especialmente se eles são de tipos semelhantes e a única forma de identificá-los é pela sua posição;

·             É desejável tornar mais fácil para outros desenvolvedores ver quais configurações e objetos estão disponíveis, o que não é possível através de Constructor Injection.

Nota

Um risco no uso de injeção de dependência, principalmente quando definimos cadeias mais complexas de criação de objetos, é a referência circular, em geral de difícil detecção. As causas mais comuns são:

à Objetos gerados através de Constructor Injection que fazem referência mutuamente em seus parâmetros de um construtor, ou ainda, onde uma instância de uma classe é passada como um parâmetro para seu construtor.

à  Objetos gerados por meio de Setter Injection que fazem referência uns aos outros.

Decidindo sobre o uso do Unity Application Block

Vimos até aqui que a injeção de dependência pode trazer uma desejável simplificação no processo de construção de objetos gerando automaticamente os objetos dependentes, contudo, devemos nos atentar para os casos onde as dependências são simples, pois o código pode ficar mais complexo que o necessário. Considere utilizar este padrão quando:

·       Suas classes e objetos possuem dependências complexas de outras classes e objetos ou requerem alguma abstração;

·       A sua aplicação obterá ganhos em utilizar um gerenciador de lifetime dos objetos;

·       Você precisa configurar e alterar as dependências em tempo de execução da aplicação;

·       Você precisa persistir ou armazenar as suas dependências entre postbacks da sua aplicação Web.

Evite a utilização se:

·       Suas classes e objetos não possuem dependência em outras classes e objetos;

·       As dependências são muito simples e não requerem abstração.

Conclusão

O Padrão de Injeção de dependência pode ser uma técnica interessante para ser explorada dentro dos seus aplicativos. O Unity Application Block pode ajudá-lo a criar aplicações com baixo acoplamento entre classes, contudo tão importante quanto o padrão aqui apresentado é entendermos em que contextos podemos lançar mão destas técnicas. Como resultado a aplicação se tornará menos complexa para aplicação de testes e a manutenção de dependências terá menor impacto em tempo e entendimento.

Ao longo deste artigo vimos alguns cenários para uso do Padrão de Injeção de Dependência como alternativa na solução do problema do alto acoplamento. O código-fonte completo da solução utilizada está disponível em meu blog: http://reverb.leandrodaniel.com/artigos e também no site da Devmedia. Até o próximo artigo!

Referências

http://msdn.microsoft.com/en-us/library/cc467894.aspx - Enterprise Library

http://msdn.microsoft.com/en-us/library/dd203101.aspx - Unity Application Block

http://martinfowler.com/articles/injection.html - Inversion of Control Containers and the Dependency Injection pattern

http://en.wikipedia.org/wiki/Mock_object - Sobre o NMock

 
Você precisa estar logado para dar um feedback. Clique aqui para efetuar o login
Receba nossas novidades
Ficou com alguma dúvida?