JavaRush /Blogue Java /Random-PT /Padrão de design de proxy

Padrão de design de proxy

Publicado no grupo Random-PT
Na programação, é importante planejar adequadamente a arquitetura da aplicação. Uma ferramenta indispensável para isso são os padrões de projeto. Hoje falaremos sobre Proxy, ou seja, Deputado.

Por que você precisa de um deputado?

Esse padrão ajuda a resolver problemas associados ao acesso controlado a um objeto. Você pode ter uma pergunta: “Por que precisamos desse acesso controlado?” Vejamos algumas situações que o ajudarão a descobrir o que é o quê.

Exemplo 1

Vamos imaginar que temos um projeto grande com um monte de código antigo, onde existe uma classe responsável por baixar relatórios do banco de dados. A classe funciona de forma síncrona, ou seja, todo o sistema fica ocioso enquanto o banco de dados processa a solicitação. Em média, um relatório é gerado em 30 minutos. Por conta dessa funcionalidade, seu upload começa às 00h30, e a administração recebe esse relatório pela manhã. Durante a análise, constatou-se que é necessário receber o relatório imediatamente após sua geração, ou seja, em até um dia. É impossível reprogramar o horário de lançamento, pois o sistema aguardará uma resposta do banco de dados. A solução é mudar o princípio de funcionamento iniciando o upload e a geração do relatório em uma thread separada. Esta solução permitirá que o sistema funcione normalmente e a gestão receberá novos relatórios. Porém, há um problema: o código atual não pode ser reescrito, pois suas funções são utilizadas por outras partes do sistema. Nesse caso, você pode introduzir uma classe de proxy intermediária usando o padrão Deputado, que receberá uma solicitação para fazer upload de um relatório, registrar o horário de início e lançar um thread separado. Quando o relatório for gerado, o thread concluirá seu trabalho e todos ficarão felizes.

Exemplo 2

A equipe de desenvolvimento cria um site de pôster. Para obter dados sobre novos eventos, recorrem a um serviço terceirizado, cuja interação é realizada por meio de uma biblioteca especial fechada. Durante o desenvolvimento, surgiu um problema: um sistema de terceiros atualiza os dados uma vez por dia e uma solicitação ocorre sempre que o usuário atualiza a 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 fornecer aos visitantes o resultado salvo em cada reinicialização, atualizando esse cache conforme necessário. Neste caso, utilizar o padrão Deputado é uma excelente solução sem alterar a funcionalidade finalizada.

Como o padrão funciona

Para implementar esse padrão, você precisa criar uma classe proxy. Implementa uma interface de classe de serviço, simulando seu comportamento para o código do cliente. Assim, ao invés do objeto real, o cliente interage com seu proxy. Normalmente, todas as solicitações são repassadas para a classe de serviço, mas com ações adicionais antes ou depois de sua chamada. Simplificando, este objeto proxy é uma camada entre o código do cliente e o objeto de destino. Vejamos um exemplo de armazenamento em cache de uma solicitação de um disco antigo muito lento. Que seja um horário de trem elétrico em alguma aplicação antiga, cujo princípio de funcionamento não pode ser alterado. O disco com a programação atualizada é inserido todos os dias em um horário fixo. Então nós temos:
  1. Interface TimetableTrains.
  2. A classe TimetableElectricTrainsque implementa esta interface.
  3. É por meio dessa classe que o código do cliente interage com o sistema de arquivos do disco.
  4. Classe cliente DisplayTimetable. Seu método printTimetable()usa métodos de classe TimetableElectricTrains.
O esquema é simples: Padrão de design de proxy - 2Atualmente, toda vez que um método é chamado, printTimetable()a classe TimetableElectricTrainsacessa o disco, descarrega os dados e os fornece ao cliente. Este sistema funciona bem, mas é muito lento. Portanto, foi decidido aumentar o desempenho do sistema adicionando um mecanismo de cache. Isso pode ser feito utilizando o padrão Proxy: Padrão de design de proxy - 3Dessa forma a turma DisplayTimetablenem perceberá que está interagindo com a turma TimetableElectricTrainsProxye não com a anterior. A nova implementação carrega o agendamento uma vez por dia e, após solicitações repetidas, retorna o objeto já carregado da memória.

Para quais tarefas é melhor usar o Proxy?

Aqui estão algumas situações em que esse padrão certamente será útil:
  1. Cache.
  2. A implementação lenta também é conhecida como implementação lenta. Por que carregar um objeto de uma só vez quando você pode carregá-lo conforme necessário?
  3. Registrando solicitações.
  4. Dados provisórios e verificações de acesso.
  5. Lançando threads de processamento paralelo.
  6. Gravar ou contar o histórico de uma chamada.
Existem outros casos de uso também. Compreendendo o princípio de funcionamento desse padrão, você mesmo poderá encontrar uma aplicação bem-sucedida para ele. À primeira vista, Deputado faz a mesma coisa que Facade , mas não é. O Proxy possui a mesma interface do objeto de serviço. Além disso, não confunda o padrão com Decorator ou Adapter . O Decorator fornece uma interface estendida, enquanto o Adaptador fornece uma alternativa.

Vantagens e desvantagens

  • + Você pode controlar o acesso ao objeto de serviço como desejar;
  • + Capacidades adicionais para gerenciar o ciclo de vida de um objeto de serviço;
  • + Funciona sem objeto de serviço;
  • + Melhora o desempenho e a segurança do código.
  • - Existe risco de deterioração do desempenho devido a tratamentos adicionais;
  • - Complica a estrutura das classes do programa.

Padrão substituto na prática

Vamos implementar com você um sistema que lê horários de trens do disco:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Uma classe que implementa a interface principal:
public class TimetableElectricTrains implements TimetableTrains {

   @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ê tenta obter a programação de todos os trens, o programa lê o arquivo do disco. Mas ainda são flores. O arquivo também é lido toda vez que você precisa obter a programação de apenas um trem! É bom que esse código só exista em exemplos ruins :) Classe cliente:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       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]);
       }
   }
}
Arquivo de exemplo:

9B-6854;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Vamos testar:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Conclusão:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Agora vamos seguir as etapas de implementação do nosso padrão:
  1. Defina uma interface que permita usar um novo proxy em vez do objeto original. No nosso exemplo é TimetableTrains.

  2. Crie uma classe de proxy. Deve conter uma referência a um objeto de serviço (criar em uma classe ou passar em um construtor);

    Aqui está nossa classe de proxy:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

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

  3. Implementamos a lógica da classe proxy. Basicamente a chamada é sempre redirecionada para o objeto original.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    O método getTimetable()verifica se a matriz de agendamento está armazenada em cache na memória. Caso contrário, emite uma solicitação para carregar os dados do disco, armazenando o resultado. Se a solicitação já estiver em execução, ela retornará rapidamente um objeto da memória.

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

    Você não pode fazer isso. Se você teve que duplicar o código ou realizar manipulações semelhantes, significa que algo deu errado e você precisa analisar o problema de um ângulo diferente. Em nosso exemplo simples não há outra maneira, mas em projetos reais, muito provavelmente, o código será escrito de forma mais correta.

  4. Substitua a criação do objeto original no código do cliente por um objeto de substituição:

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           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]);
           }
       }
    }

    Exame

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Ótimo, funciona corretamente.

    Você também pode considerar uma fábrica que criará o objeto original e um objeto substituto dependendo de determinadas condições.

Link útil em vez de um ponto

  1. Excelente artigo sobre padrões e um pouco sobre “Deputy”

Isso é tudo por hoje! Seria bom voltar a aprender e testar seus novos conhecimentos na prática :)
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION