Artigo no estilo: Curso

Por que eu devo ler este artigo:Fornecendo alternativas para a codificação de funcionalidades genéricas como logging, tratamento de exceções e acesso a dados, o Enterprise Library procura tornar mais simples e produtiva a implementação destas rotinas em soluções voltadas à plataforma .NET.

Neste artigo encerraremos o projeto iniciado na edição anterior, fazendo uso do mecanismo de injeção de dependências do Unity Application Block (que também é parte integrante do Enterprise Library).

O Microsoft Enterprise Library é formado por coleções de componentes agrupados sob a forma de application blocks. Cada uma destas estruturas engloba uma série de funcionalidades genéricas, sem uma relação direta com aspectos de um ramo de negócio e procurando atender a um tipo de demanda bem específica.

Dentre os diferentes blocos que fazem parte do Enterprise Library estão bibliotecas com recursos para a manipulação de bases de dados relacionais, tratamento de erros, validação, logging e injeção de dependências.

Além de proporcionar uma maior produtividade através da reutilização de soluções previamente testadas, os application blocks do Enterprise Library foram projetados incorporando práticas e padrões de eficácia.

Quanto às práticas favorecidas pelo Enterprise Library, merece destaque a questão envolvendo a construção de aplicações com uma menor dependência entre as diferentes partes que constituem a mesma.

Este relacionamento entre componentes de um software é conhecido como acoplamento, sendo que o ideal é que se diminua ao máximo possível esta interdependência. Um forte acoplamento é uma característica comumente encontrada em aplicações mal projetadas, sendo que mudanças em determinados pontos podem afetar de forma drástica tais sistemas.

Diversas são as soluções possíveis para isto, sendo que a técnica conhecida como injeção de dependências oferece uma alternativa bastante flexível para este tipo de demanda.

Neste segundo artigo o conceito de injeção de dependências será discutido em maiores detalhes, assim como o suporte que o Enterprise Library oferece para a implementação desta técnica. Para isto será finalizada a solução iniciada na edição passada, a partir do uso de recursos do Unity Application Block em uma aplicação construída sob a tecnologia ASP.NET MVC.

Injeção de Dependências: uma visão geral

A técnica conhecida como Injeção de Dependências procura fornecer meios para diminuir o acoplamento entre diferentes partes de um software. Em um artigo publicado em 2004 (“Inversion of Control Containers and the Dependency Injection pattern”), o renomado pesquisador Martin Fowler destacou que o padrão de Injeção de Dependências corresponde a uma variação de um conceito mais genérico chamado Inversão de Controle (termo este originário do inglês “Inversion of Control”, podendo ainda ser identificado pela forma abreviada “IoC”).

A Inversão de Controle é um pattern que enfatiza o uso da delegação, possibilitando a obtenção de classes mais coesas. A ideia principal por trás deste padrão é que um objeto transfira a execução de uma atividade específica a outro tipo, com a classe que serviu de base para a geração desta primeira instância não acumulando responsabilidades que vão além do seu objetivo inicial.

Na prática, o objeto que dispara uma ação deverá conhecer o mínimo possível de detalhes de implementação a respeito do segundo tipo para o qual é realizada tal chamada.

A Inversão de Controle é justamente o princípio seguido por frameworks e componentes projetados para reuso posterior. Importante destacar que tal separação de responsabilidades contribui para a obtenção de aplicações melhor estruturadas, menos propensas a falhas e mais flexíveis diante da necessidade quase certa de modificações futuras.

No que se refere ao padrão de Injeção de Dependências, caberá a frameworks e bibliotecas instanciar as classes das quais um ou mais tipos de objetos dependem em uma aplicação. Este processo acontece através de uma estrutura conhecida como “container” e envolve as seguintes atividades:

· Primeiramente, o container irá verificar de quais tipos depende um determinado objeto. Normalmente serão referenciadas interfaces nas classes consumidoras, como uma forma de diminuir o acoplamento para com implementações passíveis de modificações futuras. Embora menos comum, é possível ainda que sejam utilizadas classes abstratas ao invés de interfaces;

· Com as dependências identificadas, o container irá criar novas instâncias baseadas em classes concretas que implementem os tipos encontrados (ou mesmo reutilizar objetos gerados anteriormente, quando dispor de formas para controlar o ciclo de vida de objeto.

Este mapeamento entre interfaces (ou classes abstratas) e suas respectivas implementações pode ser feito via arquivos de configurações ou ainda, a partir do próprio código-fonte;

· Por fim, o container fará a associação das instâncias criadas a referências declaradas na classe consumidora. Tais declarações podem corresponder a propriedades públicas ou ainda, parâmetros que serão repassados a um construtor ou método público definido para fins de inicialização.

Outro ponto a ser destacado é o uso do Reflection (BOX 1) por soluções de injeção de dependências. Tal mecanismo fornece as informações e funcionalidades necessárias para a instanciação dinâmica de classes, auxiliando na resolução das dependências encontradas ao longo de uma aplicação.

BOX 1. Reflection

Reflection é uma técnica disponibilizada pelo .NET Framework através da qual é possível o acesso e a manipulação de informações (metadata) relativas a assemblies, classes, interfaces, propriedades, métodos, dentre outras estruturas de programação. É através da classe System.Type e de tipos definidos no namespace System.Reflection que são disponibilizados recursos para a utilização de informações de metadados.

Na Figura 1 é apresentado um exemplo de solução que faz uso de um containter para a injeção de dependências. Neste caso, foi considerada uma aplicação de vendas que possui compatibilidade com diferentes impressoras para a impressão de cupons fiscais (ECFs).

Procurando suportar os principais fornecedores de ECFs do mercado, este sistema hipotético foi modelado da seguinte maneira:

· Em uma inteface IImpressoraFiscal deverão constar as diferentes operações que poderão ser realizadas com uma impressora fiscal;

· Inicialmente está previsto suporte a equipamentos dos fornecedores A e B, representados pelas classes ImpressoraFiscalA e ImpressoraFiscalB, respectivamente. Estes tipos implementam a interface IImpressoraFiscal, com cada uma de suas operações realizando a comunicação com os ECFs através de drivers fornecidos pelos fabricantes;

· Uma classe responsável pela emissão de cupons fiscais e outras ações envolvendo estes tipos de documentos que não conhecerá as implementações concretas de IImpressoraFiscal. Tal tipo apenas espera por um objeto cuja classe correspondente implemente esta interface, sendo que a dependência aqui encontrada será resolvida (empregando definições de mapeamento em um arquivo de configurações) por um container, como o Unit Application Block. Este tipo de técnica para a instanciação dinâmica de classes é conhecido como “Late Binding”;

· Esta arquitetura privilegia um baixo acoplamento para com as implementações do tipo IImpressoraFiscal. Esta característica permitirá o suporte a novos tipos de ECFs, sem que isto implique em grandes modificações no código já existente (na verdade, grande parte do trabalho consistirá apenas na codificação de uma nova classe que derive da interface IImpressoraFiscal).

Figura 1. Exemplo de uso da técnica de Injeção de Dependências

Além das questões envolvendo um baixo acoplamento, optar pelo uso de técnicas de injeção de dependências em projetos de software pode trazer como benefícios:

· Uma maior flexibilidade diante da necessidade de mudanças. Desde que bem planejadas, alterações poderão ser introduzidas em pontos isolados de uma aplicação (classes que normalmente implementam interfaces utilizadas em outras partes do sistema), sem que isto acarrete necessariamente em efeitos colaterais;

· A facilidade na implementação de arquiteturas focadas na extensibilidade dos sistemas resultantes. O conceito conhecido como “extensibilidade” é típico de sistemas projetados para crescimento futuro. Novos comportamentos podem ser adicionados a tais soluções, sem que isto implique na modificação de funcionalidades já existentes.

Este caráter plugável pode então ser conseguido sem grandes dificuldades, a partir do uso de interfaces bem definidas (como no exemplo citado anteriormente envolvendo impressoras fiscais);

· Sistemas com uma melhor testabilidade, ou seja, a capacidade de integração com uma aplicação a mecanismos para a execução de testes unitários automatizados. A execução destas verificações nem sempre será possível, muitas vezes por questões envolvendo custos relacionados ao uso de ambientes (como recursos hospedados na nuvem).

A utilização de técnicas de injeção de dependência é muito útil em tais casos, já que permite que stubs e mocks (BOX 2) sejam c ...

Quer ler esse conteúdo completo? Tenha acesso completo