Pular para o conteúdo

Proxy

Fornecer um representante ou substituto de outro objeto para controlar o acesso a ele.

Às vezes acessar um objeto diretamente é indesejável: ele é caro para criar (imagem de alta resolução, conexão), vive em outro processo/máquina, exige verificação de permissão, ou seus resultados poderiam ser cacheados. Não queremos poluir o objeto real com essas preocupações, nem mudar o código cliente. O Proxy resolve interpondo-se: implementa a mesma interface e adiciona a lógica de controle antes/depois de delegar ao objeto real.

public interface RepositorioDocumento {
String conteudo(String id);
}
public class RepositorioDocumentoReal implements RepositorioDocumento {
public String conteudo(String id) {
return consultaBancoLenta(id);
}
private String consultaBancoLenta(String id) {
System.out.println("Consulta cara ao banco para " + id);
return "Conteúdo do documento " + id;
}
}

Cada chamada de conteudo(id) bate no banco, mesmo para o mesmo id repetido. Adicionar cache dentro do repositório real misturaria a responsabilidade de persistência com a de cache.

import java.util.HashMap;
import java.util.Map;
public class RepositorioDocumentoProxy implements RepositorioDocumento {
private RepositorioDocumentoReal real;
private final Map<String, String> cache = new HashMap<>();
public String conteudo(String id) {
String emCache = cache.get(id);
if (emCache != null) {
System.out.println("Cache hit para " + id);
return emCache;
}
if (real == null) {
real = new RepositorioDocumentoReal();
}
String resultado = real.conteudo(id);
cache.put(id, resultado);
return resultado;
}
}

Uso — o cliente continua programando contra a interface, sem saber que fala com um proxy:

RepositorioDocumento repo = new RepositorioDocumentoProxy();
System.out.println(repo.conteudo("doc-42"));
System.out.println(repo.conteudo("doc-42"));

O proxy adia a criação do objeto real (lazy) até a primeira necessidade e serve chamadas repetidas do cache, sem qualquer alteração no RepositorioDocumentoReal nem no cliente.

classDiagram
    class RepositorioDocumento {
        <<interface>>
        +conteudo(String) String
    }
    class RepositorioDocumentoReal {
        +conteudo(String) String
    }
    class RepositorioDocumentoProxy {
        -real : RepositorioDocumentoReal
        -cache : Map
        +conteudo(String) String
    }
    class Cliente
    RepositorioDocumento <|.. RepositorioDocumentoReal
    RepositorioDocumento <|.. RepositorioDocumentoProxy
    Cliente --> RepositorioDocumento : usa
    RepositorioDocumentoProxy --> RepositorioDocumentoReal : controla acesso
Tipo Propósito Exemplo
Virtual Adiar criação/carga de um objeto caro até ser realmente necessário (lazy) Carregar imagem só quando exibida; ORM lazy-loading de associações
Remoto Representar localmente um objeto em outro processo/máquina, escondendo a rede Stub de RMI/gRPC; cliente de serviço remoto
Proteção Controlar acesso conforme permissões Verificar papel do usuário antes de delegar
Cache / Smart reference Memorizar resultados ou contar referências, log, lazy-init O exemplo acima; contagem de referências

Estruturalmente idênticos — ambos implementam a mesma interface do objeto envelopado e delegam a ele. A diferença é de intenção:

Proxy Decorator
Intenção Controlar o acesso ao objeto real Adicionar responsabilidades/comportamento
Quem controla o ciclo de vida Geralmente o próprio proxy (cria o real, faz lazy) O cliente monta a cadeia; o objeto já existe
Composição Normalmente um proxy por objeto Encadeamento de vários decorators
Interface Igual à do objeto real Igual à do objeto real

Regra prática: se você está gerenciando quando/se/como o objeto real é acessado, é Proxy. Se está enriquecendo o resultado ou o comportamento de uma chamada, é Decorator.

  • A favor: introduz controle de acesso, lazy, cache e representação remota de forma transparente, respeitando SRP (o objeto real não conhece cache/segurança/rede).
  • Contra: uma camada de indireção a mais; proxies de lazy podem esconder custos (uma chamada “barata” dispara I/O); complexidade de invalidação de cache.
  • Confundir com Decorator: mesma forma, intenção diferente (ver tabela acima).
  • Cache sem estratégia de invalidação: proxy de cache que nunca expira serve dados obsoletos.
  • Proxy que altera semântica: um proxy deve preservar o contrato da interface (LSP); mudar comportamento observável quebra o cliente.
  • Lazy escondendo latência crítica: proxy virtual que dispara chamadas remotas caras em getters aparentemente triviais surpreende quem consome.

1. Qual é a intenção do Proxy e quais seus principais tipos?

Resposta

Controlar o acesso a outro objeto por meio de um substituto com a mesma interface. Tipos: virtual (lazy loading de objeto caro), remoto (representar objeto em outro processo/máquina), proteção (controle de acesso por permissão) e cache/smart reference (memorização, contagem, log).

2. Proxy e Decorator têm a mesma estrutura; como distingui-los?

Resposta

Pela intenção. Proxy controla o acesso (decide quando/se/como o objeto real é usado, muitas vezes gerenciando seu ciclo de vida). Decorator adiciona comportamento/responsabilidades ao resultado, tipicamente encadeando vários envelopes sobre um objeto que já existe.

3. Como o Proxy ajuda a respeitar o SRP?

Resposta

Ele extrai preocupações transversais (cache, segurança, lazy, rede) do objeto real, que permanece focado em sua responsabilidade central. O controle de acesso vive no proxy, não misturado à lógica de negócio/persistência.

4. Que armadilha um proxy virtual (lazy) pode introduzir?

Resposta

Esconder custos: uma chamada que parece barata pode disparar criação de objeto pesado ou I/O remoto na primeira invocação. Isso surpreende o cliente e pode causar latência inesperada em pontos críticos. Também há o risco de cache sem invalidação servindo dados obsoletos.