Na primeira parte deste artigo demos alguns conselhos para ajudar a identificar e aplicar padrões de projetos. 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 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 possa ser executada 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, 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étodoGetTodosProdutos 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?
- A classe ProdutoService depende da classe ProdutoRepository. Se ProdutoRepository for alterada, vamos precisar alterar também a classe ProductService, e isso não é bom.
- O código não pode ser testado facilmente. Sem ter uma classe ProdutoRepository que se conecte com um banco de dados real, faz com que você não possa testar o método ProdutoServicepor, causa do forte acoplamento que existe entre essas duas classes. Outro problema relacionado com o teste é a dependência com contexto HTTP para o uso com o cache dos produtos. É difícil testar o código, pois ele está muito vinculado ao contexto HTTP.
- Você está preso ao contexto HTTP para o cache. Em seu estado atual, se você precisar usar um cache diferente para armazenamento, tal como o Velocity ou Memcached, exigiria uma alteração da classe ProdutoService e qualquer outra classe que usa o cache.
O Velocity.net é um serviço de cache distribuido que é facilmente instalado em múltiplos servidores e pode ser acessado pelo .NET
através de classes criadas no namespace System.Data.Cache.
O Memcached é um sistema de cache em memória distribuido muito fácil de usar.
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, 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 e 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 classeProdutoRepository 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 que 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 três maneiras distintas: via Construtor, Método e 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 e mudar a API Context cache HTTP para uma API compatível que você deseje usar. Então, você pode injetá-la através de uma interface para a classe ProdutoService usando o príncipio 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? É converter a interface de uma classe em outra interface esperada pelos clientes. E é exatamente isso que precisamos aqui.
Abaixo vemos o diagrama UML representando o padrão Adapter:

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