Fala, galera! Tudo certo?
Hoje vamos abordar o último pattern do grupo “Estruturais“: o Proxy.
Neste começo de ano meus trabalhos aqui no portal ainda não estão a todo vapor, pois ainda estou fazendo o planejamento de artigos, eventos, etc, mas logo voltaremos com a programação semanal de artigos, além de novidades! Aguardem!
Chega de papo, vamos falar sobre o pattern Proxy!
Proxy
O objetivo principal do Proxy é encapsular um objeto através de um outro objeto que possui a mesma interface, de forma que o segundo objeto, conhecido como “Proxy”, controla o acesso ao primeiro, que é o objeto real.
Calma, vamos dar um exemplo para explicar melhor. Imagine que você tem que prover acesso a objetos com uma grande quantidade de recursos, como um arquivo grande, uma coleção com muitos itens, mas não deseja instanciar esse objeto até que o mesmo seja realmente requisitado pelo sistema.
Um exemplo simples seria carregar um arquivo apenas na primeira vez que ele for requisitado, e caso seja mais de uma vez, aproveitar o arquivo já carregado.
Fazer isso manualmente implica em incluir vários IF’s ou um método à parte para controlar quando a instância é nula ou não. Carregar o arquivo acaba deixando o código mais complexo e ainda faz com que esse trecho de código assuma mais responsabilidades do que o necessário, quebrando um dos princípios do SOLID, o princípio responsabilidade única (S).
Utilizando o Proxy, temos um objeto que implementa a mesma interface que o objeto original implementa, mas fornecendo um controle de acesso a este objeto original, instanciado dentro do Proxy. Toda chamada para o objeto original passa e é controlada pelo objeto Proxy.
Abaixo temos o diagrama UML deste exemplo:
No diagrama acima temos cada um dos itens, com suas responsabilidades descritas abaixo:
- Interface Imagem: interface que define o contrato para nossos objetos, com o método exibir, responsável por exibir dados da imagem no Console.
- Classe ImagemArquivo: implementação da interface, com uma propriedade para armazenar o nome do arquivo a ser carregado, a implementação do método para exibir dados da imagem no Console e o método responsável por carregar o arquivo da imagem do disco para a memória.
- Classe ImagemProxy: este é o objeto que aplica o padrão Proxy. Ele implementa a mesma interface do objeto ImagemArquivo e possui uma propriedade/variável do tipo ImagemArquivo para armazenar a instância desta classe. Ao chamar o método exibir, este verificará se a variável imagemArquivo já está instanciada, sendo que se estiver, já chama o método exibir() da instância. Caso contrário irá instanciar a classe ImagemArquivo, adicionar o nome do arquivo, conforme a propriedade definida na classe ImagemProxy, e chamar, na sequência, os métodos carregar() e exibir() da instância de ImagemArquivo(). Chamadas feitas após este momento, aproveitarão essa instância, não gastando tempo novamente com o carregamento do arquivo.
- Classe ProxyDemo: aplicação Console que instancia a classe Proxy e utiliza a mesma para exibir dados de uma imagem.
Notem que no exemplo acima todas as responsabilidades ficaram divididas de forma simples, onde uma classe tem a responsabilidade pelo carregamento e exibição da imagem, outra tem a responsabilidade de controlar este acesso (Proxy) e nosso console, que apenas utiliza o Proxy.
Nossa classe de Imagem propriamente dita, e a Console não têm nenhuma lógica para controle de acesso, tornando nosso código e a manutenção do mesmo, mais simples e fácil.
Vamos ver agora a ficha resumo do pattern, onde podemos conferir a implementação do exemplo em C#, além de alguns detalhes extras como prós e contras no uso deste pattern.
Ficha Resumo
- Nome: Proxy
- Objetivo/intenção: encapsular um objeto através de um outro objeto que possui a mesma interface, de forma que o segundo objeto, conhecido como “Proxy”, controle o acesso ao primeiro, que é o objeto real;
- Motivação: quando temos um controle ou modificações adicionais a serem realizadas durante o acesso a um determinado objeto, precisamos de um modo simples para fazer isso, sem adicionar complexidade/responsabilidade adicional na classe que faz esse acesso
- Aplicabilidade: o pattern Proxy é bastante utilizado em aplicações J2EE, onde temos um controle grande sobre o acesso à determinados objetos e funcionalidades da plataforma, e em sistemas onde precisamos controlar o instanciamento de objetos conforme a necessidade, durante a execução do aplicativo, sem adicionar complexidade e responsabilidades adicionais para a classe que faz o acesso
- Estrutura: no desenho abaixo temos um exemplo UML de aplicação do pattern, já descrito no início do artigo com a responsabilidade de cada parte do diagrama:
- Consequências: a aplicação do pattern Proxy tem como vantagens a maior eficiência e menor custo de nossa aplicação em termos de processamento, dissociação de clientes da localização de componentes de servidor remoto e a separação do código de limpeza da funcionalidade. Em contrapartida, para alguns casos, sua implementação pode tornar-se complexa e até mesmo diminuir a eficiência do sistema devido a uma implementação mal realizada
- Implementações: abaixo temos um exemplo de código em C# para o pattern:
using System;
namespace ProxyPattern
{
public interface Imagem
{
void Exibir();
}
public class ImagemArquivo : Imagem
{
public string NomeArquivo { get; private set; }
private byte[] _file;
public ImagemArquivo(string nomeArquivo)
{
NomeArquivo = nomeArquivo;
_file = new byte[0];
}
public void Exibir()
{
Console.WriteLine($"Arquivo carregado {NomeArquivo} com {_file.Length} bytes.");
}
public void Carregar()
{
_file = System.IO.File.ReadAllBytes(NomeArquivo);
}
}
public class ImagemProxy : Imagem
{
public string NomeArquivo { get; private set; }
private ImagemArquivo _imagemInstance;
public ImagemProxy(string nomeArquivo)
{
NomeArquivo = nomeArquivo;
}
public void Exibir()
{
if (_imagemInstance == null)
{
_imagemInstance = new ImagemArquivo(NomeArquivo);
_imagemInstance.Carregar();
}
_imagemInstance.Exibir();
}
}
class Program
{
static void Main(string[] args)
{
var imagem = new ImagemProxy("C:\\teste.jpg");
imagem.Exibir();
}
}
}
- Usos conhecidos: o exemplo mais utilizado é quando estamos trabalhando com EJBs na pltaforma J2EE do Java, no uso pelo objeto “javax.ejb.EJBObject”, que representa uma referência remota ao EJB. Para o cliente que está utilizando a interface remote de um EJB, é transparente a chamada remota ao servidor, permitindo que complexos sistemas distribuídos possam ser desenvolvidos como se fossem chamadas locais
- Padrões relacionados: O padrão Adapter tem funcionalidade semelhante, mas provendo uma interface diferente para o objeto, enquanto no Proxy utilizamos a mesma interface. O padrão Decorator e o Proxy têm objetivos diferentes, apesar de suas estruturas serem bem semelhantes
Concluindo
Hoje aprendemos sobre o pattern Proxy: qual sua utilidade e quando aplicar. Um detalhe que temos que ter atenção, até para evitar confusão com os patterns Adapter e Decorator, é que o Proxy e o objeto real que está sendo encapsulado devem implementar a mesma interface.
Por hoje deixo este material para vocês e na semana que vem iniciamos com o grupo de patterns Comportamentais, começando pelo pattern “Chain of Responsability“.
Para quem perdeu a série desde o início, segue o link para a primeira parte, onde listo todos os patterns que vamos abordar, cada um com o link correspondente:
Um abraço a todos e até a próxima!