CodeGym /Java blog /Tilfældig /Proxy design mønster
John Squirrels
Niveau
San Francisco

Proxy design mønster

Udgivet i gruppen
I programmering er det vigtigt at planlægge din applikations arkitektur korrekt. Designmønstre er en uundværlig måde at opnå dette på. Lad os i dag tale om fuldmagter.

Hvorfor har du brug for en proxy?

Dette mønster hjælper med at løse problemer forbundet med kontrolleret adgang til et objekt. Du kan spørge: "Hvorfor har vi brug for kontrolleret adgang?" Lad os se på et par situationer, der vil hjælpe dig med at finde ud af, hvad der er hvad.

Eksempel 1

Forestil dig, at vi har et stort projekt med en masse gammel kode, hvor der er en klasse, der er ansvarlig for at eksportere rapporter fra en database. Klassen arbejder synkront. Det vil sige, at hele systemet er inaktivt, mens databasen behandler anmodningen. I gennemsnit tager det 30 minutter at generere en rapport. Derfor starter eksportprocessen kl. 12.30, og ledelsen modtager rapporten om morgenen. En revision afslørede, at det ville være bedre straks at kunne modtage rapporten inden for normal åbningstid. Starttidspunktet kan ikke udskydes, og systemet kan ikke blokere, mens det venter på svar fra databasen. Løsningen er at ændre hvordan systemet fungerer, generere og eksportere rapporten på en separat tråd. Denne løsning vil lade systemet fungere som normalt, og ledelsen vil modtage friske rapporter. Imidlertid, der er et problem: den aktuelle kode kan ikke omskrives, da andre dele af systemet bruger dens funktionalitet. I dette tilfælde kan vi bruge proxy-mønsteret til at introducere en mellemliggende proxy-klasse, der vil modtage anmodninger om at eksportere rapporter, logge starttidspunktet og starte en separat tråd. Når rapporten er genereret, afsluttes tråden, og alle er glade.

Eksempel 2

Et udviklingsteam er ved at oprette en begivenhedswebside. For at få data om nye begivenheder forespørger teamet en tredjepartstjeneste. Et særligt privat bibliotek letter interaktionen med tjenesten. Under udviklingen opdages et problem: Tredjepartssystemet opdaterer sine data én gang om dagen, men der sendes en anmodning til det, hver gang en bruger opdaterer en side. Dette skaber et stort antal anmodninger, og tjenesten holder op med at svare. Løsningen er at cache tjenestens svar og returnere det cachelagrede resultat til besøgende, efterhånden som siderne genindlæses, og cachen opdateres efter behov. I dette tilfælde er proxy-designmønsteret en fremragende løsning, der ikke ændrer den eksisterende funktionalitet.

Princippet bag designmønsteret

For at implementere dette mønster skal du oprette en proxy-klasse. Den implementerer grænsefladen til serviceklassen og efterligner dens adfærd for klientkode. På denne måde interagerer klienten med en proxy i stedet for det rigtige objekt. Som udgangspunkt sendes alle anmodninger videre til serviceklassen, men med yderligere handlinger før eller efter. Kort sagt er en proxy et lag mellem klientkoden og målobjektet. Overvej eksemplet med cache-forespørgselsresultater fra en gammel og meget langsom harddisk. Antag, at vi taler om en køreplan for elektriske tog i en gammel app, hvis logik ikke kan ændres. En disk med opdateret tidsplan indsættes hver dag på et fast tidspunkt. Så vi har:
  1. TrainTimetableinterface.
  2. ElectricTrainTimetable, som implementerer denne grænseflade.
  3. Klientkoden interagerer med filsystemet gennem denne klasse.
  4. TimetableDisplayklientklasse. Dens printTimetable()metode bruger klassens metoder ElectricTrainTimetable.
Diagrammet er enkelt: Proxy-designmønster: - 2På nuværende tidspunkt, med hvert kald af printTimetable()metoden, ElectricTrainTimetablefår klassen adgang til disken, indlæser dataene og præsenterer dem for klienten. Systemet fungerer okay, men det er meget langsomt. Som et resultat blev det besluttet at øge systemets ydeevne ved at tilføje en caching-mekanisme. Dette kan gøres ved hjælp af proxy-mønsteret: Proxy-designmønster: - 3Således TimetableDisplaybemærker klassen ikke engang, at den interagerer med ElectricTrainTimetableProxyklassen i stedet for den gamle klasse. Den nye implementering indlæser tidsplanen én gang om dagen. Ved gentagne anmodninger returnerer den det tidligere indlæste objekt fra hukommelsen.

Hvilke opgaver er bedst for en proxy?

Her er et par situationer, hvor dette mønster helt sikkert vil komme til nytte:
  1. Caching
  2. Forsinket eller doven initialisering Hvorfor indlæse et objekt med det samme, hvis du kan indlæse det efter behov?
  3. Logningsanmodninger
  4. Mellembekræftelse af data og adgang
  5. Starter arbejdstråde
  6. Optagelse af adgang til et objekt
Og der er også andre use cases. Ved at forstå princippet bag dette mønster kan du identificere situationer, hvor det kan anvendes med succes. Ved første øjekast gør en proxy det samme som en facade , men det er ikke tilfældet. En proxy har samme grænseflade som serviceobjektet. Du må heller ikke forveksle dette mønster med dekorations- eller adaptermønstrene . En dekorator giver en udvidet grænseflade, og en adapter giver en alternativ grænseflade.

Fordele og ulemper

  • + Du kan styre adgangen til serviceobjektet, som du ønsker det
  • + Yderligere evner relateret til styring af serviceobjektets livscyklus
  • + Det fungerer uden et serviceobjekt
  • + Det forbedrer ydeevne og kodesikkerhed.
  • - Der er en risiko for, at ydeevnen kan blive dårligere på grund af yderligere anmodninger
  • - Det gør klassehierarkiet mere kompliceret

Proxy-mønsteret i praksis

Lad os implementere et system, der læser togkøreplaner fra en harddisk:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Her er klassen, der implementerer hovedgrænsefladen:

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 togkøreplanen, læser programmet en fil fra disken. Men det er kun begyndelsen på vores problemer. Hele filen læses hver gang du får køreplanen for selv et enkelt tog! Det er godt, at en sådan kode kun findes i eksempler på, hvad man ikke skal gø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]);
       }
   }
}
Eksempel fil:

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
Lad os teste 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
Lad os nu gennemgå de nødvendige trin for at introducere vores mønster:
  1. Definer en grænseflade, der tillader brugen af ​​en proxy i stedet for det originale objekt. I vores eksempel er dette TrainTimetable.

  2. Opret proxy-klassen. Det skal have en reference til serviceobjektet (opret det i klassen eller videregive til konstruktøren).

    Her er vores proxy-klasse:

    
    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 stadium opretter vi simpelthen en klasse med en reference til det originale objekt og videresender alle opkald til det.

  3. Lad os implementere logikken i proxyklassen. Grundlæggende bliver opkald altid omdirigeret til det originale objekt.

    
    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()kontrollerer, om tidsplanarrayet er blevet cachelagret i hukommelsen. Hvis ikke, sender den en anmodning om at indlæse dataene fra disken og gemmer resultatet. Hvis tidsplanen allerede er blevet anmodet om, returnerer den hurtigt objektet fra hukommelsen.

    Takket være dens enkle funktionalitet behøvede getTrainDepartureTime()-metoden ikke at blive omdirigeret til det originale objekt. Vi har simpelthen duplikeret dens funktionalitet i en ny metode.

    Gør ikke dette. Hvis du skal duplikere koden eller gøre noget lignende, så gik noget galt, og du skal se på problemet igen fra en anden vinkel. I vores simple eksempel havde vi ingen anden mulighed. Men i rigtige projekter vil koden højst sandsynligt være skrevet mere korrekt.

  4. I klientkoden skal du oprette et proxyobjekt i stedet for det originale objekt:

    
    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]);
           }
       }
    }
    

    Kontrollere

    
    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, det fungerer korrekt.

    Du kan også overveje muligheden for en fabrik, der opretter både et originalt objekt og et proxy-objekt, afhængigt af visse betingelser.

Før vi siger farvel, er her et nyttigt link

Det var alt for i dag! Det ville ikke være en dårlig idé at vende tilbage til lektionerne og prøve din nye viden i praksis :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION