Proxy
Fornece um substituto ou placeholder para outro objeto, controlando o acesso a ele — sem alterar a interface que o cliente já conhece.
Intenção
Controlar o acesso a um objeto interpondo um substituto (o proxy) que implementa exatamente a mesma interface do objeto real. O cliente não percebe diferença — ele programa para o contrato e o proxy decide quando, como e se delega a chamada ao objeto real.
Catalogado pelo GoF (1994) como padrão estrutural, o Proxy é a escolha natural sempre que o acesso direto ao objeto real precisa ser mediado: o objeto é caro de criar, está em outro processo ou máquina, requer autorização, ou merece ter suas chamadas registradas ou cacheadas.
Problema
Imagine um objeto RelatorioCompleto que carrega e processa
centenas de megabytes de dados assim que é instanciado. Sua aplicação
cria esse objeto na inicialização, mas o usuário raramente abre o relatório
completo — na maioria das sessões, ele só consulta o resumo.
O acesso direto ao objeto real cria problemas concretos:
- Custo desnecessário: o objeto é criado e todo o processamento é executado mesmo que o resultado nunca seja usado na sessão.
- Sem ponto de controle: não há onde checar permissões, registrar acessos ou retornar um resultado cacheado sem modificar o objeto original ou o cliente.
- Acoplamento ao ciclo de vida: o cliente precisa saber exatamente quando criar, destruir e cachear o objeto real.
A solução ingênua seria adicionar condicionais no cliente, criando acoplamento e misturando responsabilidades. O Proxy resolve isso de forma transparente: substitui o objeto real por um intermediário que o cliente usa exatamente da mesma forma.
Proxy vs Decorator: a diferença-chave
Tanto o Proxy quanto o Decorator envolvem um objeto mantendo a mesma interface. A distinção está na intenção:
- O Decorator adiciona responsabilidades de negócio ao objeto — timestamp, log de operação, criptografia de conteúdo. O foco é enriquecer o que o objeto faz.
- O Proxy controla o acesso ao objeto — lazy loading, cache, verificação de permissão, comunicação remota. O foco é quando e se o objeto real é acessado, não o que ele faz.
Na prática, o Proxy frequentemente gerencia o ciclo de vida do objeto real (cria-o sob demanda ou o mantém em cache), enquanto o Decorator assume que o objeto já existe e apenas acrescenta comportamento ao redor das chamadas.
Proxy vs Adapter
O Adapter muda a interface do objeto — converte um contrato incompatível no contrato que o cliente espera. O Proxy mantém exatamente a mesma interface do objeto real: o cliente não sabe que está falando com um intermediário.
Solução
O Proxy organiza o código em três participantes:
-
Subject (interface): o contrato compartilhado pelo
objeto real e pelo proxy. Define as operações que o cliente pode chamar.
Ex.: interface
Relatoriocom o métodogerar(): string. -
RealSubject: o objeto real que realiza o trabalho.
Ex.:
RelatorioCompleto— carrega dados, processa e retorna o relatório. Pode ser caro de criar ou exigir acesso privilegiado. - Proxy: implementa Subject e mantém uma referência ao RealSubject (criada sob demanda ou injetada). Intercepta as chamadas do cliente, executa sua lógica de controle (lazy init, verificação de permissão, cache, log) e, quando apropriado, delega ao RealSubject.
Tipos principais de Proxy
- Virtual (lazy-loading): adia a criação do objeto real até o primeiro uso. Ideal para objetos caros de inicializar.
- De proteção: verifica permissões antes de delegar. O cliente com acesso insuficiente recebe uma negação sem chegar ao objeto real.
- Remoto: representa um objeto que reside em outro processo ou máquina. O proxy cuida da serialização e da comunicação em rede.
- De cache/logging: armazena o resultado da primeira chamada e o retorna nas seguintes, ou registra cada acesso para auditoria.
Estrutura
«interface» Subject
┌────────────────────────────────┐
│ + gerar(): string │
└────────────────────────────────┘
▲ ▲
│ implementa │ implementa
RelatorioCompleto RelatorioProxy
(RealSubject) (Proxy)
┌──────────────────┐ ┌──────────────────────────────────┐
│ + gerar(): string│ │ - real: RelatorioCompleto | null │
│ (operação cara)│ │ - cache: string | null │
└──────────────────┘ │ + gerar(): string │
│ 1. retorna cache, se existir │
│ 2. cria RelatorioCompleto (lazy) │
│ 3. delega e armazena resultado │
└──────────────────────────────────┘
│ delega (sob demanda)
▼
RelatorioCompleto
Fluxo — primeira chamada (objeto ainda não existe):
cliente.gerar()
│
▼ (via interface Subject)
RelatorioProxy.gerar()
│ cache == null → cria RelatorioCompleto
│ delega para real.gerar()
│ armazena resultado em cache
▼
retorna string para o cliente
Fluxo — chamada subsequente (cache preenchido):
cliente.gerar()
│
▼
RelatorioProxy.gerar()
│ cache != null → retorna imediatamente
▼
retorna string (sem criar nem chamar RealSubject)
Exemplos de código
Exemplo 1 — Proxy virtual com lazy loading e cache
O proxy cria o objeto real somente no primeiro acesso e cacheia o resultado, eliminando o custo de inicialização e reprocessamento. O cliente usa exatamente a mesma interface do objeto real.
// ── Subject — interface compartilhada por real e proxy ────────
interface Relatorio {
gerar(): string;
}
// ── RealSubject — objeto caro de criar e executar ─────────────
class RelatorioCompleto implements Relatorio {
constructor() {
// Simula inicialização custosa (leitura de banco, processamento...)
console.log("[RelatorioCompleto] Inicializando e carregando dados...");
}
gerar(): string {
console.log("[RelatorioCompleto] Gerando relatório...");
return "=== RELATÓRIO COMPLETO ===\nVendas: R$1.234.567\nClientes: 4.892";
}
}
// ── Proxy virtual com lazy loading e cache ────────────────────
class RelatorioProxy implements Relatorio {
private real: RelatorioCompleto | null = null;
private cache: string | null = null;
gerar(): string {
if (this.cache !== null) {
console.log("[Proxy] Retornando resultado em cache.");
return this.cache;
}
// Cria o objeto real apenas quando necessário (lazy)
if (this.real === null) {
this.real = new RelatorioCompleto();
}
this.cache = this.real.gerar();
return this.cache;
}
}
// ── Código cliente — só conhece a interface Relatorio ─────────
function exibirRelatorio(relatorio: Relatorio): void {
console.log(relatorio.gerar());
}
const proxy = new RelatorioProxy();
// Nada foi criado ainda — o objeto real não existe.
console.log("--- Primeira chamada ---");
exibirRelatorio(proxy);
// [RelatorioCompleto] Inicializando e carregando dados...
// [RelatorioCompleto] Gerando relatório...
// === RELATÓRIO COMPLETO ===
console.log("--- Segunda chamada ---");
exibirRelatorio(proxy);
// [Proxy] Retornando resultado em cache.
// === RELATÓRIO COMPLETO ===
<?php
// ── Subject — interface compartilhada por real e proxy ────────
interface Relatorio
{
public function gerar(): string;
}
// ── RealSubject — objeto caro de criar e executar ─────────────
class RelatorioCompleto implements Relatorio
{
public function __construct()
{
// Simula inicialização custosa (leitura de banco, processamento...)
echo "[RelatorioCompleto] Inicializando e carregando dados..." . PHP_EOL;
}
public function gerar(): string
{
echo "[RelatorioCompleto] Gerando relatório..." . PHP_EOL;
return "=== RELATÓRIO COMPLETO ===\nVendas: R\$1.234.567\nClientes: 4.892";
}
}
// ── Proxy virtual com lazy loading e cache ────────────────────
class RelatorioProxy implements Relatorio
{
private ?RelatorioCompleto $real = null;
private ?string $cache = null;
public function gerar(): string
{
if ($this->cache !== null) {
echo "[Proxy] Retornando resultado em cache." . PHP_EOL;
return $this->cache;
}
// Cria o objeto real apenas quando necessário (lazy)
if ($this->real === null) {
$this->real = new RelatorioCompleto();
}
$this->cache = $this->real->gerar();
return $this->cache;
}
}
// ── Código cliente — só conhece a interface Relatorio ─────────
function exibirRelatorio(Relatorio $relatorio): void
{
echo $relatorio->gerar() . PHP_EOL;
}
$proxy = new RelatorioProxy();
// Nada foi criado ainda — o objeto real não existe.
echo "--- Primeira chamada ---" . PHP_EOL;
exibirRelatorio($proxy);
// [RelatorioCompleto] Inicializando e carregando dados...
// [RelatorioCompleto] Gerando relatório...
// === RELATÓRIO COMPLETO ===
echo "--- Segunda chamada ---" . PHP_EOL;
exibirRelatorio($proxy);
// [Proxy] Retornando resultado em cache.
// === RELATÓRIO COMPLETO ===
Exemplo 2 — Proxy de proteção com verificação de permissão
O proxy intercepta a chamada e verifica se o usuário tem a permissão necessária antes de delegar ao objeto real. O cliente não precisa implementar essa lógica — e o objeto real tampouco precisa saber que existe um controle de acesso ao redor dele.
// ── Subject ───────────────────────────────────────────────────
interface ServicoArquivo {
ler(caminho: string): string;
escrever(caminho: string, conteudo: string): void;
}
// ── RealSubject ───────────────────────────────────────────────
class ServicoArquivoReal implements ServicoArquivo {
ler(caminho: string): string {
return `[conteúdo de ${caminho}]`;
}
escrever(caminho: string, conteudo: string): void {
console.log(`[Disco] Escrevendo em ${caminho}: ${conteudo}`);
}
}
// ── Tipos de suporte ──────────────────────────────────────────
type Role = "leitura" | "escrita" | "admin";
interface Usuario {
nome: string;
roles: Role[];
}
// ── Proxy de proteção ─────────────────────────────────────────
class ServicoArquivoProtegido implements ServicoArquivo {
constructor(
private readonly real: ServicoArquivoReal,
private readonly usuario: Usuario
) {}
private temPermissao(role: Role): boolean {
return this.usuario.roles.includes(role) ||
this.usuario.roles.includes("admin");
}
ler(caminho: string): string {
if (!this.temPermissao("leitura")) {
throw new Error(`Usuário "${this.usuario.nome}" não tem permissão de leitura.`);
}
console.log(`[Proxy] Acesso de leitura autorizado para ${this.usuario.nome}.`);
return this.real.ler(caminho);
}
escrever(caminho: string, conteudo: string): void {
if (!this.temPermissao("escrita")) {
throw new Error(`Usuário "${this.usuario.nome}" não tem permissão de escrita.`);
}
console.log(`[Proxy] Acesso de escrita autorizado para ${this.usuario.nome}.`);
this.real.escrever(caminho, conteudo);
}
}
// ── Uso ───────────────────────────────────────────────────────
const real = new ServicoArquivoReal();
const leitor: Usuario = { nome: "Ana", roles: ["leitura"] };
const proxyLeitura = new ServicoArquivoProtegido(real, leitor);
console.log(proxyLeitura.ler("/dados/vendas.csv"));
// [Proxy] Acesso de leitura autorizado para Ana.
// [conteúdo de /dados/vendas.csv]
try {
proxyLeitura.escrever("/dados/vendas.csv", "novo dado");
} catch (e) {
console.error((e as Error).message);
// Usuário "Ana" não tem permissão de escrita.
}
const admin: Usuario = { nome: "Carlos", roles: ["admin"] };
const proxyAdmin = new ServicoArquivoProtegido(real, admin);
proxyAdmin.escrever("/dados/vendas.csv", "dado atualizado");
// [Proxy] Acesso de escrita autorizado para Carlos.
// [Disco] Escrevendo em /dados/vendas.csv: dado atualizado
<?php
// ── Subject ───────────────────────────────────────────────────
interface ServicoArquivo
{
public function ler(string $caminho): string;
public function escrever(string $caminho, string $conteudo): void;
}
// ── RealSubject ───────────────────────────────────────────────
class ServicoArquivoReal implements ServicoArquivo
{
public function ler(string $caminho): string
{
return "[conteúdo de {$caminho}]";
}
public function escrever(string $caminho, string $conteudo): void
{
echo "[Disco] Escrevendo em {$caminho}: {$conteudo}" . PHP_EOL;
}
}
// ── Value object de usuário ───────────────────────────────────
class Usuario
{
/** @param string[] $roles */
public function __construct(
public readonly string $nome,
public readonly array $roles
) {}
public function temPermissao(string $role): bool
{
return in_array($role, $this->roles, true)
|| in_array('admin', $this->roles, true);
}
}
// ── Proxy de proteção ─────────────────────────────────────────
class ServicoArquivoProtegido implements ServicoArquivo
{
public function __construct(
private readonly ServicoArquivoReal $real,
private readonly Usuario $usuario
) {}
public function ler(string $caminho): string
{
if (!$this->usuario->temPermissao('leitura')) {
throw new \RuntimeException(
"Usuário \"{$this->usuario->nome}\" não tem permissão de leitura."
);
}
echo "[Proxy] Acesso de leitura autorizado para {$this->usuario->nome}." . PHP_EOL;
return $this->real->ler($caminho);
}
public function escrever(string $caminho, string $conteudo): void
{
if (!$this->usuario->temPermissao('escrita')) {
throw new \RuntimeException(
"Usuário \"{$this->usuario->nome}\" não tem permissão de escrita."
);
}
echo "[Proxy] Acesso de escrita autorizado para {$this->usuario->nome}." . PHP_EOL;
$this->real->escrever($caminho, $conteudo);
}
}
// ── Uso ───────────────────────────────────────────────────────
$real = new ServicoArquivoReal();
$leitor = new Usuario('Ana', ['leitura']);
$proxyLeitura = new ServicoArquivoProtegido($real, $leitor);
echo $proxyLeitura->ler('/dados/vendas.csv') . PHP_EOL;
// [Proxy] Acesso de leitura autorizado para Ana.
// [conteúdo de /dados/vendas.csv]
try {
$proxyLeitura->escrever('/dados/vendas.csv', 'novo dado');
} catch (\RuntimeException $e) {
echo $e->getMessage() . PHP_EOL;
// Usuário "Ana" não tem permissão de escrita.
}
$admin = new Usuario('Carlos', ['admin']);
$proxyAdmin = new ServicoArquivoProtegido($real, $admin);
$proxyAdmin->escrever('/dados/vendas.csv', 'dado atualizado');
// [Proxy] Acesso de escrita autorizado para Carlos.
// [Disco] Escrevendo em /dados/vendas.csv: dado atualizado
Quando usar
- Lazy initialization (Proxy virtual): quando criar o objeto real é caro (em tempo, memória ou I/O) e ele pode não ser necessário na sessão atual — adie a criação até o primeiro acesso.
- Controle de acesso (Proxy de proteção): quando diferentes clientes devem ter permissões diferentes sobre o mesmo objeto. O proxy centraliza a verificação sem poluir o objeto real nem o cliente.
- Cache de resultados: quando o resultado de uma operação cara pode ser reutilizado em chamadas subsequentes com os mesmos parâmetros — o proxy armazena e retorna sem delegar novamente.
- Logging e auditoria: quando você precisa registrar toda chamada a um objeto sem modificar sua classe.
- Proxy remoto: quando o objeto real está em outro processo ou servidor. O proxy local cuida da serialização e comunicação.
Quando evitar
- Quando a latência importa criticamente: cada chamada passa por uma camada extra. Em loops de alta performance, a indireção pode ser perceptível.
- Quando a interface do objeto real muda com frequência: o proxy precisa acompanhar cada mudança de contrato — se o Subject é instável, o custo de manutenção pode superar o benefício.
- Como substituto para injeção de dependência: um Proxy de proteção que lida com autenticação global pode ser um sinal de que o design de autorização precisa ser revisado em nível arquitetural.
Prós e contras
Prós
- Controla o ciclo de vida do objeto real sem que o cliente saiba — lazy creation, cache, destruição sob demanda.
- Adiciona comportamento transversal (log, segurança, cache) sem modificar o objeto real nem o cliente.
- Princípio Aberto/Fechado: novos tipos de controle exigem apenas novos proxies, sem tocar no RealSubject.
- Facilita testes: em contextos de teste, um proxy pode simular o comportamento do objeto real sem depender de recursos externos.
Contras
- Adiciona uma camada de indireção que pode tornar o fluxo de execução menos óbvio na leitura do código.
- O proxy precisa implementar toda a interface do Subject — interfaces grandes geram muito boilerplate de delegação.
- O comportamento do sistema muda silenciosamente dependendo de qual implementação do Subject foi injetada — pode dificultar a depuração.
Armadilhas comuns
1. Confundir Proxy com Decorator
A armadilha mais frequente é implementar um Proxy quando a intenção é Decorator — ou vice-versa. A pergunta decisiva: você está controlando acesso ao objeto (Proxy) ou adicionando comportamento de negócio a ele (Decorator)? Um proxy de cache retorna o resultado sem chamar o real na segunda vez — isso é controle de acesso. Um decorator de log registra a chamada e sempre delega — isso é enriquecimento de comportamento. A estrutura é a mesma; a intenção é diferente.
2. Estado compartilhado no cache
Atenção: um proxy com cache precisa de uma política de invalidação clara. Se o objeto real pode mudar o estado subjacente (ex.: dados do banco atualizados por outra parte do sistema), o cache do proxy ficará desatualizado. Defina explicitamente: o cache é permanente na sessão, tem TTL, ou é invalidado por evento?
3. Proxy não é autorização de domínio
Um Proxy de proteção é adequado para controles simples e transversais (ex.: verificar se o usuário logado tem a role necessária). Não use Proxy para regras de negócio de autorização complexas que dependem do estado dos dados — nesse caso, a lógica pertence ao domínio ou a um serviço de autorização dedicado.
4. Esquecer de delegar todos os métodos
Se o Subject tem cinco métodos e o Proxy só intercepta um, os outros quatro ainda precisam ser implementados — geralmente delegando diretamente ao objeto real. É fácil esquecer um método e introduzir um bug silencioso onde a chamada "some" sem fazer nada (ou lança uma exceção inesperada em tempo de execução em linguagens dinâmicas).
Padrões relacionados
O Proxy compartilha estrutura com outros padrões e é frequentemente comparado ou combinado com eles:
O Decorator é o padrão com maior semelhança estrutural: ambos envolvem o objeto real implementando a mesma interface. A diferença está na intenção — Proxy controla acesso, Decorator adiciona comportamento. O Adapter também envolve o objeto, mas muda sua interface; o Proxy nunca muda a interface. O Composite compõe múltiplos objetos em uma árvore — o Proxy sempre representa exatamente um objeto real. O Bridge separa dois eixos de variação independentes em hierarquias próprias — é uma decisão de design prospectiva. O Proxy não separa eixos: representa exatamente um objeto real, mantém a mesma interface e controla o acesso a ele; Bridge e Proxy têm intenções e estruturas fundamentalmente distintas.