CodeGym /Java-blogg /Tilfeldig /Proxy-designmønster
John Squirrels
Nivå
San Francisco

Proxy-designmønster

Publisert i gruppen
I programmering er det viktig å planlegge applikasjonens arkitektur riktig. Designmønstre er en uunnværlig måte å oppnå dette på. La oss i dag snakke om fullmakter.

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:
  1. TrainTimetablegrensesnitt.
  2. ElectricTrainTimetable, som implementerer dette grensesnittet.
  3. Klientkoden samhandler med filsystemet gjennom denne klassen.
  4. TimetableDisplayklientklasse. Metoden printTimetable()bruker metodene til klassen ElectricTrainTimetable.
Diagrammet er enkelt: Proxy-designmønster: - 2For øyeblikket, med hvert kall av printTimetable()metoden, ElectricTrainTimetablefå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: Proxy-designmønster: - 3Dermed TimetableDisplaylegger klassen ikke engang merke til at den samhandler med ElectricTrainTimetableProxyklassen 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:
  1. Buffer
  2. Forsinket eller lat initialisering Hvorfor laste inn et objekt med en gang hvis du kan laste det etter behov?
  3. Logging forespørsler
  4. Mellomverifisering av data og tilgang
  5. Starter arbeidertråder
  6. Registrering av tilgang til et objekt
Og det er også andre brukstilfeller. Når du forstår prinsippet bak dette mønsteret, kan du identifisere situasjoner der det kan brukes med hell. Ved første øyekast gjør en proxy det samme som en fasade , men det er ikke tilfelle. En proxy har samme grensesnitt som tjenesteobjektet. Ikke forveksle dette mønsteret med dekorasjons- eller adaptermønstrene . En dekoratør gir et utvidet grensesnitt, og en adapter gir et alternativt grensesnitt.

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:
  1. Definer et grensesnitt som tillater bruk av en proxy i stedet for det opprinnelige objektet. I vårt eksempel er dette TrainTimetable.

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

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

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

Før vi sier farvel, her er en nyttig lenke

Det var alt for i dag! Det ville ikke være en dårlig idé å gå tilbake til timene og prøve ut den nye kunnskapen din i praksis :)
Kommentarer
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION