.NET - Design Patterns - Identificando e aplicando padrões - II


Na primeira parte deste artigo demos alguns conselhos para ajudar a identificar e aplicar padrões de projetos e agora vamos por em prática toda essa teoria em um exemplo simples e objetivo.

É muito bom falar sobre como os padrões e princípios são importantes e podem nos ajudar , mas mais importante é vê-los em ação na prática. Com isto em mente, este artigo examina como um simples pedaço de código ASP.NET que você já deve ter visto inúmeras vezes antes pode ser melhorado com o uso de padrões de projetos.

Vamos examinar um trecho de código que você pode encontrar em um aplicativo de comércio eletrônico típico que recupera recupera todos os produtos de uma determinada categoria.

Na figura abaixo vemos o diagrama de classe contendo uma classe ProdutoService com o método GetTodosProduto, uma classe Produto que representa os produtos da loja, e uma classe ProdutoRepository que é usada para recuperar os produtos de um banco de dados.

O trabalho da classe ProdutoService é coordenar a recuperação de uma lista de produtos a partir do repositório para um dado código (ID)
de categoria e depois armazenar os resultados em cache de forma que a próxima chamada pode ser executado mais rapidamente.

Vamos então criar um projeto usando o Visual Studio Express 2012 for web, criar as classes e examinar o código.

Abra o VS Express 2012 for web e no menu Start clique em New Project;

A seguir selecione template Other Project Types-> Visual Studio Solutions -> Blank Solution , informe o nome ASPNET_PadraoProjeto e clique no botão OK;

A seguir no menu FILE clique Add -> New Project e selecione o template Visual C# -> Class Library informando o nome ASPNET_PadraoProjeto_Service e clique no botão OK;

Será criado o projeto ASPNET_PadraoProjeto_Service e a classe Class1.cs. Renomeie o arquivo Class1.cs para Produto.cs e conforme mostrado na figura abaixo:

Vamos incluir uma nova classe ao projeto chamada ProdutoRepository via menu PROJECT -> Add Class contendo o seguinte código:

Crie agora a classe ProdutoService via menu PROJECT -> Add Class com o código mostrado a seguir:

Observe que temos que incluir uma referência a System.Web ao projeto pois estamos usando a classe HttpContext que encapsula todas as informações do HTTP específico sobre uma solicitação HTTP individual.

As classes Produto e ProdutoRepository não necessitam de qualquer explicação, porque eles são simples espaços reservados neste cenário. A classe ProdutoService usando o método GetTodosProdutos coordena a recuperação dos produtos a partir do cache, e no caso do cache estar vazio, a recuperação dos produtos a partir do repositório e a inserção no cache.

Este é um cenário típico e encontrado com frequência.

Mas então o que esta errado com o código acima ? Você saberia identificar quais os problemas ele carrega ?

O Velocity.net é um serviço de cache distribuido que é facilmente instalado em multiplos servidores e pode ser acessado pelo .NET
através de classes criadas no namespace
System.Data.Cache. (http://www.microsoft.com/en-us/download/details.aspx?id=2517)

O Memcached é um sistema de cache em memória distribuido muito fácil de usar. Site oficial: http://www.memcached.org

Agora que você já sabe o que esta de errado com o código acima vem a pergunta que não quer calar :

Quais as providências você deverá tomar para melhorar o código usando padrões de projetos ?

Vamos começar com o problema da dependência entre classe ProductService e a classe ProductRepository.

No cenário atual a classe ProdutoService é frágil, visto que se a classe ProdutoRepository for alterada ela provavelmente terá que sofrer modificações e isso vai contra dois princípios importantes: a separação de responsabilidades e o princípio da responsabilidade única.

Como resolver esse impasse ?

Aplicando o princípio da Inversão de dependência.

Este princípio nos diz que não podemos depender de uma classe concreta mas de uma interface : Programe para uma interface e não para uma implementação. (classe concreta).

Vamos começar com o problema da dependência entre classe ProductService e a classe ProductRepository.

No cenário atual a classe ProdutoService é frágil, visto que se a classe ProdutoRepository for alterada a ela provavelmente terá que sofrer modificações e isso vai contra dois princípios importantes : a separação de responsabilidades e o princípio da responsabilidade única.

Podemos empregar o princípio da inversão da dependência para desacoplar a classe ProdutoService da classe ProdutoRepository fazendo com que ambas dependam de uma abstração - uma interface.

Vamos abrir a classe ProdutoRepository refatorar a classe extraindo uma interface. Você pode fazer isso usando a refatoração ( O Visual Studio possui este recurso nativo) ou criando você mesmo o código.

Devemos criar a interface IProdutoRepository, via menu PROJECT-> Add New Item, com o código mostrado a seguir:

A seguir vamos ajustar a classe ProdutoRepository para implementar a nova interface recém-criada:

Finalmente precisamos atualizar a classe ProdutoService para garantir que ele faz referência a interface ao invés da classe concreta:

O que você conseguiu através da introdução de uma nova interface ?

A classe ProductService agora depende apenas de uma abstração em vez de uma classe concreta, o que significa que a classe agora é completamente ignorante de qualquer implementação, assegurando que ela é menos frágil e que o seu código base é menos sujeito a mudanças.

No entanto, existe um pequeno problema: a classe ProdutoService ainda é responsável pela implementação concreta, e, atualmente é impossível testar o código sem uma classe ProdutoRepository válida.

O que fazer para resolver isso ?

Aqui entra a Injeção de dependência para nos ajudar a solucionar este problema.

Aplicando o princípio da Injeção de dependência

A classe ProdutoService ainda está acoplada à implementação concreta da classe ProdutoRepository porque é de responsabilidade da classe ProdutoService criar essa instância. Isto pode ser visto no construtor da classe.

Usando a Injeção de dependência podemos mover a responsabilidade de criar a implementação de ProdutoRepository para fora da classe ProdutoService e fazer a injeção da dependência através do construtor da classe, tal como pode ser visto na listagem de código a seguir:

Note que removemos a linha de código que implementa a classe concreta:

_produtoRepository = new ProdutoRepository();

Injetando a dependência da interface:

_produtoRepository = produtoRepository;

Isso permite que um substituto possa ser passado para a classe ProdutoService durante os testes, o qual permite que você teste a classe isoladamente.

Ao remover a responsabilidade de obter dependências de ProdutoService, você está garantindo que a classe adere ao princípio da responsabilidade única, visto que agora ela só se preocupa com a recuperação dos dados do cache ou repositório e não mais em criar uma implementação concreta de IProductRepository.

A Injeção de Dependência pode ser aplicada de 3 maneiras distintas: via Construtor, via Método e via Propriedade.

Usamos neste nosso exemplo a injeção de dependência via construtor.

A última coisa que precisamos fazer agora é resolver a dependência do contexto HTTP para o cache. Para isso, vamos contratar os serviços de um padrão de design simples.

Refatorando o padrão Adapter

Como não temos o código-fonte para a classe de contexto HTTP, não podemos simplesmente criar uma interface da mesma forma que fizemos para a classe ProdutoRepository.

Felizmente, esse tipo de problema foi resolvido inúmeras vezes antes, e existe um padrão de projeto pronto para nos ajudar: o padrão de projeto Adapter.

O Padrão Adapter basicamente traduz uma interface para uma classe em uma interface compatível para que você possa aplicar esse padrão para mudar a API Context cache HTTP em uma API compatível que você deseja usar. Então você pode injetá-la através de uma interface para a classe ProdutoService usando o principio da injeção de dependência.

Vamos criar uma interface chamada ICachePersistencia, via menu PROJECT-> Add New Item -> Interface, com o seguinte código:

Agora que temos a nova interface, podemos atualizar a classe ProdutoService para usá-la ao invés de usar a implementação do contexto HTTP.

Temos a seguir o código da classe ProdutoService alterado já usando a interface ICachePersistencia:

Agora o nosso problema é que a API Context Cache HTTP não pode implementar implicitamente a nova interface ICachePersistencia.

Como o padrão Adapter pode ser usado para nos salvar desse impasse ???

Qual o objetivo do padrão Adapter ?

Seu objetivo é : Converter a interface de uma classe em outra interface esperada pelos clientes.

È exatamente isso que precisamos aqui...

Abaixo vemos o diagrama UML representando o padrão Adapter:

fonte: https://www.l2f.inesc-id.pt/~david/wiki/pt/index.php/Adapter_Pattern_(padr%C3%A3o_de_desenho)

Como vemos na figura, um cliente tem uma referência a uma abstração - o Target.

Neste caso esta é a interface ICachePersistencia.

O adaptador é uma implementação da interface de Target e simplesmente delega o método de operação para o Adaptee que gera o seu próprio método SpecificOperation.

Vemos que o adaptador simplesmente envolve uma instância do Adaptee e delega o trabalho dele enquanto implementa o contrato da interface Target.

Ajustanto o diagrama para o nosso exemplo chegamos ao seguinte diagrama:

Vemos a classe do projeto e a classe Adapter que precisamos implementar usando o padrão Adapter com o contexto HTTP cache de API.

A classe HttpContextCacheAdapter é um invólucro para o cache Context HTTP e delega o trabalho para os seus métodos.

Para implementar o padrão Adapter precisamos criar classe HttpContextCacheAdapter;

Assim inclua uma nova classe no projeto, via menu PROJECT -> Add Class, chamada HttpContextCacheAdapter.cs com o seguinte código no projeto:

Agora é mais fácil implementar uma solução de cache nova, sem afetar qualquer código existente. Por exemplo, se você quiser usar o Velocity tudo que você precisa fazer é criar um adaptador que permite que a classe ProdutoService possa interagir com o provedor de cache de armazenamento através da interface comum ICachePersistencia.

O padrão Adapter também simples, seu único propósito é fazer com que as classes com interfaces incompatíveis trabalhem em conjunto.

O adaptador não é o único padrão que pode nos ajudar com o cache de dados.

Existe um padrão de projeto chamado Proxy que também pode ser usado mas isso é assunto para outro artigo.

Vimos assim como a aplicação de alguns padrões básicos pode desacoplar o seu código tornando-o mais robusto e testável.

Pegue o projeto completo aqui: ASPNET_PadraoProjeto.zip

João 6:63 O espírito é o que vivifica, a carne para nada aproveita; as palavras que eu vos tenho dito são espírito e são vida.

João 6:64 Mas há alguns de vós que não crêem. Pois Jesus sabia, desde o princípio, quem eram os que não criam, e quem era o que o havia de entregar.

Referências:


José Carlos Macoratti