CodeGym /Java-Blog /Random-DE /Proxy-Entwurfsmuster
Autor
Artem Divertitto
Senior Android Developer at United Tech

Proxy-Entwurfsmuster

Veröffentlicht in der Gruppe Random-DE
Bei der Programmierung ist es wichtig, die Architektur Ihrer Anwendung richtig zu planen. Designmuster sind ein unverzichtbarer Weg, dies zu erreichen. Lassen Sie uns heute über Proxys sprechen.

Warum brauchen Sie einen Proxy?

Dieses Muster hilft bei der Lösung von Problemen im Zusammenhang mit dem kontrollierten Zugriff auf ein Objekt. Sie fragen sich vielleicht: „Warum brauchen wir einen kontrollierten Zugang?“ Schauen wir uns ein paar Situationen an, die Ihnen helfen werden, herauszufinden, was was ist.

Beispiel 1

Stellen Sie sich vor, wir haben ein großes Projekt mit einer Menge altem Code, in dem es eine Klasse gibt, die für den Export von Berichten aus einer Datenbank verantwortlich ist. Die Klasse arbeitet synchron. Das heißt, das gesamte System ist im Leerlauf, während die Datenbank die Anfrage verarbeitet. Im Durchschnitt dauert die Erstellung eines Berichts 30 Minuten. Dementsprechend beginnt der Exportprozess um 00:30 Uhr und die Geschäftsleitung erhält den Bericht am Morgen. Eine Prüfung ergab, dass es besser wäre, den Bericht sofort während der normalen Geschäftszeiten zu erhalten. Die Startzeit kann nicht verschoben werden und das System kann nicht blockieren, während es auf eine Antwort von der Datenbank wartet. Die Lösung besteht darin, die Funktionsweise des Systems zu ändern und den Bericht in einem separaten Thread zu erstellen und zu exportieren. Mit dieser Lösung funktioniert das System wie gewohnt und das Management erhält aktuelle Berichte. Jedoch, Es liegt ein Problem vor: Der aktuelle Code kann nicht neu geschrieben werden, da andere Teile des Systems seine Funktionalität nutzen. In diesem Fall können wir das Proxy-Muster verwenden, um eine Zwischen-Proxy-Klasse einzuführen, die Anfragen zum Exportieren von Berichten empfängt, die Startzeit protokolliert und einen separaten Thread startet. Sobald der Bericht erstellt wurde, endet der Thread und alle sind zufrieden.

Beispiel 2

Ein Entwicklungsteam erstellt eine Veranstaltungswebsite. Um Daten zu neuen Ereignissen zu erhalten, fragt das Team einen Drittanbieterdienst ab. Eine spezielle Privatbibliothek erleichtert die Interaktion mit dem Dienst. Während der Entwicklung wird ein Problem entdeckt: Das Drittsystem aktualisiert seine Daten einmal täglich, aber jedes Mal, wenn ein Benutzer eine Seite aktualisiert, wird eine Anfrage an es gesendet. Dies führt zu einer großen Anzahl von Anfragen und der Dienst reagiert nicht mehr. Die Lösung besteht darin, die Antwort des Dienstes zwischenzuspeichern und das zwischengespeicherte Ergebnis an die Besucher zurückzugeben, wenn die Seiten neu geladen werden, wobei der Cache nach Bedarf aktualisiert wird. In diesem Fall ist das Proxy-Entwurfsmuster eine hervorragende Lösung, die die vorhandene Funktionalität nicht verändert.

Das Prinzip hinter dem Designmuster

Um dieses Muster zu implementieren, müssen Sie eine Proxy-Klasse erstellen. Es implementiert die Schnittstelle der Serviceklasse und ahmt deren Verhalten für Clientcode nach. Auf diese Weise interagiert der Client mit einem Proxy statt mit dem realen Objekt. In der Regel werden alle Anfragen an die Serviceklasse weitergeleitet, allerdings mit zusätzlichen Aktionen davor oder danach. Einfach ausgedrückt ist ein Proxy eine Schicht zwischen Client-Code und dem Zielobjekt. Betrachten Sie das Beispiel der Zwischenspeicherung von Abfrageergebnissen von einer alten und sehr langsamen Festplatte. Angenommen, wir sprechen über einen Fahrplan für elektrische Züge in einer alten App, deren Logik nicht geändert werden kann. Jeden Tag zu einer festen Uhrzeit wird eine Diskette mit einem aktualisierten Stundenplan eingelegt. Also haben wir:
  1. TrainTimetableSchnittstelle.
  2. ElectricTrainTimetable, das diese Schnittstelle implementiert.
  3. Der Clientcode interagiert über diese Klasse mit dem Dateisystem.
  4. TimetableDisplayClient-Klasse. Seine printTimetable()Methode verwendet die Methoden der ElectricTrainTimetableKlasse.
Das Diagramm ist einfach: Proxy-Entwurfsmuster: - 2Derzeit greift printTimetable()die ElectricTrainTimetableKlasse bei jedem Aufruf der Methode auf die Festplatte zu, lädt die Daten und präsentiert sie dem Client. Das System funktioniert einwandfrei, ist aber sehr langsam. Aus diesem Grund wurde die Entscheidung getroffen, die Systemleistung durch Hinzufügen eines Caching-Mechanismus zu steigern. Dies kann mithilfe des Proxy-Musters erfolgen: Proxy-Entwurfsmuster: - 3Dadurch bemerkt die Klasse nicht einmal, dass sie mit der Klasse statt mit der alten Klasse TimetableDisplayinteragiert . ElectricTrainTimetableProxyDie neue Implementierung lädt den Stundenplan einmal täglich. Bei wiederholten Anfragen gibt es das zuvor geladene Objekt aus dem Speicher zurück.

Welche Aufgaben eignen sich am besten für einen Proxy?

Hier sind einige Situationen, in denen dieses Muster definitiv nützlich sein wird:
  1. Caching
  2. Verzögerte oder verzögerte Initialisierung Warum ein Objekt sofort laden, wenn Sie es nach Bedarf laden können?
  3. Protokollierungsanfragen
  4. Zwischenverifizierung von Daten und Zugriff
  5. Arbeitsthreads werden gestartet
  6. Aufzeichnen des Zugriffs auf ein Objekt
Und es gibt auch andere Anwendungsfälle. Wenn Sie das Prinzip dieses Musters verstehen, können Sie Situationen erkennen, in denen es erfolgreich angewendet werden kann. Auf den ersten Blick macht ein Proxy dasselbe wie eine Fassade , aber das ist nicht der Fall. Ein Proxy verfügt über dieselbe Schnittstelle wie das Dienstobjekt. Verwechseln Sie dieses Muster außerdem nicht mit den Dekorator- oder Adaptermustern . Ein Decorator stellt eine erweiterte Schnittstelle bereit und ein Adapter stellt eine alternative Schnittstelle bereit.

Vorteile und Nachteile

  • + Sie können den Zugriff auf das Serviceobjekt nach Ihren Wünschen steuern
  • + Zusätzliche Fähigkeiten im Zusammenhang mit der Verwaltung des Lebenszyklus des Serviceobjekts
  • + Es funktioniert ohne Serviceobjekt
  • + Es verbessert die Leistung und Codesicherheit.
  • - Es besteht das Risiko, dass sich die Leistung aufgrund zusätzlicher Anfragen verschlechtert
  • - Es macht die Klassenhierarchie komplizierter

Das Proxy-Muster in der Praxis

Lassen Sie uns ein System implementieren, das Zugfahrpläne von einer Festplatte liest:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Hier ist die Klasse, die die Hauptschnittstelle implementiert:

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 "";
   }
}
Jedes Mal, wenn Sie den Zugfahrplan erhalten, liest das Programm eine Datei von der Festplatte. Aber das ist nur der Anfang unserer Probleme. Die gesamte Datei wird jedes Mal gelesen, wenn Sie den Fahrplan auch nur für einen einzelnen Zug erhalten! Es ist gut, dass es solchen Code nur in Beispielen dafür gibt, was man nicht tun sollte :) Client-Klasse:

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

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
Lass es uns testen:

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

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
Gehen wir nun die Schritte durch, die zur Einführung unseres Musters erforderlich sind:
  1. Definieren Sie eine Schnittstelle, die die Verwendung eines Proxys anstelle des Originalobjekts ermöglicht. In unserem Beispiel ist das TrainTimetable.

  2. Erstellen Sie die Proxy-Klasse. Es sollte einen Verweis auf das Dienstobjekt haben (in der Klasse erstellen oder an den Konstruktor übergeben).

    Hier ist unsere 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;
       }
    }
    

    In dieser Phase erstellen wir einfach eine Klasse mit einem Verweis auf das ursprüngliche Objekt und leiten alle Aufrufe an dieses weiter.

  3. Lassen Sie uns die Logik der Proxy-Klasse implementieren. Grundsätzlich werden Aufrufe immer auf das ursprüngliche Objekt umgeleitet.

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

    Der getTimetable()prüft, ob das Stundenplan-Array im Speicher zwischengespeichert wurde. Wenn nicht, sendet es eine Anfrage zum Laden der Daten von der Festplatte und speichert das Ergebnis. Wenn der Zeitplan bereits angefordert wurde, wird das Objekt schnell aus dem Speicher zurückgegeben.

    Dank der einfachen Funktionalität musste die Methode getTrainDepartureTime() nicht auf das Originalobjekt umgeleitet werden. Wir haben seine Funktionalität einfach in einer neuen Methode dupliziert.

    Tun Sie das nicht. Wenn Sie den Code duplizieren oder etwas Ähnliches tun müssen, ist etwas schiefgegangen und Sie müssen das Problem noch einmal aus einem anderen Blickwinkel betrachten. In unserem einfachen Beispiel hatten wir keine andere Möglichkeit. In realen Projekten wird der Code jedoch höchstwahrscheinlich korrekter geschrieben.

  4. Erstellen Sie im Client-Code ein Proxy-Objekt anstelle des Originalobjekts:

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

    Überprüfen

    
    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, es funktioniert einwandfrei.

    Sie könnten auch die Option einer Fabrik in Betracht ziehen, die abhängig von bestimmten Bedingungen sowohl ein Originalobjekt als auch ein Proxy-Objekt erstellt.

Bevor wir uns verabschieden, hier noch ein hilfreicher Link

Das ist alles für heute! Es wäre keine schlechte Idee, zum Unterricht zurückzukehren und Ihr neues Wissen in der Praxis auszuprobieren :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION