Categorias
Arquitetura C# Padrões Programação

C# with Patterns: Adapters

Na série C# with Patterns, que mostra como usar padrões de arquitetura em programação com C#, vamos conhecer o padrão de adaptadores, ou Adapters Pattern.

Digamos que você tem uma classe chamada Person, com uma dependência na interface IPerson.

Por outro lado, você outra classe chamada Customer, que seria ideal para injetar na classe Person como dependência, mas a classe Customer não implementa a interface necessária

Outra situação é quando você precisa avaliar alguma dependência fortemente acoplada. Um cenário comum é quando são usadas diretamente algumas classes .NET, digamos, um método HttpRuntime que você queira testar. Você tem que ter uma instância HttpRuntime válida para executar o teste, senão ele irá falhar e não será aceito. A solução é fazer com que o método dependa de alguma abstração que será injetada. A Questão é: como extrair uma classe HttpRuntime e implementar alguma interface a partir dela? Afinal, é bem possível que você não tenha acesso à implementação da classe HttpRuntime, certo?

A solução é escrever um adaptador que fique entra a classe Person e a Classe Customer, que envolva as funcionalidades da classe Customer.




Um exemplo do mundo real

Se você viaja do Brasil para a Suécia e tentar conectar seu computador na tomada do hotel, não conseguirá, pois os padrões de conexões elétricas são diferentes dos brasileiros.

A classe Person é a flecha de conexão e a classe Customer é a tomada na parede. os dois objetos claramente não são compatíveis, mas temos uma solução: você pode inserir um adaptador na tomada e fazer a “conversão” entre a flecha e a tomada. Resumindo: o adaptador permitiria que as classes funcionem juntas, o que não seria possível devido à incompatibilidade de interfaces. O adaptador assumirá a forma da interface tendo o benefício adicional da extensibilidade: você pode escrever várias implementações a partir desta interface de adaptador, garantindo que seu método não esteja fortemente acoplado a uma única classe concreta.

Demonstração

Abra o Visual Studio e crie uma nova solução em branco chamada AdapterPattern. Adicione uma solution folder chamada Domain e dentro desta pasta um projeto do tipo Class Library chamado AdapterPattern.Domain.

No projeto AdapterPattern.Domain, exclua o arquivo Class1.cs, e crie uma pasta chamada Entities. Adicione em Entities uma classe chamada Customer. Não adicione nenhum propriedade, não é o foco agora.

Nós precisamos expor as operações de repositório por uma interface, então crie uma pasta chamada Interfaces, e dentro desta pasta outra chamada Repositories no projeto de domínio, e por fim adicione uma interface chamada ICustomerRepository à pasta.

Na solução, crie uma nova Solution folder chamada Infra e dentro desta pasta outra chamada Data. Em Data adicione um novo projeto do tipo Class Library chamado AdapterPattern.Infra.Data e adicione uma referência ao projeto de domínio.

Dentro deste novo projeto, crie uma pasta chamada Repositories e por fim, nesta pasta, adicione uma classe chamada CustomerRepository que implemente a interface ICustomerRepository.

Na solução, crie uma nova Solution folder chamada Application e adicione um novo projeto do tipo Class Library chamado AdapterPattern.Application e adicione referência aos projetos de domínio de dados e ao Assembly System.Web. E Dentro deste novo projeto adicione uma classe chamada CustomerService.

Este pequeno trecho de código é simples: nós precisamos retorna uma lista de clientes usando uma dependência abstrata de ICustomerRepository. Nós verificamos se a lista está disponível no cache do HttpContext. Se for este o caso, converta o valor em cache e retorne os clientes. Caso contrário, procure a lista de do repositório e armazene em cache.

Então, o que há de errado com o método GetAllCustomers?

Testabilidade

O método é difícil de testar devido à sua dependência com a classe HttpContext. Se você quiser obter algum resultado confiável via teste do comportamento deste método, você precisará fornecedor um objeto HttpContext válido. Caso contrário, se o teste falhar, então, por que ele falhou? Foi uma falha genuína, o que significa que a lista de cliente não foi carregada? Ou por que não havia um objeto HttpContext disponível? É a abordagem errada que torna o resultado de saída dependente de um objeto tão volátil.

Flexibilidade

Com esta implementação, estamos presos no HttpContext e sua solução de cache. E se quisermos mudar para um diferente, como Memcache ou System.Runtime? Neste caso, precisamo editar o código e alterar para um nova. Pior ainda, digamos que todos as suas classes de serviço usam HttpContext como solução de cache e você deseja fazer uma transição para uma nova solução de cache para todas elas. Você já deve ter percebido o quento seria tedioso, demorado e propenso a erro isto poderia ser.

Outra anotação: o método também viola o Princípio de Responsabilidade Única, pois, além de listar os cliente, realiza o caching. Sinceramente falando, não deveríamos estar fazendo isso, pois causa o um efeito colateral difícil de detectar. A solução para este problema está no padrão Decorator, que é assunto para outro post.

Solução

É claro que você deve reavaliar o objeto HttpContext.Current.Cache e deixar o cliente de ConsumerService injetá-lo em vez disso, usando um princípio de abordagem chamado de Injeção de Dependência. Como de costume, o ideal é escrever uma classe abstrata que encapsula funções de cache. Para isso, crie uma pasta chamada Interfaces no projeto AdapterPattern.Application, e dentro desta pasta, crie uma interface chamada ICacheStorage, como segue:

Acredito que este código é bastante claro. Em seguida, precisamos alterar a classe CustomerService para que utilize a nova interface:

Nós nos livramos do objeto HttpContext, então a próxima tarefa é injetar ele de algum forma através da interface. Esta é a essência do padrão de adaptadores: se escreve uma classe adaptadora que irá resolver a incompatibilidade entre a sua interface personalizada ICacheStorage e o objeto HttpContext.Current.Cache. A solução é realmente simples. Adicione uma nova classe ao projeto de aplicação chamada HttpContextCacheStorage:

Esta é a classe adaptadora que encapsula o caching do objeto HttpContext. Você pode injetar agora esta classe concreta em CustomerService. Se no futuro você precisar usar outra solução de cache, basta você escrever outro adaptador para isso, que seja MemCached, Velocity, System.Runtime.Cache, ou qualquer outro que você escolha.

Acesse o projeto no GitHub.

Texto traduzido e adaptado do post do Andras NemesDesign patterns and practices in .NET: the Adapter Pattern.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.