Proxy
Introdução
Seção intitulada “Introdução”O Proxy é um padrão de projeto estrutural que fornece um substituto para outro objeto. Esse substituto implementa a mesma interface do objeto real, de modo que o cliente pode usá-lo como se estivesse falando diretamente com o serviço original.
O ponto central do padrão é que o proxy intercepta o acesso ao objeto real para executar alguma ação adicional antes ou depois da chamada principal. Isso permite controlar uso, adiar inicialização, registrar operações, proteger acesso ou reaproveitar resultados em cache sem mudar o código cliente.
Problema
Seção intitulada “Problema”Imagine um sistema acadêmico que consulta um serviço externo para gerar relatórios detalhados de desempenho dos alunos. Esse serviço pode ser caro em tempo de resposta porque:
- faz chamadas remotas pela rede
- carrega muitos dados antes de responder
- exige autenticação e autorização
- pode repetir consultas idênticas em pouco tempo
- nem sempre precisa ser instanciado logo no início da aplicação
Sem um desenho adequado, o cliente tende a depender diretamente do objeto real:
- instanciando o serviço pesado por conta própria
- repetindo verificações de acesso em vários pontos
- consultando o serviço remoto mesmo quando o resultado já foi obtido antes
- misturando lógica principal com detalhes técnicos de infraestrutura
- espalhando decisões de criação e uso pela aplicação
Esse desenho gera problemas práticos:
- maior acoplamento com a implementação concreta
- desperdício de recursos por criação ou chamada desnecessária
- repetição de código de controle de acesso, log ou cache
- dificuldade para evoluir políticas transversais de uso
- menor clareza sobre quem realmente controla o objeto pesado
O problema central não é apenas acessar outro objeto. O problema é precisar controlar esse acesso sem alterar a interface esperada pelo cliente.
Solução
Seção intitulada “Solução”O Proxy resolve isso criando uma classe com a mesma interface do objeto real. O cliente continua programando contra o mesmo contrato, mas passa a receber o proxy no lugar do serviço concreto.
- Defina uma interface comum, como
RelatorioService. - Faça o serviço real implementar essa interface.
- Crie um proxy que também implemente
RelatorioService. - Coloque no proxy a lógica de controle desejada.
- Faça o proxy delegar ao objeto real quando necessário.
Com isso, o cliente não precisa saber se está falando com o objeto real ou com seu substituto. O proxy pode decidir quando criar o objeto pesado, se o acesso é permitido, se um resultado pode ser retornado do cache ou se uma operação deve ser registrada em log.
O ponto central do padrão é este: o cliente mantém o mesmo contrato, enquanto o proxy passa a controlar o acesso ao objeto real.
Analogia
Seção intitulada “Analogia”Pense em um cartão de acesso de biblioteca universitária. O estudante não entra diretamente em todas as salas e acervos especiais. Em vez disso, ele usa um cartão que representa sua autorização e controla o que pode ou não ser acessado.
O cartão não substitui fisicamente a biblioteca, mas atua como intermediário para validar e liberar a entrada. No Proxy, a ideia é semelhante: existe um intermediário com o mesmo ponto de contato para controlar o acesso ao recurso real.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Proxy quando:
- um objeto é caro para criar ou usar
- você precisa controlar permissões de acesso
- chamadas remotas devem parecer locais para o cliente
- resultados repetidos podem ser armazenados em cache
- você quer registrar uso sem alterar o serviço real
Tipos comuns de proxy em software:
- proxy virtual para inicialização preguiçosa
- proxy de proteção para controle de acesso
- proxy remoto para representar serviços em outro processo ou servidor
- proxy de cache para reutilizar resultados
- proxy de log para auditoria ou monitoramento
Exemplos reais em software moderno:
- objeto que adia carregamento de imagens pesadas
- cliente HTTP que representa um serviço remoto
- camada que valida permissão antes de acessar documentos sigilosos
- wrapper que faz cache de consultas repetidas
- componente que registra chamadas a uma API externa
Anti-padrão / Mau uso
Seção intitulada “Anti-padrão / Mau uso”Proxy pode ser mal utilizado quando a equipe o aplica apenas porque existe um objeto intermediário, sem um motivo claro para controlar acesso. Alguns erros frequentes são:
- usar Proxy quando um simples serviço já resolveria o problema
- adicionar lógica demais ao proxy até ele virar um objeto deus
- esconder efeitos importantes de rede ou latência como se fossem triviais
- confundir Proxy com Decorator apenas porque ambos encapsulam outro objeto
As consequências incluem mais complexidade acidental, dificuldade de depuração e uma falsa impressão de que o acesso ao recurso real é sempre barato ou simples.
Proxy funciona melhor quando o objetivo de controle é claro, pequeno e bem definido.
Como implementar
Seção intitulada “Como implementar”Uma forma incremental de implementar Proxy é:
- Identifique o serviço cujo acesso precisa ser controlado.
- Defina ou extraia uma interface comum para cliente, serviço e proxy.
- Implemente o serviço real com a lógica principal.
- Crie o proxy com uma referência para o serviço real.
- Adicione ao proxy a política desejada, como cache, permissão ou log.
- Faça o proxy delegar ao serviço real somente quando apropriado.
Antes de escrever o código, vale responder: o cliente deve continuar vendo a mesma interface, mas com regras extras de acesso? Se a resposta for sim, Proxy costuma ser uma boa escolha.
Exemplo em código
Seção intitulada “Exemplo em código”No exemplo abaixo, um sistema acadêmico controla o acesso a um relatório remoto e evita repetir consultas idênticas.
interface RelatorioService { String gerarRelatorio(int alunoId);}
class RelatorioRemotoService implements RelatorioService { @Override public String gerarRelatorio(int alunoId) { System.out.println("Consultando servidor remoto..."); return "Relatorio completo do aluno " + alunoId; }}
class RelatorioProxy implements RelatorioService { private RelatorioService service; private String cache; private Integer ultimoAlunoId;
@Override public String gerarRelatorio(int alunoId) { if (cache != null && ultimoAlunoId != null && ultimoAlunoId.equals(alunoId)) { System.out.println("Retornando relatorio em cache..."); return cache; }
if (service == null) { service = new RelatorioRemotoService(); }
cache = service.gerarRelatorio(alunoId); ultimoAlunoId = alunoId; return cache; }}Nesse código, RelatorioProxy oferece a mesma interface do serviço real. O cliente continua chamando gerarRelatorio, mas o proxy decide quando instanciar o serviço remoto e quando reutilizar um resultado já obtido.
Essa abordagem reduz custo de acesso e concentra a política de uso em um único ponto.
- controla acesso ao objeto real sem mudar o cliente
- permite inicialização preguiçosa de objetos pesados
- facilita introduzir cache, log e autorização
- mantém cliente e serviço acoplados ao mesmo contrato
- respeita bem o princípio aberto/fechado
Contras
Seção intitulada “Contras”- aumenta o número de classes envolvidas
- pode esconder custos reais de rede ou processamento
- adiciona uma camada extra de indireção
- se mal projetado, concentra responsabilidades demais
Relações com outros padrões/conceitos
Seção intitulada “Relações com outros padrões/conceitos”O Proxy se relaciona com outros padrões importantes:
- Decorator: Decorator adiciona comportamento ao objeto encapsulado; Proxy controla acesso ao objeto mantendo o mesmo contrato
- Facade: Facade simplifica o acesso a um subsistema inteiro; Proxy normalmente representa e controla um objeto específico
- Adapter: Adapter muda a interface para compatibilidade; Proxy preserva a mesma interface
- Mediator: Mediator coordena comunicação entre vários objetos; Proxy intermedeia o acesso a um objeto concreto
Essas comparações ajudam a perceber que Proxy não existe para simplificar um subsistema inteiro nem para traduzir contratos, mas para intermediar e controlar o acesso a um recurso.
Implementações alternativas (quando aplicável)
Seção intitulada “Implementações alternativas (quando aplicável)”As formas mais comuns de aplicar Proxy são:
- proxies manuais escritos pela equipe para cache, log ou autorização
- proxies remotos gerados por frameworks de RPC e HTTP clients
- proxies virtuais para objetos pesados, como documentos e imagens
- proxies dinâmicos oferecidos por bibliotecas e frameworks
Em frameworks corporativos, é comum encontrar proxies em recursos como transações, segurança, lazy loading e chamadas remotas.
Exemplo completo
Seção intitulada “Exemplo completo”Agora veja um caso mais realista em um sistema universitário. Apenas coordenadores podem acessar o histórico disciplinar completo de um aluno, e consultas repetidas devem evitar nova chamada ao serviço remoto.
interface HistoricoService { String obterHistoricoCompleto(int alunoId, String perfil);}
class HistoricoRemotoService implements HistoricoService { @Override public String obterHistoricoCompleto(int alunoId, String perfil) { System.out.println("Buscando historico completo no servidor..."); return "Historico completo do aluno " + alunoId; }}
class HistoricoProxy implements HistoricoService { private HistoricoService service; private String cache; private Integer alunoEmCache;
@Override public String obterHistoricoCompleto(int alunoId, String perfil) { if (!"COORDENADOR".equals(perfil)) { return "Acesso negado ao historico completo."; }
if (cache != null && alunoEmCache != null && alunoEmCache.equals(alunoId)) { System.out.println("Retornando historico em cache..."); return cache; }
if (service == null) { service = new HistoricoRemotoService(); }
cache = service.obterHistoricoCompleto(alunoId, perfil); alunoEmCache = alunoId; return cache; }}
public class SistemaAcademico { public static void main(String[] args) { HistoricoService historicoService = new HistoricoProxy();
System.out.println(historicoService.obterHistoricoCompleto(123, "ALUNO")); System.out.println(historicoService.obterHistoricoCompleto(123, "COORDENADOR")); System.out.println(historicoService.obterHistoricoCompleto(123, "COORDENADOR")); }}Nesse exemplo, HistoricoProxy concentra duas responsabilidades típicas do padrão: controle de acesso e cache. O cliente continua usando a interface HistoricoService, sem depender diretamente da implementação remota.
Esse tipo de modelagem funciona bem quando o sistema precisa intermediar acesso a recursos caros, sensíveis ou remotos sem alterar o contrato esperado pelo cliente.