Desenvolvimento

20 fev, 2019

Arquitetura e desenvolvimento de software – Parte 13: Proxy

100 visualizações
Publicidade

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:

Exemplo em UML do padrão Proxy sendo aplicado.

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:
Exemplo em UML do padrão Proxy sendo aplicado.
  • 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!