CodeGym /Blog Java /Aleatoriu /Model de design proxy
John Squirrels
Nivel
San Francisco

Model de design proxy

Publicat în grup
În programare, este important să planificați corect arhitectura aplicației. Modelele de design sunt o modalitate indispensabilă de a realiza acest lucru. Astăzi să vorbim despre proxy.

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:
  1. TrainTimetableinterfata.
  2. ElectricTrainTimetable, care implementează această interfață.
  3. Codul client interacționează cu sistemul de fișiere prin această clasă.
  4. TimetableDisplayclasa de client. Metoda sa printTimetable()folosește metodele clasei ElectricTrainTimetable.
Diagrama este simplă: Model de design proxy: - 2în prezent, cu fiecare apel al printTimetable()metodei, ElectricTrainTimetableclasa 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: Model de design proxy: - 3Astfel, TimetableDisplayclasa nici măcar nu observă că interacționează cu ElectricTrainTimetableProxyclasa î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:
  1. Memorarea în cache
  2. 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?
  3. Cereri de înregistrare
  4. Verificarea intermediară a datelor și accesul
  5. Începeți firele de lucru
  6. Înregistrarea accesului la un obiect
Și există și alte cazuri de utilizare. Înțelegând principiul din spatele acestui model, puteți identifica situațiile în care poate fi aplicat cu succes. La prima vedere, un proxy face același lucru ca o fațadă , dar nu este cazul. Un proxy are aceeași interfață ca obiectul de serviciu. De asemenea, nu confundați acest model cu modelele decoratorului sau adaptorului . Un decorator oferă o interfață extinsă, iar un adaptor oferă o interfață alternativă.

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:
  1. Definiți o interfață care să permită utilizarea unui proxy în locul obiectului original. În exemplul nostru, acesta este TrainTimetable.

  2. 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.

  3. 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.

  4. Î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.

Înainte de a ne lua rămas bun, iată un link util

Asta e tot pentru azi! Nu ar fi o idee rea să te întorci la lecții și să-ți încerci noile cunoștințe în practică :)
Comentarii
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION