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:TrainTimetable
interface.ElectricTrainTimetable
, que implementa essa interface.- O código do cliente interage com o sistema de arquivos por meio dessa classe.
TimetableDisplay
classe cliente. SeuprintTimetable()
método usa os métodos daElectricTrainTimetable
classe.

printTimetable()
método, a ElectricTrainTimetable
classe 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: 
TimetableDisplay
classe nem percebe que está interagindo com a ElectricTrainTimetableProxy
classe 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:- Cache
- Inicialização atrasada ou preguiçosa Por que carregar um objeto imediatamente se você pode carregá-lo conforme necessário?
- Pedidos de registro
- Verificação intermediária de dados e acesso
- Iniciando threads de trabalho
- Gravando o acesso a um objeto
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:
-
Defina uma interface que permita o uso de um proxy em vez do objeto original. No nosso exemplo, isso é
TrainTimetable
. -
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.
-
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.
-
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.
GO TO FULL VERSION