De ce ai nevoie de un proxy?
Acest model ajută la rezolvarea problemelor asociate cu accesul controlat la un obiect. Puteți întreba: „De ce avem nevoie de acces controlat?” Să ne uităm la câteva situații care vă vor ajuta să vă dați seama ce este.Exemplul 1
Imaginați-vă că avem un proiect mare cu o grămadă de cod vechi, unde există o clasă responsabilă pentru exportul rapoartelor dintr-o bază de date. Clasa funcționează sincron. Adică, întregul sistem este inactiv în timp ce baza de date procesează cererea. În medie, este nevoie de 30 de minute pentru a genera un raport. În consecință, procesul de export începe la ora 12:30, iar conducerea primește raportul dimineața. Un audit a relevat că ar fi mai bine să primiți imediat raportul în timpul orelor normale de lucru. Ora de începere nu poate fi amânată, iar sistemul nu poate bloca în timp ce așteaptă un răspuns din baza de date. Soluția este de a schimba modul în care funcționează sistemul, generând și exportând raportul pe un fir separat. Această soluție va permite sistemului să funcționeze ca de obicei, iar conducerea va primi rapoarte noi. In orice caz, există o problemă: codul actual nu poate fi rescris, deoarece alte părți ale sistemului își folosesc funcționalitatea. În acest caz, putem folosi modelul proxy pentru a introduce o clasă intermediară de proxy care va primi cereri de export de rapoarte, va înregistra ora de începere și va lansa un fir separat. Odată generat raportul, firul se termină și toată lumea este fericită.Exemplul 2
O echipă de dezvoltare creează un site web pentru evenimente. Pentru a obține date despre evenimente noi, echipa solicită un serviciu terță parte. O bibliotecă privată specială facilitează interacțiunea cu serviciul. În timpul dezvoltării, se descoperă o problemă: sistemul terț își actualizează datele o dată pe zi, dar îi este trimisă o solicitare de fiecare dată când un utilizator reîmprospătează o pagină. Acest lucru creează un număr mare de solicitări, iar serviciul nu mai răspunde. Soluția este să memorați în cache răspunsul serviciului și să returnați rezultatul stocat în cache vizitatorilor pe măsură ce paginile sunt reîncărcate, actualizând memoria cache după cum este necesar. În acest caz, modelul de design proxy este o soluție excelentă care nu schimbă funcționalitatea existentă.Principiul din spatele modelului de design
Pentru a implementa acest model, trebuie să creați o clasă proxy. Implementează interfața clasei de servicii, mimând comportamentul acesteia pentru codul client. În acest mod, clientul interacționează cu un proxy în loc de obiectul real. De regulă, toate solicitările sunt transmise clasei de servicii, dar cu acțiuni suplimentare înainte sau după. Mai simplu spus, un proxy este un strat între codul clientului și obiectul țintă. Luați în considerare exemplul de stocare în cache a rezultatelor interogărilor de la un hard disk vechi și foarte lent. Să presupunem că vorbim despre un orar pentru trenurile electrice într-o aplicație veche a cărei logică nu poate fi schimbată. Un disc cu un orar actualizat este introdus în fiecare zi la o oră fixă. Deci avem:TrainTimetable
interfata.ElectricTrainTimetable
, care implementează această interfață.- Codul client interacționează cu sistemul de fișiere prin această clasă.
TimetableDisplay
clasa de client. Metoda saprintTimetable()
folosește metodele claseiElectricTrainTimetable
.

printTimetable()
metodei, ElectricTrainTimetable
clasa accesează discul, încarcă datele și le prezintă clientului. Sistemul funcționează bine, dar este foarte lent. Ca urmare, a fost luată decizia de a crește performanța sistemului prin adăugarea unui mecanism de stocare în cache. Acest lucru se poate face folosind modelul proxy: 
TimetableDisplay
clasa nici măcar nu observă că interacționează cu ElectricTrainTimetableProxy
clasa în loc de clasa veche. Noua implementare încarcă orarul o dată pe zi. Pentru solicitările repetate, returnează din memorie obiectul încărcat anterior.
Ce sarcini sunt cele mai bune pentru un proxy?
Iată câteva situații în care acest model va fi cu siguranță util:- Memorarea în cache
- Inițializare întârziată sau leneșă De ce să încărcați un obiect imediat dacă îl puteți încărca după cum este necesar?
- Cereri de înregistrare
- Verificarea intermediară a datelor și accesul
- Începeți firele de lucru
- Înregistrarea accesului la un obiect
Avantaje și dezavantaje
- + Puteți controla accesul la obiectul de serviciu oricum doriți
- + Abilități suplimentare legate de gestionarea ciclului de viață al obiectului de serviciu
- + Funcționează fără un obiect de serviciu
- + Îmbunătățește performanța și securitatea codului.
- - Există riscul ca performanța să se înrăutățească din cauza solicitărilor suplimentare
- - Face ierarhia clasei mai complicată
Modelul proxy în practică
Să implementăm un sistem care citește orarele trenurilor de pe un hard disk:
public interface TrainTimetable {
String[] getTimetable();
String getTrainDepartureTime();
}
Iată clasa care implementează interfața 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 "";
}
}
De fiecare dată când obțineți orarul trenului, programul citește un fișier de pe disc. Dar acesta este doar începutul necazurilor noastre. Întregul fișier este citit de fiecare dată când obțineți orarul chiar și pentru un singur tren! Este bine că un astfel de cod există doar în exemple de ceea ce nu trebuie făcut :) Clasa client:
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]);
}
}
}
Exemplu de fișier:
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
Să-l testăm:
public static void main(String[] args) {
TimetableDisplay timetableDisplay = new timetableDisplay();
timetableDisplay.printTimetable();
}
Ieșire:
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
Acum să parcurgem pașii necesari pentru a introduce modelul nostru:
-
Definiți o interfață care să permită utilizarea unui proxy în locul obiectului original. În exemplul nostru, acesta este
TrainTimetable
. -
Creați clasa proxy. Ar trebui să aibă o referință la obiectul de serviciu (creați-l în clasă sau treceți la constructor).
Iată clasa noastră 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; } }
În această etapă, pur și simplu creăm o clasă cu o referință la obiectul original și redirecționăm toate apelurile către acesta.
-
Să implementăm logica clasei proxy. Practic, apelurile sunt întotdeauna redirecționate către obiectul 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; } }
Verifică
getTimetable()
dacă matricea orarului a fost stocată în cache în memorie. Dacă nu, trimite o solicitare de încărcare a datelor de pe disc și salvează rezultatul. Dacă orarul a fost deja solicitat, acesta returnează rapid obiectul din memorie.Datorită funcționalității sale simple, metoda getTrainDepartureTime() nu a trebuit să fie redirecționată către obiectul original. Pur și simplu i-am duplicat funcționalitatea într-o nouă metodă.
Nu face asta. Dacă trebuie să duplicați codul sau să faceți ceva similar, atunci ceva a mers prost și trebuie să priviți problema din nou dintr-un unghi diferit. În exemplul nostru simplu, nu aveam altă opțiune. Dar în proiectele reale, codul va fi cel mai probabil scris mai corect.
-
În codul clientului, creați un obiect proxy în loc de obiectul 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]); } } }
Verifica
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
Super, funcționează corect.
Ați putea lua în considerare și opțiunea unei fabrici care creează atât un obiect original, cât și un obiect proxy, în funcție de anumite condiții.
GO TO FULL VERSION