EstruturalIntermediário6 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Proxy

Coloca um substituto com a mesma interface na frente de um objeto para controlar o acesso a ele — adiando a criação, cacheando resultados, checando permissão ou falando com um objeto remoto.

Em 10 segundos

Um substituto com a mesma interface do objeto real fica na frente dele e decide quando (ou se) a chamada chega ao destino: cria sob demanda, devolve do cache, exige permissão ou vai pela rede — tudo sem o cliente saber.

  • #acesso
  • #lazy
  • #cache
  • #gof
Analogia

O cartão de crédito

O cartão tem a mesma 'interface' do dinheiro: você paga com ele em qualquer lugar que aceite dinheiro. Mas ele não é o dinheiro — é um substituto que controla o acesso à sua conta. Ele verifica limite antes de deixar passar, registra cada compra num extrato e evita que você carregue a quantia toda no bolso. O lojista não muda nada no que faz; para ele, pagamento é pagamento.

01Conceito

O problema

Alguns objetos custam caro para existir: uma imagem em alta resolução, uma conexão de banco, um relatório de milhões de linhas. Criar todos antecipadamente desperdiça memória e tempo com o que talvez nem seja usado.

Outros exigem cerimônia no acesso: checar permissão, registrar auditoria, respeitar rate limit. Espalhar essas checagens por todo chamador é repetição garantida — e uma delas vai ser esquecida.

A tentação é resolver dentro do próprio objeto real: um if (!carregado) carregar() no começo de cada método. Isso mistura a responsabilidade do objeto (exibir a imagem) com a de infraestrutura (gerenciar o carregamento), e o objeto deixa de ser testável sem o disco.

Proxy, Decorator e Adapter são estruturalmente parecidos — todos embrulham um objeto — e a diferença está na intenção, não no desenho. Adapter muda a interface (contratos incompatíveis). Decorator mantém a interface e ADICIONA comportamento, normalmente empilhável. Proxy mantém a interface e CONTROLA o acesso, normalmente sozinho e com a responsabilidade de decidir se a chamada chega.

Um sinal prático: no Decorator, quem monta a pilha é o cliente, e cada camada agrega valor visível ao resultado. No Proxy, o cliente costuma nem saber que existe, e o valor é invisível — mesma resposta, obtida de forma mais barata ou mais segura.

02Conceito

Os quatro sabores

Proxy é um padrão com quatro usos clássicos, todos com a mesma estrutura: virtual (adia a criação), de proteção (checa permissão), remoto (esconde a rede) e cache/smart (memoriza e conta referências).

Virtual é o mais comum: o objeto caro só nasce no primeiro uso. É o que ORMs fazem com lazy loading — pedido.cliente devolve um proxy, e só quando você lê um campo dele é que sai o SELECT.

De proteção filtra por permissão antes de delegar. É a base de muito middleware de autorização: a interface é a mesma, mas a chamada só passa se o chamador puder.

Remoto faz uma chamada de rede parecer local (o 'stub' de RPC/gRPC). Aqui mora a maior armadilha do padrão: a falácia de que a rede é confiável — mais sobre isso nas armadilhas.

Cache/smart guarda o resultado, conta referências ou libera recursos. Um memoizador é, no fundo, um proxy de cache sobre uma função.

Como se organiza
  • Clientechama exibir() — vê só a interface Imagem

  • ImagemProxymesma interface; decide se a chamada passa

    • ImagemRealsó nasce no primeiro exibir()

O objeto real aparece tracejado porque pode nem existir: enquanto ninguém chamar exibir(), o proxy segura a criação. A borda do cliente termina na interface — ele não distingue um do outro.
Sem o padrão × com o padrão

Com Proxy: paga quem usa

  • a galeria monta 100 proxies baratos, sem tocar no disco
  • o carregamento acontece no primeiro exibir()
  • memória proporcional ao que foi realmente aberto
  • nos testes, um proxy falso substitui o real

O custo vira uma indireção a mais e uma latência que aparece no meio da interação, não no início — o usuário vê a lista rápido, mas espera ao abrir.

O proxy não elimina o custo: ele o move no tempo e o cobra só de quem usa. Quando todos os itens acabam sendo abertos, o total é o mesmo — com uma classe a mais.
03Anatomia

Navegue pelas camadas

Camada 1/3Cliente

Camada 1

Cliente

programa contra a interface

Depende só do contrato (`Imagem`), nunca da classe concreta. É essa disciplina que permite trocar o real pelo proxy sem tocar em uma linha do chamador.

No código

const img: Imagem = fabrica.criar('a.png');
img.exibir();

Se violar: se o cliente declara `ImagemReal` em vez de `Imagem`, o proxy não encaixa e o padrão morre no tipo.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

interface Imagem {
  exibir(): void;
}

// Objeto real: caro de criar (le o arquivo do disco no construtor)
class ImagemReal implements Imagem {
  constructor(private arquivo: string) {
    console.log("carregando do disco:", arquivo); // custo alto
  }
  exibir(): void {
    console.log("exibindo", this.arquivo);
  }
}

// Proxy virtual: mesma interface, adia a criacao ate o primeiro uso
class ImagemProxy implements Imagem {
  private real?: ImagemReal;
  constructor(private arquivo: string) {}

  exibir(): void {
    // so agora o objeto caro nasce — e uma vez so
    this.real ??= new ImagemReal(this.arquivo);
    this.real.exibir();
  }
}

// A galeria monta 100 itens sem tocar no disco
const galeria: Imagem[] = ["a.png", "b.png"].map((f) => new ImagemProxy(f));
galeria[0].exibir(); // carrega so a que o usuario abriu
06No mundo real

Casos de uso

Lazy loading em ORM

Cenário

Ao carregar um pedido, o ORM poderia trazer cliente, itens, pagamentos e histórico — dezenas de joins que quase nenhuma tela usa por completo.

Como o padrão entra

O ORM devolve proxies nas associações: pedido.cliente é um objeto vazio com a interface de Cliente. O SELECT só dispara quando algum campo é lido de fato. Hibernate, Doctrine e o Prisma fazem variações disso.

Trade-off: É a origem clássica do problema N+1: um laço sobre 200 pedidos lendo p.cliente.nome dispara 200 queries. E acessar um proxy fora da sessão/transação estoura erro de lazy initialization — o custo escondido reaparece no pior momento.

Autorização em API interna

Cenário

Um serviço de RH expõe operações sobre folha de pagamento; cada uma exige um papel diferente, e a checagem precisa acontecer sempre, sem exceção.

Como o padrão entra

Um FolhaProxy implementa a mesma interface do serviço e, antes de delegar, valida o papel do chamador e registra a tentativa na auditoria. A regra de acesso fica num lugar só, e o serviço real continua sem saber o que é um usuário.

Trade-off: Se a decisão de permissão depender de dados do próprio resultado ('só pode ver salários da sua equipe'), o proxy precisa executar a chamada para então filtrar — e a economia de não executar desaparece.

Stub de gRPC

Cenário

Um serviço de catálogo precisa consultar preços em outro serviço, que roda em outra máquina, com serialização e rede no meio.

Como o padrão entra

O stub gerado pelo gRPC é um proxy remoto: expõe precos.consultar(sku) como se fosse um objeto local e esconde serialização, conexão e desserialização.

Trade-off: A conveniência mente sobre a física: a chamada parece local mas pode levar 200ms, falhar por timeout ou por partição de rede. Código escrito como se fosse local (num laço, sem timeout, sem retry) vira incidente em produção.

Passo a passoComo colocar o substituto na frente
  1. 1

    Compartilhar a interface

    Proxy e objeto real implementam o mesmo contrato. Sem isso, o cliente distingue os dois e o padrão morre no tipo.

  2. 2

    Guardar a política no proxy

    Lazy, cache, permissão ou rede moram aqui — finos, sem regra de negócio. O real continua fazendo só o trabalho dele.

  3. 3

    Adiar o custo real

    No virtual: o caro só nasce no primeiro uso. Criar o real no construtor do proxy anula o ganho inteiro.

  4. 4

    Entregar o proxy ao cliente

    Quem consome programa contra a interface e não precisa saber que existe intermediário.

O Proxy que carrega no construtor

Compila, roda, passa no code review. E quebra depois.

A classe se chama Proxy, implementa a interface certa e até aparece no diagrama — mas instancia o objeto caro no construtor. É um repasse com nome sofisticado; o lazy loading nunca aconteceu.

interface Imagem { exibir(): void; }

class ImagemReal implements Imagem {
  constructor(private arquivo: string) {
    console.log("carregando do disco:", arquivo);
  }
  exibir() { console.log("exibindo", this.arquivo); }
}

// "Proxy virtual"... que carrega no construtor.
class ImagemProxy implements Imagem {
  private real: ImagemReal;
  constructor(arquivo: string) {
    this.real = new ImagemReal(arquivo); // <- paga o custo sempre
  }
  exibir() { this.real.exibir(); }       // so repassa
}

// A galeria de 100 itens volta a ler 100 arquivos no boot.
// Mesma interface, zero controle de acesso — casca vazia.

O que quebra

  • No boot da galeriaCem arquivos são lidos antes de qualquer clique — exatamente o custo que o proxy prometia adiar.
  • Na memóriaO consumo fica proporcional ao catálogo inteiro, não ao que o usuário abriu.
  • No testeMontar a lista já exige disco/rede; o falso barato que o proxy deveria permitir não existe.

A saída: Guarde só o caminho (ou a factory) no proxy. O ImagemReal nasce no primeiro exibir() — this.real ??= new ImagemReal(...) — e uma vez só.

08Cuidado

Armadilhas

  1. Fingir que a rede não existe

    O proxy remoto é o padrão mais perigoso da lista justamente porque funciona bem demais: a chamada parece local. Mas latência, falha parcial e timeout continuam lá. Um proxy remoto sem timeout explícito, retry com backoff e circuit breaker transforma a lentidão do vizinho na sua indisponibilidade.

  2. Confundir com Decorator

    Os dois embrulham mantendo a interface, então a escolha é de intenção. Se você está adicionando comportamento que o cliente pediu e quer empilhar (log + retry + cache), é Decorator. Se está controlando SE e QUANDO a chamada chega ao objeto, invisivelmente, é Proxy. Chamar tudo de proxy faz o nome perder o poder de comunicar.

  3. Cache sem invalidação

    Um proxy de cache é trivial de escrever e traiçoeiro de manter: sem política de expiração ou invalidação, ele serve dado velho para sempre — e o bug aparece longe, como 'o sistema mostra o preço antigo'. Quem coloca cache assume a dívida de decidir quando ele mente.

09Decisão

Quando usar × quando evitar

Use quando

3
  • O objeto real é caro de criar e nem sempre será usado.
  • O acesso precisa de política transversal: permissão, auditoria, rate limit, cache.
  • O objeto vive em outro processo e você quer isolar a rede num ponto só.

Evite quando

3
  • O objeto é barato — sobra indireção e falta ganho.
  • Você quer mudar a interface (é Adapter) ou somar comportamento empilhável (é Decorator).
  • A política depende do resultado da chamada, o que anula o ganho de interceptá-la.

Testar o que ficou

3 armadilhas de Proxy viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • O objeto Proxy do JSO padrão virou palavra-chave: você intercepta leitura, escrita e chamada sem tocar no alvo.
  • Reatividade do Vue 3O objeto reativo é um Proxy que avisa o framework a cada acesso e a cada escrita.
  • Lazy loading de ORMpedido.cliente parece um objeto e só vai ao banco quando alguém realmente lê um campo.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Controla o acesso ao real (lazy, cache, auth).
const imagem = new ProxyImagem("foto.png");
imagem.desenhar(); // carrega sob demanda

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Uma indireção a mais entre o cliente e o objeto real, que o leitor precisa atravessar
  • Comportamento escondido no proxy (cache, lazy, log) surpreende quem espera a chamada direta

Não vale se: não há nada a interceptar — sem controle de acesso, cache ou carga tardia, o proxy é só uma casca vazia.

Costuma ser confundido com

Onde isto aparece

Continue por aqui