Dev & EngARTIGO

.NET – Design Patterns – Identificando e aplicando padrões – Parte 02

Na primeira parte demos alguns conselhos para identificar e aplicar padrões de projetos. Agora vamos por em prática essa teoria.

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↳.NET113 conteúdosNovidades do .NET 9 – o tipo genérico OrderedDictionaryDev (Back & Front) · dez 2024Novidades .NET 10: novas formas de uso do .NET CLIDev (Back & Front) · dez 2025Novidades do .NET 5: implementando um proxy reverso com YARP + ASP.NET 5Dev (Back & Front) · nov 2020Ver tudo em Dev (Back & Front) → 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↳E-commerce21 conteúdosECBR Club: novo espaço para devs se conectarem ao ecossistema de e-commerceDev (Back & Front) · mai 2025TOTVS anuncia joint venture com VTEXMarketing Tech · mai 2019Jovens da Brasilândia recebem formação gratuita em tecnologiaGestão Dev & TI · jul 2025Ver tudo em Marketing Tech → 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↳Banco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data →.

net_dp21

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.

net_dp22

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.

net_dp23

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:

net_dp24

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

net_dp25

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.

net_dp26

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:

net_dp28

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

net_dp29

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

net_dp2a

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:

net_dp2b

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:

net_dp2c

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:

net_dp2d

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:

fonte: https://www.l2f.inesc-id.pt/~david/wiki/pt/index.php/Adapter_Pattern_(padr%C3%A3o_de_desenho)
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:

net_dp2f

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:

net_dp2g

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

é referência em Visual Basic no Brasil e autor dos livros "Aprenda Rápido: ASP" e "ASP, ADO e Banco de Dados na Internet". Mantenedor do site macoratti.net.

Mais de José Carlos Macoratti
Ver perfil →