CodeGym /Java Blog /Willekeurig /Proxy-ontwerppatroon
John Squirrels
Niveau 41
San Francisco

Proxy-ontwerppatroon

Gepubliceerd in de groep Willekeurig
Bij het programmeren is het belangrijk om de architectuur van uw applicatie correct te plannen. Ontwerppatronen zijn een onmisbare manier om dit te bereiken. Laten we het vandaag hebben over proxy's.

Waarom heb je een volmacht nodig?

Dit patroon helpt bij het oplossen van problemen die verband houden met gecontroleerde toegang tot een object. U vraagt ​​zich misschien af: "Waarom hebben we gecontroleerde toegang nodig?" Laten we een paar situaties bekijken die u zullen helpen erachter te komen wat wat is.

voorbeeld 1

Stel je voor dat we een groot project hebben met een heleboel oude code, waarbij er een klasse is die verantwoordelijk is voor het exporteren van rapporten uit een database. De klas werkt synchroon. Dat wil zeggen, het hele systeem is inactief terwijl de database het verzoek verwerkt. Gemiddeld duurt het 30 minuten om een ​​rapport te genereren. Dienovereenkomstig start het exportproces om 12.30 uur en ontvangt het management de melding in de ochtend. Uit een audit bleek dat het beter zou zijn om de rapportage direct tijdens kantooruren te kunnen ontvangen. De starttijd kan niet worden uitgesteld en het systeem kan niet blokkeren terwijl het wacht op een reactie van de database. De oplossing is om de manier waarop het systeem werkt te veranderen door het rapport te genereren en te exporteren naar een aparte thread. Met deze oplossing werkt het systeem zoals gewoonlijk en ontvangt het management nieuwe rapporten. Echter, er is een probleem: de huidige code kan niet worden herschreven, omdat andere delen van het systeem de functionaliteit ervan gebruiken. In dit geval kunnen we het proxypatroon gebruiken om een ​​tussenliggende proxyklasse te introduceren die verzoeken ontvangt om rapporten te exporteren, de starttijd vast te leggen en een aparte thread te starten. Zodra het rapport is gegenereerd, wordt de thread beëindigd en is iedereen tevreden.

Voorbeeld 2

Een ontwikkelingsteam maakt een evenementenwebsite. Om gegevens over nieuwe gebeurtenissen te krijgen, vraagt ​​het team een ​​service van derden. Een speciale privébibliotheek vergemakkelijkt de interactie met de dienst. Tijdens de ontwikkeling wordt een probleem ontdekt: het systeem van derden werkt zijn gegevens één keer per dag bij, maar elke keer dat een gebruiker een pagina vernieuwt, wordt er een verzoek naar verzonden. Hierdoor ontstaat een groot aantal verzoeken en reageert de service niet meer. De oplossing is om de reactie van de service in de cache op te slaan en het resultaat in de cache terug te sturen naar bezoekers wanneer pagina's opnieuw worden geladen, waarbij de cache indien nodig wordt bijgewerkt. In dit geval is het proxy-ontwerppatroon een uitstekende oplossing die de bestaande functionaliteit niet verandert.

Het principe achter het ontwerppatroon

Om dit patroon te implementeren, moet u een proxyklasse maken. Het implementeert de interface van de serviceklasse en bootst zijn gedrag na voor clientcode. Op deze manier communiceert de client met een proxy in plaats van met het echte object. In de regel worden alle verzoeken doorgegeven aan de serviceklasse, maar met aanvullende acties ervoor of erna. Simpel gezegd, een proxy is een laag tussen de clientcode en het doelobject. Neem het voorbeeld van het cachen van queryresultaten van een oude en erg trage harde schijf. Stel dat we het hebben over een dienstregeling voor elektrische treinen in een oude app waarvan de logica niet kan worden veranderd. Elke dag wordt er op een vast tijdstip een schijfje met een bijgewerkte dienstregeling geplaatst. Dus we hebben:
  1. TrainTimetablekoppel.
  2. ElectricTrainTimetable, die deze interface implementeert.
  3. De clientcode communiceert via deze klasse met het bestandssysteem.
  4. TimetableDisplayklant klasse. De printTimetable()methode gebruikt de methoden van de ElectricTrainTimetableklasse.
Het diagram is eenvoudig: op dit moment heeft de klasse Proxy-ontwerppatroon: - 2bij elke aanroep van de methode toegang tot de schijf, laadt de gegevens en presenteert deze aan de client. Het systeem functioneert goed, maar is erg traag. Als gevolg hiervan werd besloten om de systeemprestaties te verbeteren door een cachingmechanisme toe te voegen. Dit kan worden gedaan met behulp van het proxypatroon: de klasse merkt dus niet eens dat deze interactie heeft met de klasse in plaats van met de oude klasse. De nieuwe implementatie laadt het rooster één keer per dag. Voor herhaalverzoeken retourneert het het eerder geladen object uit het geheugen. printTimetable()ElectricTrainTimetableProxy-ontwerppatroon: - 3TimetableDisplayElectricTrainTimetableProxy

Welke taken zijn het beste voor een proxy?

Hier zijn een paar situaties waarin dit patroon zeker van pas zal komen:
  1. Caching
  2. Vertraagde of luie initialisatie Waarom zou u een object meteen laden als u het naar behoefte kunt laden?
  3. Verzoeken loggen
  4. Tussentijdse verificatie van gegevens en toegang
  5. Werknemersthreads starten
  6. Toegang tot een object vastleggen
En er zijn ook andere use-cases. Als u het principe achter dit patroon begrijpt, kunt u situaties identificeren waarin het met succes kan worden toegepast. Op het eerste gezicht doet een proxy hetzelfde als een façade , maar dat is niet het geval. Een proxy heeft dezelfde interface als het serviceobject. Verwar dit patroon ook niet met de decorateur- of adapterpatronen . Een decorateur biedt een uitgebreide interface en een adapter biedt een alternatieve interface.

Voor-en nadelen

  • + U kunt de toegang tot het serviceobject regelen zoals u dat wilt
  • + Aanvullende mogelijkheden met betrekking tot het beheer van de levenscyclus van het serviceobject
  • + Het werkt zonder een serviceobject
  • + Het verbetert de prestaties en codebeveiliging.
  • - Het risico bestaat dat de prestaties verslechteren als gevolg van aanvullende verzoeken
  • - Het maakt de klassenhiërarchie ingewikkelder

Het proxypatroon in de praktijk

Laten we een systeem implementeren dat treindienstregelingen van een harde schijf leest:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Dit is de klasse die de hoofdinterface implementeert:

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 "";
   }
}
Elke keer dat u de dienstregeling van de trein ontvangt, leest het programma een bestand van schijf. Maar dat is nog maar het begin van onze problemen. Het hele bestand wordt gelezen elke keer dat u de dienstregeling krijgt voor zelfs maar een enkele trein! Het is goed dat dergelijke code alleen bestaat in voorbeelden van wat je niet moet doen :) Cliëntklasse:

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

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
Laten we het testen:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
Uitgang:

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
Laten we nu de stappen doorlopen die nodig zijn om ons patroon te introduceren:
  1. Definieer een interface die het gebruik van een proxy toestaat in plaats van het oorspronkelijke object. In ons voorbeeld is dit TrainTimetable.

  2. Maak de proxyklasse. Het moet een verwijzing naar het serviceobject hebben (maak het in de klasse of geef het door aan de constructor).

    Dit is onze proxyklasse:

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

    In dit stadium maken we gewoon een klasse met een verwijzing naar het oorspronkelijke object en sturen we alle oproepen ernaar door.

  3. Laten we de logica van de proxyklasse implementeren. In principe worden oproepen altijd doorgestuurd naar het oorspronkelijke object.

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

    Hiermee getTimetable()wordt gecontroleerd of de roosterreeks in het geheugen is opgeslagen. Zo niet, dan stuurt het een verzoek om de gegevens van schijf te laden en slaat het resultaat op. Als het tijdschema al is opgevraagd, haalt het het object snel uit het geheugen terug.

    Dankzij de eenvoudige functionaliteit hoefde de methode getTrainDepartureTime() niet te worden omgeleid naar het oorspronkelijke object. We hebben de functionaliteit eenvoudigweg gedupliceerd in een nieuwe methode.

    Doe dit niet. Als je de code moet dupliceren of iets soortgelijks moet doen, dan is er iets misgegaan en moet je het probleem opnieuw vanuit een andere hoek bekijken. In ons eenvoudige voorbeeld hadden we geen andere optie. Maar in echte projecten zal de code hoogstwaarschijnlijk correcter worden geschreven.

  4. Maak in de clientcode een proxy-object in plaats van het oorspronkelijke object:

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

    Rekening

    
    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
    

    Geweldig, het werkt naar behoren.

    U kunt ook de optie overwegen van een fabriek die zowel een origineel object als een proxy-object maakt, afhankelijk van bepaalde voorwaarden.

Voordat we afscheid nemen, is hier een handige link

Dat is alles voor vandaag! Het zou geen slecht idee zijn om terug te keren naar de lessen en je nieuwe kennis in de praktijk uit te proberen :)
Opmerkingen
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION