Pular para o conteúdo principal

Proxy

Descrição​

O Proxy fornece um substituto (ou representante) para outro objeto, controlando o acesso a ele. O proxy implementa a mesma interface do objeto real (o RealSubject), de modo que o cliente não percebe se está falando com o objeto verdadeiro ou com o intermediário.

Ao receber uma chamada, o proxy pode executar lógica adicional antes ou depois de delegar ao objeto real: criar o objeto apenas quando necessário (virtual proxy), verificar permissões (protection proxy), encaminhar a chamada para outro processo ou máquina (remote proxy), armazenar resultados em cache (caching proxy) ou registrar uso e contar referências (smart reference).

A diferença em relação a outros wrappers está na intenção: o Proxy não acrescenta funcionalidades de negócio nem muda a interface, ele controla quando, como e se o acesso ao objeto real acontece.

Problemas que resolve​

  • Objetos caros de criar (imagens grandes, conexões, documentos) sendo instanciados mesmo quando nunca são usados.
  • Necessidade de aplicar controle de acesso a um objeto sem modificar sua classe.
  • Chamadas a objetos remotos que exigem serialização, rede e tratamento de falhas, poluindo o código cliente.
  • Repetição de operações custosas que poderiam ser cacheadas de forma transparente.
  • Falta de um ponto central para logging, métricas ou contagem de referências de um recurso.

Quando usar​

  • Quando é preciso adiar a criação ou o carregamento de um objeto pesado até o primeiro uso (lazy initialization).
  • Quando o acesso a um objeto deve ser restrito conforme o perfil ou as permissões do cliente.
  • Quando um objeto vive em outro espaço de endereçamento (outro processo, serviço ou servidor) e o cliente deve usá-lo como se fosse local.
  • Quando se deseja adicionar cache, auditoria ou rate limiting de forma transparente ao cliente.
  • Quando o objeto real vem de uma biblioteca de terceiros e não pode ser alterado.

Quando evitar​

  • Quando o objeto real é barato e não há necessidade de controle de acesso: o proxy só acrescenta indireção.
  • Quando a latência extra introduzida pela camada intermediária é crítica.
  • Quando o objetivo é acrescentar comportamento empilhável e combinável; nesse caso o Decorator é mais adequado.
  • Quando a transparência esconde custos importantes (por exemplo, chamadas de rede que parecem locais) e isso leva a uso ingênuo e ineficiente.

Trade-offs​

GanhoCusto / Risco
Controle do ciclo de vida e do acesso ao objeto real sem que o cliente saibaCamada extra de indireção e mais classes para manter
Lazy loading reduz uso de memória e tempo de inicializaçãoO primeiro acesso pode ter latência inesperada
Respeita o princípio aberto/fechado: novas políticas sem alterar o RealSubjectA interface do proxy precisa acompanhar toda mudança na interface do Subject
Cache e remote proxy simplificam o código clienteTransparência pode ocultar falhas de rede, dados obsoletos e custos reais
  • Acoplamento: o cliente depende apenas da interface Subject, o que facilita trocar o objeto real pelo proxy via injeção de dependência.
  • Testabilidade: proxies podem ser testados isoladamente com um RealSubject falso; mocks de frameworks costumam ser proxies dinâmicos.
  • Concorrência: proxies com lazy initialization ou cache precisam de cuidado com acesso concorrente.
  • Ferramentas: muitas linguagens oferecem proxies dinâmicos (Java java.lang.reflect.Proxy, JavaScript Proxy, AOP em frameworks), reduzindo código repetitivo.

Diagrama​

O Client trabalha apenas com a interface Subject. Tanto RealSubject quanto Proxy a implementam; o Proxy mantém uma referência ao RealSubject e decide quando e se deve delegar a chamada, aplicando controle de acesso, lazy loading ou cache.

Exemplo​

Um visualizador de documentos exibe miniaturas de várias imagens em alta resolução. Carregar todas do disco na abertura seria lento, então cada imagem é representada por um proxy que só carrega o arquivo quando ela precisa ser exibida.

interface Image {
display(): void;
}

class HighResImage implements Image {
constructor(private path: string) {
console.log(`Carregando ${path} do disco...`); // operação cara
}
display(): void {
console.log(`Exibindo ${this.path}`);
}
}

class ImageProxy implements Image {
private real?: HighResImage;
constructor(private path: string) {}

display(): void {
if (!this.real) {
this.real = new HighResImage(this.path); // lazy loading
}
this.real.display();
}
}

const gallery: Image[] = [new ImageProxy("a.png"), new ImageProxy("b.png")];
gallery[0].display(); // carrega e exibe apenas a.png

Padrões relacionados​

  • Adapter — também envolve outro objeto, mas fornece uma interface diferente; o Proxy mantém a mesma interface.
  • Decorator — estrutura quase idêntica, porém o Decorator acrescenta responsabilidades e é empilhável, enquanto o Proxy controla o acesso e geralmente gerencia o ciclo de vida do objeto real.
  • Facade — também é um intermediário, mas simplifica um subsistema inteiro em vez de representar um único objeto.
  • Flyweight — proxies podem ser usados para compartilhar e referenciar objetos flyweight de forma transparente.
  • Singleton — o proxy costuma ser o ponto de acesso controlado a um recurso único e caro.

Referência​