Objeto Proxy em JavaScript – Case de Uso

Objeto Proxy em JavaScript.

O modelo é uma das coisas mais reaproveitáveis em nossas aplicações. Nele, não faz sentido haver código de infraestrutura ou correspondente a frameworks. Esses periféricos mudam constantemente, e o cerne de sua aplicação não deve ter um acoplamento forte a eles.

Vou lhe apresentar uma situação abaixo, onde acabamos tendo que colocar código de atualização da view dentro do model. E como o Padrão de Projeto Proxy pode nos auxiliar a desacoplar as coisas. Para demonstrar a situação, criei uma pequena aplicação que cadastra e apaga clientes em uma lista.

Aqui está o arquivo do projeto, caso você queira acompanhar a evolução, já colocando o código para rodar. Aliás, recomendo fortemente. Eu, pelo menos, toda vez que tentei somente ver um vídeo ou ler um artigo sem colocar a mão na massa, me perdi pelo caminho e, de fato, não entendi a matéria.

No modelo ou cerne da aplicação, temos a representação de um contato para o nosso sistema, através de uma classe de mesmo nome.

E a modelagem de um objeto ListaDeContatos que, como o nome sugere, vai guardar a lista com os contatos que forem sendo inseridos.

Para a view, criamos alguns templates caseiros, utilizando somente recursos do JavaScript, como Templates String. Mas poderia ter sido usado algum framework, por exemplo, o React.

A View é uma superclasse e implementa o método update(), comum a todas as views.

As classes ContatosView e FormularioView herdam o método update de View, através de herança. Isso porque, update é comum.

O método template, é diferente para cada uma das classes. Por isso, cada uma implementa o seu.

Na superclasse, lançamos um erro sinalizando ao programador que ele deve implementar o template de cada classe filha que herdar de View.

O template de ContatosView gera a lista de contatos.

E o de FormularioView, o formulário por onde o usuário insere ou apaga ocorrências.

Por último, mas não menos importante, na raiz do projeto, temos o arquivo index.html. Onde tudo será montado e exibido ao usuário.

Os controllers serão via teclado, mas poderia ser por comandos de voz ou sinal de fumaça :) Isso muda constantemente, e deve estar isolado de nosso modelo. Por isso, já está em uma seção chamada controllers.

Até aqui, apresentamos o projeto, que servirá de base para nossa discussão. Ele está arquiteturado observando-se o paradigma de Orientação à Objetos.

Os mais experientes neste paradigma, verão que muita coisa pode ser aprimorada, especialmente em nossa classe CadastroController. Mas a ideia não é ensinar Orientação a Objetos. Nem as técnicas de Templates Declarativos, que passaram a ser mais viáveis com Templates String e programação funcional a partir do ES6.

O foco será aprender a utiliar o objeto Proxy, e mostrar um exemplo onde ele pode ser utilizado.

Entendendo o Problema

Para começar, gostaria de lhe convidar a dar uma olhada na classe CadastroController. Repare que: toda vez que uma atualização de view é requerida, o programador precisa fazê-la manualmente. Por exemplo: quando acontece a inserção ou exclusão de pessoas na lista, lá vai o programador e invoca o método update(), passando o novo estado da lista de contatos. Através da seguinte linha de código:

this._contatosView.update(this._listaDeContatos);

Temos ainda somente dois templates para serem atualizados. Imagine a hora que essa aplicação crescer, você vai lembrar de chamar a atualização da view todas as vezes? Nem eu... Por isso, nossa aplicação deve ser capaz de saber quando alguma view precisa ser atualizada.

No caso da ListaDeContatos, a view deve ser atualizada quando inserimos ou apagamos os contatos.

Isso poderia ser feito assim: estando em CadastroController, passamos uma função para dentro de ListaDeContatos via construtor. Essa função irá conter justamente o método update().

O método ainda continuará recebendo a lista de contatos. Então a função recebe esse parâmetro e o delega ao método.

Agora em ListaDeContatos, vamos receber a função pelo construtor com o nome de armadilha (trap). Pois este é o nome que você encontrará nas documentações do Proxy. Na sequência, invocá-la nos métodos sinalizados acima. A saber: adiciona e apaga.

A lista de contatos – que o método update precisa para funcionar – será exatamente o this. Pois já estamos no próprio contexto de ListaDeContatos.

A função foi declarada no contexto de CadastroController, mas será executada dentro de ListaDeContatos. Portanto, para que o this, dentro do escopo da função, continue sendo CadastroController precisamos usar uma Arrow Function. Do contrário, this adotará o contexto de envocação da função, que no caso, será ListaDeContatos.

Com isso, em CadastroController, já podemos remover todas as chamadas ao método update() que tinham por objetivo, atualizar a view de contatos.

A view do formulário, por onde o usuário interage com nossa aplicação, não muda no decorrer do tempo. Portanto, depois de instanciada, chamamos seu método update(), de modo a renderizar o formulário na tela. Essa chamada, deve permanecer.

Mas aí é que está o problema. Sujamos nosso modelo com código de atualização da view. É neste ponto que entra o objeto Proxy. Então vamos voltar o modelo ListaDeContatos ao que ele era antes. Sem essa história de armadilhas.

E manter nosso CadastroController também sem as chamadas de atualização de view. Como isso pode funcionar? Vamos ver.

Entendendo o Objeto Proxy

O Padrão de Projeto Proxy, já está implementado em JavaScript. Tudo o que você precisa fazer para usá-lo, é instanciar um proxy.

No console do navegador em nossa aplicação, podemos instanciar uma pessoa, por exemplo:

E posteriormente, acessar alguma propriedade dela, tipo o nome.

contato.nome; //"Suzete"

Mas também, podemos encapsular o objeto em uma proxy. Assim:

E o resultado ao invocar a propriedade nome será o mesmo.

contato.nome; //"Suzete"

Ao instanciar um proxy você passa, como primeiro parâmetro, o objeto a ser encapsulado. O segundo, é um handler (lidador). Onde estarão as armadilhas. Tudo o que for declarado entre chaves no JavaScript é considerado um objeto.

Disparando Armadilhas na Leitura de Propriedades

Nossa classe Contato possui getters para nome, email e telefone.

Então vamos colocar a armadilha que dispara quando um get for feito a alguma das propriedades.

A função lidadora do proxy para interceptar operações de leitura em propriedades chama-se get, e recebe três parâmetros: target, prop e receiver.

O target é o objeto real, encapsulado pelo proxy. Prop, a propriedade que está sendo lida. E o receiver, uma referência ao próprio proxy.

Na tira de código acima, acessamos as propriedades nome e email do contato instanciado. Dessa forma, nossa armadilha foi acionada. Porém, como não dissemos qual seria o retorno após a execução da armadilha, ele foi undefined.

Eu posso definir qualquer coisa como retorno. Veja:

Ou fazer uma operação de leitura na propriedade interceptada, que é justamente o retorno esperado por quem invocou o nosso get responsável por disparar a armadilha.

A leitura na propriedade é feita com Reflect.get(), onde target é o objeto original encapsulado pela proxy, prop refere-se à propriedade que estou acessando, e receiver é uma referência a meu proxy.

As propriedades da nossa classe Contato nada mais fazem do que um get nas propriedades do construtor da classe. Por isso que a armadilha dispara duas vezes.

Disparando Armadilhas na Modificação de Propriedades

O nosso interesse mesmo, é executar uma armadilha quando algo é atribuido. Seja a uma propriedade ou método.

Com isso em mente, para que tenhamos um exemplo, vamos criar um setter denominado sobrenome, em nossa classe Contato.

E adaptando nossa armadilha para interceptar propriedades de atribuição, alteramos a função de get para set. Essa nova função também recebe um parâmetro extra, chamado value. E claro, depois de executar a armadilha, passamos os parâmetros que a nossa propriedade sobrenome do objeto real está esperando, através do Reflect.set().

Mas, o proxy implementado pelo JavaScript não está preparado para interceptar métodos. E agora, o que fazer para interceptar nossos métodos adiciona e apaga no projeto que estamos usando de exemplo? Vamos para uma implementação caseira.

Disparando Armadilhas na Invocação de Métodos

Quando chamamos um método ou função, o JavaScript internamente executa um get e depois manda um apply – através da API de reflexão – com os parâmetros que o método ou função está esperando.

Então em nossa classe CadastroController, encapsulamos a ListaDeContatos em um Proxy;

No lidador do Proxy, colocamos a armadilha que dispara em operações de leitura;

Ao interceptar um getter, ele pode ser uma propriedade, método ou função. Dessa forma, antes de executar o código da minha armadilha, verifico se o que interceptei foi um método ou função.

Checo através de um if, comparando o typeof do getter interceptado com o de function. Isso porque, o typeof de um método ou função é function. Como a propriedade – prop – que estamos avaliando em nosso objeto – target – varia, usamos a notação de colchetes para deixar isso flexível.

Se tratando de um método ou função, ainda preciso ter em mente que não é para qualquer deles que irei disparar minha armadilha. Uma forma de filtrar, poderia ser utilizando o método includes() do objeto Array.

Uma vez que trata-se do método de interesse, vamos retornar uma função, que irá substituir (na instância do objeto encapsulado pelo proxy) o método interceptado.

Dentro dessa função, vamos chamar o método do objeto original que foi interceptado, através do método estático apply() da classe Reflect, atribuindo o contexto (this) e os arguments.

O arguments é necessário para métodos interceptados que passam parâmetros. Isso porque, a armadilha de leitura get do nosso lidador não dá acesso ao value, onde os parâmetros estaríam disponíveis.

E por último, atualizar a view. Aqui devemos salvar o this em uma variável externa, para garantir que _contatosView.update seja executado no contexto de CadastroController.

Caso o fluxo não tenha entrado em nosso if, significa que o getter interceptado foi então uma propriedade. Nesse caso, executamos simplesmente o get na propriedade, e deixamos a coisa seguir.

Recaptulando: a função retornada pela armadilha substitui o método interceptado na instância de lista de contatos encapsulada pelo proxy. Essa função faz então a chamada ao método do objeto real, através de Reflect.get e, na sequência, atualiza a view com o novo estado da lista.

Usamos bastante esta técnica aqui na Dev Content, como forma de atualizar nossas views automaticamente sem poluir o model. Esperamos que você possa também tirar proveito disso e usar deste artifício para modelar suas aplicações mais facilmente em MVC, nos casos em que usar um dos frameworks existentes no mercado seria exagero.

Artigos relacionados

Ir para o topo