[Delphi] Design Patterns GoF – Proxy

Boa noite, meus amigos!
No artigo passado, sobre o Flyweight, citei a importância do fator de desempenho em um sistema. O artigo de hoje também está relacionado à este requisito não-funcional, porém, abordando o próximo – e último – Design Pattern da família estrutural: o Proxy! Elaborei um exemplo prático bem instrutivo para apresentar as vantagens. Vamos nessa?

Introdução

Alguma vez você já precisou configurar o endereço do proxy nas opções de internet do Windows? Este endereço refere-se a um servidor que atua como intermediário entre a máquina cliente e a internet, aplicando filtros, analisando dados e gerenciando a complexidade das requisições. Um servidor proxy, portanto, pode aprimorar a experiência de navegação web do usuário.

Exemplo de configuração do servidor proxy no Internet Explorer

O Design Pattern Proxy tem basicamente a mesma responsabilidade. Ao atuar como um intermediário entre dois lados, é capaz de aperfeiçoar o desempenho de uma rotina em uma aplicação, executando validações e tratando dados, por exemplo, de forma que contribua para essa finalidade. O Proxy geralmente é adequado para cenários em que o cliente utiliza um objeto de uma classe extensa ou complexa, na qual consome muita memória ou afeta o desempenho da aplicação. Para isso, o padrão de projeto reduz essa carga gerenciando as demandas dessa classe.

A imagem abaixo ilustra a principal diferença ao utilizar o Proxy:

Ilustração da diferença da utilização do padrão de projeto Proxy

Analogia

O Proxy pode ser conveniente para qualquer situação em que necessita-se de um intermediário para controlar os acessos a um objeto que expõe suas complexidades. Por exemplo, imagine um objeto complexo (composto por vários objetos internos) que possui uma rotina de fechamento de caixa de uma loja. Considere também que, antes de realizar este fechamento, a rotina deve validar a permissão do usuário conectado e verificar se nenhuma estação de trabalho está com uma instância aberta do sistema. A solução seria semelhante ao código abaixo:

No entanto, observe que existe a possibilidade de a rotina principal do objeto, que é o fechamento, não ser executada em função das validações. Neste caso, a maioria dos objetos internos (ou todos) inicializados durante a criação do objeto complexo foram desnecessários. Ao instalar um Proxy, adicionamos uma nova camada entre o cliente e o objeto complexo com a seguinte codificação:

O cliente, por sua vez, chamaria o Proxy da forma a seguir:

Objeto complexo encapsulado! 🙂

Exemplo de codificação do Proxy

Bom, pessoal, acredito que o exemplo acima já tenha transmitido a ideia básica do padrão de projeto, mas, para evidenciar as vantagens em um ambiente real, elaborei um exemplo prático envolvendo cálculo de distância entre duas cidades.

Para isso, utilizaremos a API do Google Maps, enviando uma URL com os parâmetros (nome das cidades de origem e destino) e recebendo um JSON como retorno. O nosso Proxy será um agente de aprimoramento de desempenho e fará o intermédio entre o cliente e o objeto complexo. Para cada requisição na API, o Proxy armazenará o resultado em um DataSet para que, caso a mesma consulta seja realizada, os dados sejam consultados neste DataSet – que é mais rápido – ao invés de utilizar a API. Na verdade, será semelhante a um recurso de cache.

Como bônus, apresentarei uma forma de ler dados no formato JSON com as classes nativas das versões mais recentes do Delphi (XE+), declaradas no namespace System.JSON.

O objetivo do exemplo é demonstrar a utilização do Proxy para “encapsular” o acesso ao objeto complexo, que neste contexto, é chamado de Real Subject (objeto real). O Client (consumidor da rotina) não conhecerá a classe que envia a requisição para a API. Nós apenas enviaremos os parâmetros e o Proxy controlará o acesso ao objeto real, ou seja, se o cálculo da distância já estiver no DataSet de cache, a criação do objeto real será ignorada, favorecendo o desempenho.

Apenas para título de conhecimento, a resposta da API em formato JSON possui a seguinte estrutura:

Interface Subject e classe Real Subject

Pois bem, devemos iniciar com o Subject, uma abstração que terá a assinatura do método do cálculo de distância:

Em seguida, codificaremos o elemento Real Subject que, no nosso caso, será uma classe “complexa” por assumir as seguintes responsabilidades:

  • Tratar a URL de envio (encoding);
  • Enviar a URL para a API do Google Maps através de um componente TIdHTTP;
  • Receber a resposta em JSON e buscar o valor da distância utilizando um objeto da classe TJSONObject.

Classe Proxy

O próximo passo é criar o Proxy, que controlará a criação e os acessos ao Real Subject. É importante destacar que essa classe também implementa a Interface Subject:

Observem que a condição para verificar se os dados existem no Dataset de cache foi inserida antes da criação do Real Subject. Em outras palavras, se essa condição for verdadeira, o Real Subject não será criado e, por consequência, não será necessário consultar a distância pela API.

Em ação!

Para testar o Proxy, desenhei o formulário abaixo, que será o nosso Client:

Exemplo de aplicação utilizando o Design Pattern Proxy

O botão “Calcular Distância” buscará os valores informados nos campos de texto e chamará o Proxy para receber a distância em quilômetros, exibindo uma mensagem para o usuário:

Ponto final! 🙂

Para fazer o teste de desempenho, faça a mesma consulta de distância duas vezes e observe que, na segunda vez, o retorno é mais rápido, já que o Proxy se encarrega de buscar os dados no DataSet de cache ao invés de utilizar a API. Além disso, os dados do DataSet são salvos em disco, portanto, mesmo que você reinicie a aplicação, as consultas realizadas anteriormente já estarão armazenadas! Que firmeza, hein?

Conclusão

Leitores, no link abaixo disponibilizo o projeto de exemplo deste artigo com algumas codificações extras. Adicionei um TMemo para registrar o histórico das consultas enquanto a aplicação está aberta e também um TRadioGroup no formulário para “ligar ou desligar” o cache. Ao selecionar “Sim”, o Proxy é utilizado, caso contrário, o Real Subject será diretamente instanciado e não haverá leitura do cache. Em algumas situações, disponibilizar essa opção pode ser importante, como, por exemplo, evitar que as validações do Proxy sejam temporariamente ignoradas. É por isso que o Proxy e o Real Subject devem implementar a mesma Interface.

A maior diferença no projeto, na verdade, está na leitura dos dados JSON. Recebi uma orientação de um desenvolvedor chamado Messias Bueno (obrigado, meu caro!) para ler a distância em apenas um comando totalmente orientado a objetos:

Interessante, não?

Obrigado pela visita, pessoal! Abraço!


André Celestino