[Delphi] Design Patterns GoF – Adapter

Saudações, pessoal!
Neste artigo, iniciaremos o estudo dos padrões de projeto da categoria Estruturais! Recebi bons feedbacks dos artigos relacionados aos padrões de projeto criacionais, e espero que os próximos tenham a mesma repercussão.
O primeiro deles é o Adapter. Na minha opinião, é um dos Design Patterns mais fáceis de compreender.

Introdução

Para iniciar o texto, um fato: desde 2010, o padrão de tomadas NBR 14136 entrou em vigor no Brasil para substituir os diferentes tipos que haviam no mercado. A sua estrutura traz um terceiro pino (central) para a função de aterramento:

Padrão NBR 14136 de tomadas

Porém, na época, a grande maioria de brasileiros ainda possuíam eletrodomésticos e ferramentas com o formato antigo de tomadas e, claro, a solução não era simplesmente descartá-los e comprar novos utilitários atualizados com o novo padrão. Durante essa fase de transição, a melhor alternativa era adquirir um adaptador que convertia o velho padrão no novo:

Adaptador de tomadas

Bom, acho que só essa analogia já transmite um pouco da ideia, não é? 🙂

Adapter

A função do Adapter é justamente essa: atuar como um intermediário entre duas interfaces de modo que elas possam se comunicar, ou melhor, tornem-se compatíveis.

Recordam-se do artigo “30 dias com o WebService da GINFES“, que publiquei em 2012? Sei que muitos não o leram (por ser antigo), mas, no artigo, faço menção da ferramenta WSDL Importer do Delphi, que interpreta um endereço WSDL e produz uma unit com as declarações das funções que o WebService disponibiliza. Essa unit é um exemplo perfeito de Adapter. A aplicação cliente, por si só, não consegue se comunicar diretamente com o WebService, portanto, é necessário uma classe para exercer uma função de adaptador entre os dois lados.

Embora o Adapter possa ser empregado em várias situações, é comum encontrá-lo nos seguintes cenários:

  • Integração com tecnologias externas, como WebServices para consultas de dados, pagamentos ou envio de arquivos;
  • Integração entre sistemas diferentes, como aplicações desktop com aplicações web ou mobile;
  • Migração de dados, quando, por exemplo, uma empresa adquire outra do mesmo segmento e pretende unificar as bases de dados.

Geralmente, o Adapter é implementado em projetos que já estão em produção e, por algum motivo, devem “adaptar-se” a um novo ambiente, encaixando-se em uma das três situações acima.

Entrando em detalhes mais técnicos, o Adapter é formado por 4 elementos:

  • Client: cliente que irá consumir os objetos;
  • Target: define a interface que o Client usará;
  • Adaptee: consiste na classe que precisa ser adaptada;
  • Adapter: realiza a adaptação entre as interfaces.

Exemplo de codificação do Adapter

Para a nossa aplicação prática do padrão, pensei em uma rotina de busca de endereço (logradouro, bairro e cidade) pelo CEP utilizando uma API de terceiros.

Antes de iniciar, ressalto que a estrutura de classes do Adapter pode parecer um pouco confusa no início, portanto, vou entrar bastante em detalhes.

Vamos considerar que, atualmente, a nossa aplicação faz uso do serviço do ViaCEP para consultar o endereço. Esse serviço permite acessar um endereço específico, informando o CEP como parâmetro, para retornar os dados em formato XML.

Primeiramente, temos uma Interface relacionada aos métodos de consulta do CEP:

Em seguida, temos também uma classe responsável pela comunicação com o WebService para enviar/trazer os dados, que implementa a Interface acima:

O objeto XMLDocument armazena o retorno da consulta, ou melhor, os dados do endereço. Como o utilizaremos em mais de um método, achei viável declará-lo como uma variável da classe. Logo, os métodos Create e Destroy são declarados apenas para instanciar e liberar essa variável da memória:

O método de consulta é bem simples. Apenas acessa o endereço do ViaCEP e atribui o resultado no objeto XMLDocument:

Por fim, as três últimas funções acessam os nodes (nós) do XML para obter os dados consultados:

Até o momento, a rotina funciona perfeitamente e nenhum adaptador é necessário, portanto, a seguir, temos apenas dois elementos: o Target (agente que solicita a busca) e o Client (que consome o Target). No nosso exemplo, o Client é uma simples tela para informar o CEP. O Target, por sua vez, possui o nome de TComunicador:

Observe que o construtor recebe um objeto de uma classe que implementa a Interface IWebServiceViaCEP. Como vimos anteriormente, este objeto é o que consulta, efetivamente, o CEP no serviço. TComunicador é apenas uma ligação entre o Client e o serviço de busca, semelhante a um Controller.

Os métodos Create e Destroy associam e desassociam, respectivamente, a variável da classe referente ao objeto da classe de consulta:

Por fim, há a implementação do método principal da classe, encarregado de utilizar o objeto da classe de consulta para buscar o endereço e retornar o resultado como itens de uma TStringList:

Opa, já começou a ficar mais claro! Agora, para entendermos por completo, veremos como o Client (tela) processa essa ação:

Notou que utilizei a palavra “injetar” nos comentários? Isso é muito importante! Essa “injeção” cria uma composição de objetos para trabalharem em conjunto.

A necessidade de um adaptador

Pois bem, pessoal, eis que surge um cenário: recentemente, alguns clientes que usam a aplicação solicitaram que a busca do CEP passe a ser realizada pelo WebService dos Correios, e não mais pelo ViaCEP. Embora a alteração pareça simples, a busca de CEP pelos Correios é realizada por WSDL (Web Services Description Language), bem diferente do ViaCEP, que bastava apenas acessar um endereço.

Nessa situação, teremos que garantir que a implementação da rotina dos Correios não impacte na funcionalidade já existente do ViaCEP. Em outras palavras, a nossa missão é fazer com que as duas consultas se comportem da mesma maneira sem a necessidade de alterar a classe de consulta do ViaCEP (TWebServiceViaCEP). Essa é a motivação do Adapter!

Nas próximas linhas de código, você notará que faremos o Target “conversar” com o WebService dos Correios por meio de um adaptador.
Antes de prosseguir, uma nota muito importante: quando estudei o Adapter, pensei que a classe já existente que deveria ser adaptada, mas é o inverso! A nova classe é que passará por uma “adaptação” para tornar-se compatível com o ambiente atual. No nosso caso, a rotina dos Correios que será adaptada.

Bom, então, mãos à massa!

Após a importação do WSDL, o primeiro passo é analisar e criar a classe de consulta dos Correios. Neste exemplo, ela terá a seguinte estrutura:

Observe que os métodos são bem diferentes da classe de consulta do ViaCEP. O parâmetro de consulta e o retorno são objetos ao invés de tipos primitivos.

O método DefinirParametrosConsulta cria um objeto que o WebService exige para consulta e o preenche com o CEP informado pelo usuário:

Para consultar o CEP e receber a resposta, é necessário instanciar um componente do tipo THTTPRIO, responsável por produzir requisições SOAP e enviá-las ao WebService. Interface disponibilizada pelos Correios, conforme o manual de integração, chama-se AtendeCliente.

A função ObterResposta acessa as propriedades do objeto de resposta conforme o tipo de informação solicitado no parâmetro:

E o Destroy, por fim, libera o objeto de parâmetro da memória:

Claro, poderíamos criar um novo comunicador exclusivo para os Correios, mas, neste caso, estaríamos aumentando a complexidade ciclomática da nossa aplicação. Além disso, imagine que essa rotina seja empregada em várias partes da aplicação, como cadastro de clientes, fornecedores, parceiros, funcionários e filiais. A nossa intenção é modificar o mínimo possível do código para não comprometer o funcionamento em cada um desses locais.

Classe Adaptee

O próximo passo é implementar a solução do problema: o Adapter! A classe abaixo adaptará o objeto da classe dos Correios para torná-la compatível com a classe do ViaCEP. Logo, a classe dos Correios é o nosso Adaptee.

Notou algo familiar?

A classe implementa a Interface IWebServiceViaCEP, a mesma utilizada para a classe de consulta do ViaCEP, apenas com uma particularidade: ela recebe um objeto de consulta dos Correios no construtor:

Agora, preste atenção na implementação de cada método:

Agora ficou claríssimo! O adaptador “simula” a classe do ViaCEP, com os mesmos nomes de métodos, porém, utiliza o objeto da classe dos Correios para realizar a busca e obter o retorno.

amos tirar a prova? Veja a diferença do método ConsultarEnderecoWebService entre as duas classes abaixo. Embora realizem ações diferentes, o nome do método que será consumido pelo Target é o mesmo.

Entendi, mas como fica o Client?
Legal, chegamos na última parte! A chamada no Client sofre uma pequena alteração. Ao invés de injetarmos um objeto do tipo TWebServiceViaCEP, injetaremos um objeto do tipo TAdapter, já que implementam a mesma Interface:

O que mudou no Client? Adicionamos apenas 1 linha, referente ao adaptador. Todo o resto é idêntico, salvo que instanciamos um objeto da classe dos Correios ao invés do ViaCEP. Viram como é interessante?

Conclusão

Sei que parece muita coisa, concordo. O conceito do Adapter é de fácil compreensão, mas a implementação tem uma complexidade um pouco mais alta. No entanto, as vantagens são visíveis:

  • A classe com funcionamento já consolidado (ViaCEP) não foi modificada;
  • Adicionamos apenas 1 linha no método original;
  • Podemos criar outros adaptadores conforme necessário (por exemplo, BuscarCEP);
  • Todos os adaptadores implementarão uma única Interface.

Bom, leitores, entendo que o exemplo ficou bastante extenso. Por isso, recomendo que vocês baixem o exemplo no link abaixo e veja o Adapter funcionando na prática. A tela principal do projeto traz 2 botões: o primeiro aciona a consulta pelo ViaCEP e o segundo utiliza o WebService dos Correios através de um adaptador. De qualquer forma, deixei o código bem documentado para uma melhor compreensão.

Ah, uma observação: não sei o motivo, mas, no projeto de exemplo, o request gerado pelo componente THTTPRIO retorna um erro ao ser processado. Por conta disso, tive que sobrescrever a requisição SOAP originalmente gerada pelo componente THTTPRIOadaptando-a para a estrutura exigida pelo WebService dos Correios.

Antes de encerrar o artigo, gostaria de compartilhar um comentário. Certa vez, um colega de trabalho perguntou: “Podemos dizer que o Adapter é uma solução paliativa, não é? O ideal não seria remodelar a modelagem de classes para atender aos novos padrões?”. Bom, concordo em partes. Usando a analogia dos padrões de tomadas, praticamente todos os brasileiros já trocaram de eletrodomésticos hoje em dia, uma vez que o padrão entrou em vigor há 6 anos, portanto, os adaptadores estão gradativamente caindo em desuso, salvo exceções. Seguindo este mesmo raciocínio, classes adaptadoras também podem se tornar obsoletas. Neste caso, sim, uma remodelagem é uma ação factível.

No entanto, discordo da afirmação de que este Design Pattern é um paliativo. Pelo contrário. O Adapter é uma solução sólida e pontual para um problema de incompatibilidade no sistema. Sendo assim, enquanto essa incompatibilidade existir, o Adapter, ao meu ver, é uma das melhores alternativas.

 

Hora de partir, leitores!
Vejo vocês no artigo do próximo padrão de projeto estrutural!


André Celestino