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:
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:
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:
1 2 3 4 5 6 7 |
type IWebServiceViaCEP = interface procedure ConsultarEnderecoWebService(const Cep: string); function ObterLogradouro: string; function ObterBairro: string; function ObterCidade: string; end; |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
type TWebServiceViaCEP = class(TInterfacedObject, IWebServiceViaCEP) private XMLDocument: IXMLDocument; public constructor Create; destructor Destroy; override; procedure ConsultarEnderecoWebService(const Cep: string); function ObterLogradouro: string; function ObterBairro: string; function ObterCidade: string; end; |
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:
1 2 3 4 5 6 7 8 9 10 |
constructor TWebServiceViaCEP.Create; begin XMLDocument := TXMLDocument.Create(nil); end; destructor TWebServiceViaCEP.Destroy; begin XMLDocument := nil; inherited; end; |
O método de consulta é bem simples. Apenas acessa o endereço do ViaCEP e atribui o resultado no objeto XMLDocument
:
1 2 3 4 5 |
procedure TWebServiceViaCEP.ConsultarEnderecoWebService(const Cep: string); begin XMLDocument.FileName := Format('https://viacep.com.br/ws/%s/xml/', [Cep]); XMLDocument.Active := True; end; |
Por fim, as três últimas funções acessam os nodes (nós) do XML para obter os dados consultados:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function TWebServiceViaCEP.ObterBairro: string; begin result := VarToStr(XMLDocument.DocumentElement.ChildValues['bairro']); end; function TWebServiceViaCEP.ObterCidade: string; begin result := VarToStr(XMLDocument.DocumentElement.ChildValues['localidade']); end; function TWebServiceViaCEP.ObterLogradouro: string; begin result := VarToStr(XMLDocument.DocumentElement.ChildValues['logradouro']); end; |
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
:
1 2 3 4 5 6 7 8 9 10 |
type TComunicador = class private WebServiceViaCEP: IWebServiceViaCEP; public constructor Create(WebServiceViaCEP: IWebServiceViaCEP); destructor Destroy; override; function ConsultarEndereco(const Cep: string): TStringList; end; |
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:
1 2 3 4 5 6 7 8 9 10 |
constructor TComunicador.Create(WebServiceViaCEP: IWebServiceViaCEP); begin Self.WebServiceViaCEP := WebServiceViaCEP; end; destructor TComunicador.Destroy; begin WebServiceViaCEP := nil; inherited; end; |
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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
function TComunicador.ConsultarEndereco(const Cep: string): TStringList; var Dados: TStringList; begin WebServiceViaCEP.ConsultarEnderecoWebService(Cep); Dados := TStringList.Create; Dados.Values['Logradouro'] := WebServiceViaCEP.ObterLogradouro; Dados.Values['Bairro'] := WebServiceViaCEP.ObterBairro; Dados.Values['Cidade'] := WebServiceViaCEP.ObterCidade; result := Dados; end; |
Opa, já começou a ficar mais claro! Agora, para entendermos por completo, veremos como o Client (tela) processa essa ação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var WebServiceViaCEP: TWebServiceViaCEP; Comunicador: TComunicador; Retorno: TStringList; begin // instancia o objeto da classe de consulta WebServiceViaCEP := TWebServiceViaCEP.Create; // instancia o comunicador (Target), injetando o objeto de consulta Comunicador := TComunicador.Create(WebServiceViaCEP); Retorno := TStringList.Create; try // o Target consulta o endereço (utilizando o objeto injetado) e retorna os dados Retorno := Comunicador.ConsultarEndereco(EditCEP.Text); EditLogradouro.Text := Retorno.Values['Logradouro']; EditBairro.Text := Retorno.Values['Bairro']; EditCidade.Text := Retorno.Values['Cidade']; finally FreeAndNil(Retorno); FreeAndNil(Comunicador); end; end; |
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:
1 2 3 4 5 6 7 8 9 10 11 |
type TWebServiceCorreios = class private Parametros: consultaCEP; Resposta: enderecoERP; public destructor Destroy; override; procedure DefinirParametrosConsulta(const Cep: string); procedure ConsultarCEP; function ObterResposta(const Informacao: string): string; end; |
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:
1 2 3 4 5 6 |
procedure TWebServiceCorreios.DefinirParametrosConsulta( const Cep: string); begin Parametros := consultaCEP.Create; Parametros.cep := Cep; end |
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. A Interface disponibilizada pelos Correios, conforme o manual de integração, chama-se AtendeCliente
.
1 2 3 4 5 6 7 8 9 |
procedure TWebServiceCorreios.ConsultarCEP; var Correios: AtendeCliente; HTTPRIO: THTTPRIO; begin HTTPRIO := THTTPRIO.Create(nil); Correios := GetAtendeCliente(True, '', HTTPRIO); Resposta := Correios.consultaCEP(Parametros).return; end; |
A função ObterResposta
acessa as propriedades do objeto de resposta conforme o tipo de informação solicitado no parâmetro:
1 2 3 4 5 6 7 8 9 10 11 12 |
function TWebServiceCorreios.ObterResposta( const Informacao: string): string; begin result := EmptyStr; if Informacao = 'Logradouro' then result := Resposta.end_ else if Informacao = 'Bairro' then result := Resposta.bairro else if Informacao = 'Cidade' then result := Resposta.cidade; end; |
E o Destroy
, por fim, libera o objeto de parâmetro da memória:
1 2 3 4 5 |
destructor TWebServiceCorreios.Destroy; begin FreeAndNil(Parametros); inherited; end; |
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.
1 2 3 4 5 6 7 8 9 10 11 |
type TAdapter = class(TInterfacedObject, IWebServiceViaCEP) private WebServiceCorreios: TWebServiceCorreios; public constructor Create(WebServiceCorreios: TWebServiceCorreios); procedure ConsultarEnderecoWebService(const Cep: string); function ObterLogradouro: string; function ObterBairro: string; function ObterCidade: string; end; |
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:
1 2 3 4 |
constructor TAdapter.Create(WebServiceCorreios: TWebServiceCorreios); begin Self.WebServiceCorreios := WebServiceCorreios; end; |
Agora, preste atenção na implementação de cada método:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
procedure TAdapter.ConsultarEnderecoWebService(const Cep: string); begin WebServiceCorreios.DefinirParametrosConsulta(Cep); WebServiceCorreios.ConsultarCEP; end; function TAdapter.ObterBairro: string; begin result := WebServiceCorreios.ObterResposta('Bairro'); end; function TAdapter.ObterCidade: string; begin result := WebServiceCorreios.ObterResposta('Cidade'); end; function TAdapter.ObterLogradouro: string; begin result := WebServiceCorreios.ObterResposta('Logradouro'); end; |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// TWebServiceViaCEP procedure TWebServiceViaCEP.ConsultarEnderecoWebService(const Cep: string); begin XMLDocument.FileName := Format('https://viacep.com.br/ws/%s/xml/', [Cep]); XMLDocument.Active := True; end; // TAdapter (Correios) procedure TAdapter.ConsultarEnderecoWebService(const Cep: string); begin WebServiceCorreios.DefinirParametrosConsulta(Cep); WebServiceCorreios.ConsultarCEP; end; |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var WebServiceCorreios: TWebServiceCorreios; Adapter: TAdapter; Comunicador: TComunicador; Retorno: TStringList; begin // instancia o objeto de consulta dos Correios (Adaptee) WebServiceCorreios := TWebServiceCorreios.Create; // instancia o Adaptador (Adapter) Adapter := TAdapter.Create(WebServiceCorreios); // instancia o comunicador (Target), injetando o adaptador Comunicador := TComunicador.Create(Adapter); // as próximas linhas são idênticas... Retorno := TStringList.Create; try Retorno := Comunicador.ConsultarEndereco(EditCEP.Text); EditLogradouro.Text := Retorno.Values['Logradouro']; EditBairro.Text := Retorno.Values['Bairro']; EditCidade.Text := Retorno.Values['Cidade']; finally FreeAndNil(Retorno); FreeAndNil(Comunicador); end; end; |
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 THTTPRIO
, adaptando-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!
Amigo André, como sempre artigos muito claros e bem explicados. Parabéns. Gostaria de sugerir o padrão Bridge, também da GoF. E o “Dependency Injection”, pois vejo que os Delpheiros pouco sabem a respeito e é um mundo muito amplo no mercado Java e Android. Injeção de Dependência pode trazer ganho muito grande aos aplicativos principalmente com desacoplamento permitindo, por exemplo, possibilitando mock das classes para fins de testes automatizados.
Isso aÃ, Marcos! Dependency Injection é um grande aliado do baixo acoplamento. Se este padrão estivesse no mindset de todo desenvolvedor, a taxa de manutenção e custo de evolução não seriam tão altos quanto são atualmente. Espero que este conceito seja cada vez mais disseminado no cenário de desenvolvimento…
Ah, e por coincidência, o padrão Bridge é o tema do próximo artigo! 🙂
Grande abraço!
Boa tarde André.
Muito bom o seu exemplo.
Estou com um erro na unit webservicecorreios, nessa linha:
HTTPRIO.OnBeforeExecute := BeforeExecute;
o erro é Incompatible types: ‘Parameter lists differ’
Sabe me dizer o que pode estar causando?
Olá, Renan!
O projeto de exemplo que anexei no artigo foi desenvolvido na versão Berlin. Nas versões anteriores, o evento BeforeExecute tem uma assinatura de parâmetros diferente, referente ao tipo do segundo parâmetro:
Se você for abrir o projeto nessas versões, terá que modificar este evento para compilá-lo.
Abraço!
Olá André.
Obrigado pela resposta.
Estou tentando na versão 10.2 Tokyo.
Será que mudou algo da Berlin para esta?
Tem razão, Renan.
Quando migrei o exemplo do Delphi 7 para o Berlin, esqueci de atualizar o projeto em anexo.
Agora está correto. Peço que baixe-o novamente.
Abraço!
Obrigado, está perfeito!
Bom dia André.
Estava implantando a sua solução no meu sistema e me deparei com 2 situações.
Fiz a busca no evento de exit do edCEP.Text
1- Caso não tenha internet, obtenho um erro. PoderÃamos tratar esse erro e apresentar uma mensagem amigavel ou ainda, permitir que o usuario digite manualmente em caso de erro?
2- Tem como usar as 2 consultas ao mesmo tempo?
Ex: Caso falhe VIACEP, automaticamente tentar CORREIOS?
Obrigado
PS: o tratamento de exceção já tinha no botão dos correios, então não precisa.
Olá, Renan!
Se fizermos esse controle, de usar as duas classes ao mesmo tempo, fugiremos da proposta do Adapter. A intenção é que apenas uma delas seja utilizada no cliente, adaptada quando necessário. Em outras palavras, o objetivo do padrão de projeto é adaptar uma funcionalidade conforme a realidade existente. No artigo, essa adaptação é o provedor de busca de endereço pelo CEP.
Abraço!
Ótimo trabalho e muito didático. Parabéns !!!
Agradeço, Carlos! 🙂