Hvorfor trenger du en proxy?
Dette mønsteret hjelper til med å løse problemer knyttet til kontrollert tilgang til et objekt. Du kan spørre: "Hvorfor trenger vi kontrollert tilgang?" La oss se på et par situasjoner som vil hjelpe deg å finne ut hva som er hva.Eksempel 1
Tenk deg at vi har et stort prosjekt med en haug med gammel kode, hvor det er en klasse som er ansvarlig for å eksportere rapporter fra en database. Klassen jobber synkront. Det vil si at hele systemet er inaktivt mens databasen behandler forespørselen. I gjennomsnitt tar det 30 minutter å generere en rapport. Følgelig starter eksportprosessen klokken 12:30, og ledelsen mottar rapporten om morgenen. En revisjon avdekket at det ville være bedre å umiddelbart kunne motta rapporten i normal arbeidstid. Starttidspunktet kan ikke utsettes, og systemet kan ikke blokkere mens det venter på svar fra databasen. Løsningen er å endre hvordan systemet fungerer, generere og eksportere rapporten på en egen tråd. Denne løsningen lar systemet fungere som normalt, og ledelsen vil motta ferske rapporter. Derimot, det er et problem: gjeldende kode kan ikke skrives om, siden andre deler av systemet bruker funksjonaliteten. I dette tilfellet kan vi bruke proxy-mønsteret til å introdusere en mellomliggende proxy-klasse som vil motta forespørsler om å eksportere rapporter, logge starttiden og starte en egen tråd. Når rapporten er generert, avsluttes tråden og alle er fornøyde.Eksempel 2
Et utviklingsteam lager et arrangementsnettsted. For å få data om nye hendelser, spør teamet en tredjepartstjeneste. Et eget privat bibliotek legger til rette for samhandling med tjenesten. Under utviklingen oppdages et problem: tredjepartssystemet oppdaterer dataene sine en gang om dagen, men en forespørsel sendes til det hver gang en bruker oppdaterer en side. Dette skaper et stort antall forespørsler, og tjenesten slutter å svare. Løsningen er å cache tjenestens svar og returnere det hurtigbufrede resultatet til besøkende etter hvert som sidene lastes inn på nytt, og oppdatere cachen etter behov. I dette tilfellet er proxy-designmønsteret en utmerket løsning som ikke endrer den eksisterende funksjonaliteten.Prinsippet bak designmønsteret
For å implementere dette mønsteret, må du opprette en proxy-klasse. Den implementerer grensesnittet til tjenesteklassen, og etterligner dens oppførsel for klientkode. På denne måten samhandler klienten med en proxy i stedet for det virkelige objektet. Som regel sendes alle forespørsler videre til serviceklassen, men med tilleggshandlinger før eller etter. Enkelt sagt er en proxy et lag mellom klientkoden og målobjektet. Tenk på eksempelet med bufring av spørringsresultater fra en gammel og veldig treg harddisk. Anta at vi snakker om en rutetabell for elektriske tog i en gammel app hvis logikk ikke kan endres. En disk med oppdatert timeplan settes inn hver dag til et fast tidspunkt. Så vi har:TrainTimetable
grensesnitt.ElectricTrainTimetable
, som implementerer dette grensesnittet.- Klientkoden samhandler med filsystemet gjennom denne klassen.
TimetableDisplay
klientklasse. MetodenprintTimetable()
bruker metodene til klassenElectricTrainTimetable
.

printTimetable()
metoden, ElectricTrainTimetable
får klassen tilgang til disken, laster dataene og presenterer dem for klienten. Systemet fungerer greit, men det er veldig tregt. Som et resultat ble beslutningen tatt om å øke systemytelsen ved å legge til en hurtigbuffermekanisme. Dette kan gjøres ved hjelp av proxy-mønsteret: 
TimetableDisplay
legger klassen ikke engang merke til at den samhandler med ElectricTrainTimetableProxy
klassen i stedet for den gamle klassen. Den nye implementeringen laster timeplanen en gang om dagen. For gjentatte forespørsler returnerer den det tidligere lastede objektet fra minnet.
Hvilke oppgaver er best for en proxy?
Her er noen situasjoner der dette mønsteret definitivt vil komme til nytte:- Buffer
- Forsinket eller lat initialisering Hvorfor laste inn et objekt med en gang hvis du kan laste det etter behov?
- Logging forespørsler
- Mellomverifisering av data og tilgang
- Starter arbeidertråder
- Registrering av tilgang til et objekt
Fordeler og ulemper
- + Du kan kontrollere tilgangen til tjenesteobjektet slik du ønsker
- + Ytterligere evner knyttet til å administrere livssyklusen til tjenesteobjektet
- + Det fungerer uten et tjenesteobjekt
- + Det forbedrer ytelsen og kodesikkerheten.
- – Det er en risiko for at ytelsen kan bli dårligere på grunn av tilleggsforespørsler
- – Det gjør klassehierarkiet mer komplisert
Proxymønsteret i praksis
La oss implementere et system som leser togruter fra en harddisk:
public interface TrainTimetable {
String[] getTimetable();
String getTrainDepartureTime();
}
Her er klassen som implementerer hovedgrensesnittet:
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 "";
}
}
Hver gang du får rutetabellen, leser programmet en fil fra disken. Men det er bare begynnelsen på våre problemer. Hele filen leses hver gang du får rutetabellen for til og med ett enkelt tog! Det er bra at slik kode bare finnes i eksempler på hva man ikke skal gjøre :) Klientklasse:
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]);
}
}
}
Eksempelfil:
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
La oss teste det:
public static void main(String[] args) {
TimetableDisplay timetableDisplay = new timetableDisplay();
timetableDisplay.printTimetable();
}
Produksjon:
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
La oss nå gå gjennom trinnene som kreves for å introdusere mønsteret vårt:
-
Definer et grensesnitt som tillater bruk av en proxy i stedet for det opprinnelige objektet. I vårt eksempel er dette
TrainTimetable
. -
Opprett proxy-klassen. Det skal ha en referanse til tjenesteobjektet (opprett det i klassen eller send det til konstruktøren).
Her er proxy-klassen vår:
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; } }
På dette stadiet oppretter vi ganske enkelt en klasse med en referanse til det opprinnelige objektet og videresender alle anrop til det.
-
La oss implementere logikken til proxy-klassen. I utgangspunktet blir anrop alltid omdirigert til det opprinnelige objektet.
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; } }
Kontrollerer
getTimetable()
om timeplan-arrayet har blitt bufret i minnet. Hvis ikke, sender den en forespørsel om å laste dataene fra disken og lagrer resultatet. Hvis timeplanen allerede er forespurt, returnerer den raskt objektet fra minnet.Takket være dens enkle funksjonalitet, trengte ikke getTrainDepartureTime()-metoden å bli omdirigert til det opprinnelige objektet. Vi dupliserte ganske enkelt funksjonaliteten i en ny metode.
Ikke gjør dette. Hvis du må duplisere koden eller gjøre noe lignende, så gikk noe galt, og du må se på problemet på nytt fra en annen vinkel. I vårt enkle eksempel hadde vi ikke noe annet alternativ. Men i virkelige prosjekter vil koden mest sannsynlig være skrevet mer riktig.
-
I klientkoden oppretter du et proxy-objekt i stedet for det opprinnelige objektet:
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]); } } }
Kryss av
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
Flott, det fungerer riktig.
Du kan også vurdere alternativet for en fabrikk som lager både et originalobjekt og et proxy-objekt, avhengig av visse forhold.
GO TO FULL VERSION