Varför behöver du en proxy?
Detta mönster hjälper till att lösa problem i samband med kontrollerad åtkomst till ett objekt. Du kanske frågar "Varför behöver vi kontrollerad åtkomst?" Låt oss titta på ett par situationer som hjälper dig att ta reda på vad som är vad.Exempel 1
Föreställ dig att vi har ett stort projekt med en massa gammal kod, där det finns en klass som ansvarar för att exportera rapporter från en databas. Klassen arbetar synkront. Det vill säga att hela systemet är inaktivt medan databasen bearbetar begäran. I genomsnitt tar det 30 minuter att generera en rapport. Följaktligen startar exportprocessen klockan 12:30 och ledningen får rapporten på morgonen. En granskning visade att det vore bättre att omedelbart kunna få rapporten under normal kontorstid. Starttiden kan inte skjutas upp och systemet kan inte blockera medan det väntar på svar från databasen. Lösningen är att ändra hur systemet fungerar, generera och exportera rapporten på en separat tråd. Denna lösning låter systemet fungera som vanligt och ledningen kommer att få färska rapporter. Dock, det finns ett problem: den aktuella koden kan inte skrivas om, eftersom andra delar av systemet använder dess funktionalitet. I det här fallet kan vi använda proxymönstret för att introducera en mellanliggande proxyklass som tar emot förfrågningar om att exportera rapporter, logga starttiden och starta en separat tråd. När rapporten väl är genererad avslutas tråden och alla är nöjda.Exempel 2
Ett utvecklingsteam skapar en evenemangswebbplats. För att få data om nya händelser frågar teamet en tredjepartstjänst. Ett särskilt privat bibliotek underlättar interaktionen med tjänsten. Under utvecklingen upptäcks ett problem: tredjepartssystemet uppdaterar sina data en gång om dagen, men en begäran skickas till det varje gång en användare uppdaterar en sida. Detta skapar ett stort antal förfrågningar och tjänsten slutar svara. Lösningen är att cachelagra tjänstens svar och returnera det cachade resultatet till besökarna när sidor laddas om, uppdatera cachen efter behov. I det här fallet är proxydesignmönstret en utmärkt lösning som inte ändrar den befintliga funktionaliteten.Principen bakom designmönstret
För att implementera detta mönster måste du skapa en proxyklass. Den implementerar gränssnittet för tjänsteklassen och efterliknar dess beteende för klientkod. På detta sätt interagerar klienten med en proxy istället för det verkliga objektet. Som regel skickas alla förfrågningar vidare till serviceklassen, men med ytterligare åtgärder före eller efter. Enkelt uttryckt är en proxy ett lager mellan klientkoden och målobjektet. Tänk på exemplet med cachning av frågeresultat från en gammal och mycket långsam hårddisk. Anta att vi pratar om en tidtabell för elektriska tåg i någon gammal app vars logik inte kan ändras. En diskett med uppdaterad tidtabell sätts in varje dag vid en fast tidpunkt. Så vi har:TrainTimetable
gränssnitt.ElectricTrainTimetable
, som implementerar detta gränssnitt.- Klientkoden interagerar med filsystemet genom denna klass.
TimetableDisplay
kundklass. DessprintTimetable()
metod använder klassens metoderElectricTrainTimetable
.

printTimetable()
metoden, ElectricTrainTimetable
kommer klassen åt disken, laddar data och presenterar den för klienten. Systemet fungerar okej, men det är väldigt långsamt. Som ett resultat togs beslutet att öka systemets prestanda genom att lägga till en cachningsmekanism. Detta kan göras med hjälp av proxymönstret: 
TimetableDisplay
märker klassen inte ens att den interagerar med ElectricTrainTimetableProxy
klassen istället för den gamla klassen. Den nya implementeringen laddar tidtabellen en gång om dagen. För upprepade förfrågningar returnerar den det tidigare laddade objektet från minnet.
Vilka uppgifter är bäst för en proxy?
Här är några situationer där det här mönstret definitivt kommer att vara användbart:- Cachning
- Fördröjd eller lat initialisering Varför ladda ett objekt direkt om du kan ladda det efter behov?
- Loggningsförfrågningar
- Mellanliggande verifiering av data och åtkomst
- Startar arbetartrådar
- Spela in åtkomst till ett objekt
Fördelar och nackdelar
- + Du kan styra åtkomsten till serviceobjektet hur du vill
- + Ytterligare förmågor relaterade till att hantera serviceobjektets livscykel
- + Det fungerar utan ett serviceobjekt
- + Det förbättrar prestanda och kodsäkerhet.
- – Det finns en risk att prestandan kan bli sämre på grund av ytterligare förfrågningar
- – Det gör klasshierarkin mer komplicerad
Proxymönstret i praktiken
Låt oss implementera ett system som läser tågtidtabeller från en hårddisk:
public interface TrainTimetable {
String[] getTimetable();
String getTrainDepartureTime();
}
Här är klassen som implementerar huvudgränssnittet:
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 "";
}
}
Varje gång du får tågtidtabellen läser programmet en fil från disken. Men det är bara början på våra problem. Hela filen läses varje gång du får tidtabellen för även ett enda tåg! Det är bra att sådan kod bara finns i exempel på vad man inte ska göra :) Klientklass:
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]);
}
}
}
Exempelfil:
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
Låt oss testa det:
public static void main(String[] args) {
TimetableDisplay timetableDisplay = new timetableDisplay();
timetableDisplay.printTimetable();
}
Produktion:
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
Låt oss nu gå igenom stegen som krävs för att introducera vårt mönster:
-
Definiera ett gränssnitt som tillåter användning av en proxy istället för det ursprungliga objektet. I vårt exempel är detta
TrainTimetable
. -
Skapa proxyklassen. Det bör ha en referens till serviceobjektet (skapa det i klassen eller skicka till konstruktorn).
Här är vår proxyklass:
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; } }
I det här skedet skapar vi helt enkelt en klass med en referens till det ursprungliga objektet och vidarebefordrar alla anrop till den.
-
Låt oss implementera logiken i proxyklassen. I princip omdirigeras anrop alltid till det ursprungliga 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; } }
Den
getTimetable()
kontrollerar om tidtabellsarrayen har cachelagrats i minnet. Om inte, skickar den en begäran om att ladda data från disken och sparar resultatet. Om tidtabellen redan har begärts, returnerar den snabbt objektet från minnet.Tack vare sin enkla funktionalitet behövde getTrainDepartureTime()-metoden inte omdirigeras till det ursprungliga objektet. Vi har helt enkelt duplicerat dess funktionalitet i en ny metod.
Gör inte det här. Om du måste duplicera koden eller göra något liknande, gick något fel, och du måste titta på problemet igen från en annan vinkel. I vårt enkla exempel hade vi inget annat alternativ. Men i riktiga projekt kommer koden med största sannolikhet att skrivas mer korrekt.
-
Skapa ett proxyobjekt i klientkoden istället för originalobjektet:
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]); } } }
Kolla upp
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
Jättebra, det fungerar korrekt.
Du kan också överväga alternativet för en fabrik som skapar både ett originalobjekt och ett proxyobjekt, beroende på vissa förutsättningar.
GO TO FULL VERSION