CodeGym /Blogue Java /Random-PT /Padrão de design de proxy
John Squirrels
Nível 41
San Francisco

Padrão de design de proxy

Publicado no grupo Random-PT
Na programação, é importante planejar corretamente a arquitetura do seu aplicativo. Os padrões de projeto são uma maneira indispensável de conseguir isso. Hoje vamos falar sobre proxies.

Por que você precisa de um proxy?

Esse padrão ajuda a resolver problemas associados ao acesso controlado a um objeto. Você pode perguntar: "Por que precisamos de acesso controlado?" Vejamos algumas situações que o ajudarão a descobrir o que é o quê.

Exemplo 1

Imagine que temos um projeto grande com um monte de código antigo, onde existe uma classe responsável por exportar relatórios de um banco de dados. A classe funciona de forma síncrona. Ou seja, todo o sistema fica ocioso enquanto o banco de dados processa a requisição. Em média, leva 30 minutos para gerar um relatório. Assim, o processo de exportação começa às 12h30 e a gerência recebe o relatório pela manhã. Uma auditoria revelou que seria melhor poder receber o relatório imediatamente durante o horário comercial normal. A hora de início não pode ser adiada e o sistema não pode bloquear enquanto aguarda uma resposta do banco de dados. A solução é mudar o funcionamento do sistema, gerando e exportando o relatório em uma thread separada. Esta solução permitirá que o sistema funcione normalmente e a administração receberá novos relatórios. No entanto, há um problema: o código atual não pode ser reescrito, pois outras partes do sistema usam sua funcionalidade. Nesse caso, podemos usar o padrão de proxy para introduzir uma classe proxy intermediária que receberá solicitações para exportar relatórios, registrar a hora de início e iniciar um thread separado. Depois que o relatório é gerado, o thread é encerrado e todos ficam felizes.

Exemplo 2

Uma equipe de desenvolvimento está criando um site de eventos. Para obter dados sobre novos eventos, a equipe consulta um serviço terceirizado. Uma biblioteca particular especial facilita a interação com o serviço. Durante o desenvolvimento, um problema é descoberto: o sistema de terceiros atualiza seus dados uma vez por dia, mas uma solicitação é enviada a ele toda vez que um usuário atualiza uma página. Isso cria um grande número de solicitações e o serviço para de responder. A solução é armazenar em cache a resposta do serviço e retornar o resultado armazenado em cache aos visitantes à medida que as páginas são recarregadas, atualizando o cache conforme necessário. Nesse caso, o padrão de design proxy é uma excelente solução que não altera a funcionalidade existente.

O princípio por trás do padrão de projeto

Para implementar esse padrão, você precisa criar uma classe proxy. Ele implementa a interface da classe de serviço, imitando seu comportamento para o código do cliente. Desta forma, o cliente interage com um proxy em vez do objeto real. Via de regra, todas as requisições são repassadas para a classe de serviço, mas com ações adicionais antes ou depois. Simplificando, um proxy é uma camada entre o código do cliente e o objeto de destino. Considere o exemplo de armazenar em cache os resultados da consulta de um disco rígido antigo e muito lento. Suponha que estamos falando sobre um horário de trens elétricos em algum aplicativo antigo cuja lógica não pode ser alterada. Um disco com um horário atualizado é inserido todos os dias em um horário fixo. Então nós temos:
  1. TrainTimetableinterface.
  2. ElectricTrainTimetable, que implementa essa interface.
  3. O código do cliente interage com o sistema de arquivos por meio dessa classe.
  4. TimetableDisplayclasse cliente. Seu printTimetable()método usa os métodos da ElectricTrainTimetableclasse.
O diagrama é simples: Padrão de design de proxy: - 2atualmente, a cada chamada do printTimetable()método, a ElectricTrainTimetableclasse acessa o disco, carrega os dados e os apresenta ao cliente. O sistema funciona bem, mas é muito lento. Como resultado, foi tomada a decisão de aumentar o desempenho do sistema adicionando um mecanismo de cache. Isso pode ser feito usando o padrão proxy: Padrão de design de proxy: - 3Assim, a TimetableDisplayclasse nem percebe que está interagindo com a ElectricTrainTimetableProxyclasse em vez da classe antiga. A nova implementação carrega o horário uma vez por dia. Para solicitações repetidas, ele retorna o objeto carregado anteriormente da memória.

Quais tarefas são melhores para um proxy?

Aqui estão algumas situações em que esse padrão definitivamente será útil:
  1. Cache
  2. Inicialização atrasada ou preguiçosa Por que carregar um objeto imediatamente se você pode carregá-lo conforme necessário?
  3. Pedidos de registro
  4. Verificação intermediária de dados e acesso
  5. Iniciando threads de trabalho
  6. Gravando o acesso a um objeto
E também existem outros casos de uso. Compreendendo o princípio por trás desse padrão, você pode identificar situações em que ele pode ser aplicado com sucesso. À primeira vista, um proxy faz a mesma coisa que uma fachada , mas não é o caso. Um proxy tem a mesma interface que o objeto de serviço. Além disso, não confunda esse padrão com os padrões decorador ou adaptador . Um decorador fornece uma interface estendida e um adaptador fornece uma interface alternativa.

Vantagens e desvantagens

  • + Você pode controlar o acesso ao objeto de serviço como desejar
  • + Habilidades adicionais relacionadas ao gerenciamento do ciclo de vida do objeto de serviço
  • + Funciona sem um objeto de serviço
  • + Melhora o desempenho e a segurança do código.
  • - Existe o risco de o desempenho piorar devido a solicitações adicionais
  • - Torna a hierarquia de classes mais complicada

O padrão de proxy na prática

Vamos implementar um sistema que lê horários de trens a partir de um disco rígido:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Aqui está a classe que implementa a interface principal:

public class ElectricTrainTimetable implements TrainTimetable {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for (int i = 0; i < timetable.length; i++) {
           if (timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
Cada vez que você obtém o horário do trem, o programa lê um arquivo do disco. Mas isso é apenas o começo de nossos problemas. O arquivo inteiro é lido toda vez que você obtém o horário de um único trem! É bom que esse código exista apenas em exemplos do que não fazer :) Classe do cliente:

public class TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       for (int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
Exemplo de arquivo:

9B-6854;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
Vamos testar:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Saída:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
Agora vamos percorrer as etapas necessárias para introduzir nosso padrão:
  1. Defina uma interface que permita o uso de um proxy em vez do objeto original. No nosso exemplo, isso é TrainTimetable.

  2. Crie a classe proxy. Deve ter uma referência ao objeto de serviço (crie-o na classe ou passe para o construtor).

    Aqui está nossa classe de proxy:

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    Neste estágio, estamos simplesmente criando uma classe com uma referência ao objeto original e encaminhando todas as chamadas para ele.

  3. Vamos implementar a lógica da classe proxy. Basicamente, as chamadas são sempre redirecionadas para o objeto original.

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }
    

    O getTimetable()verifica se a matriz de horário foi armazenada em cache na memória. Caso contrário, ele envia uma solicitação para carregar os dados do disco e salva o resultado. Se o horário já foi solicitado, ele retorna rapidamente o objeto da memória.

    Graças à sua funcionalidade simples, o método getTrainDepartureTime() não precisava ser redirecionado para o objeto original. Simplesmente duplicamos sua funcionalidade em um novo método.

    Não faça isso. Se você precisar duplicar o código ou fazer algo semelhante, algo deu errado e você precisará examinar o problema novamente de um ângulo diferente. Em nosso exemplo simples, não tínhamos outra opção. Mas em projetos reais, o código provavelmente será escrito de forma mais correta.

  4. No código do cliente, crie um objeto proxy em vez do objeto original:

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           for (int i = 0; i < timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }
    

    Verificar

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  04:48  08:49  04:01
    

    Ótimo, funcionou corretamente.

    Você também pode considerar a opção de uma fábrica que cria um objeto original e um objeto proxy, dependendo de certas condições.

Antes de nos despedirmos, aqui está um link útil

Isso é tudo por hoje! Não seria uma má ideia voltar às aulas e experimentar na prática os seus novos conhecimentos :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION