JavaRush /Java-Blog /Random-DE /Proxy-Entwurfsmuster

Proxy-Entwurfsmuster

Veröffentlicht in der Gruppe Random-DE
Bei der Programmierung ist es wichtig, die Anwendungsarchitektur richtig zu planen. Ein unverzichtbares Werkzeug hierfür sind Designmuster. Heute sprechen wir über Proxy, oder anders gesagt, Deputy.

Warum brauchen Sie einen Stellvertreter?

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

Beispiel 1

Stellen wir uns vor, wir haben ein großes Projekt mit einer Menge altem Code, in dem es eine Klasse gibt, die für das Herunterladen von Berichten aus der 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 wird ein Bericht in 30 Minuten erstellt. Aufgrund dieser Funktion beginnt der Upload um 00:30 Uhr und das Management erhält diesen Bericht am Morgen. Bei der Analyse stellte sich heraus, dass es notwendig ist, den Bericht unmittelbar nach seiner Erstellung, also innerhalb eines Tages, zu erhalten. Eine Neuplanung der Startzeit ist nicht möglich, da das System auf eine Antwort aus der Datenbank wartet. Die Lösung besteht darin, das Funktionsprinzip zu ändern, indem der Upload und die Berichtserstellung in einem separaten Thread gestartet werden. Mit dieser Lösung kann das System wie gewohnt funktionieren und das Management erhält aktuelle Berichte. Es gibt jedoch ein Problem: Der aktuelle Code kann nicht neu geschrieben werden, da seine Funktionen von anderen Teilen des Systems verwendet werden. In diesem Fall können Sie mithilfe des Deputy-Musters eine Zwischen-Proxy-Klasse einführen, die eine Anfrage zum Hochladen eines Berichts erhält, die Startzeit protokolliert und einen separaten Thread startet. Wenn der Bericht erstellt ist, wird der Thread seine Arbeit abschließen und alle werden zufrieden sein.

Beispiel 2

Das Entwicklungsteam erstellt eine Poster-Website. Um Daten über neue Ereignisse zu erhalten, wenden sie sich an einen Drittanbieterdienst, dessen Interaktion über eine spezielle geschlossene Bibliothek erfolgt. Während der Entwicklung trat ein Problem auf: Ein Drittsystem aktualisiert die Daten einmal täglich und jedes Mal, wenn der Benutzer die Seite aktualisiert, erfolgt eine Anfrage daran. Dies führt zu einer großen Anzahl von Anfragen und der Dienst reagiert nicht mehr. Die Lösung besteht darin, die Serviceantwort zwischenzuspeichern und den Besuchern bei jedem Neustart das gespeicherte Ergebnis bereitzustellen und diesen Cache bei Bedarf zu aktualisieren. In diesem Fall ist die Verwendung des Deputy-Musters eine hervorragende Lösung, ohne die fertige Funktionalität zu verändern.

So funktioniert das Muster

Um dieses Muster zu implementieren, müssen Sie eine Proxy-Klasse erstellen. Es implementiert eine Serviceklassenschnittstelle und simuliert deren Verhalten für Clientcode. Anstelle des realen Objekts interagiert der Client also mit seinem Proxy. Normalerweise werden alle Anfragen an die Serviceklasse weitergeleitet, jedoch mit zusätzlichen Aktionen vor oder nach ihrem Aufruf. Einfach ausgedrückt ist dieses Proxy-Objekt eine Schicht zwischen dem Client-Code und dem Zielobjekt. Schauen wir uns ein Beispiel für das Zwischenspeichern einer Anfrage von einer sehr langsamen alten Festplatte an. Sei es ein elektrischer Zugfahrplan in einer alten Anwendung, dessen Funktionsprinzip nicht geändert werden kann. Die Diskette mit dem aktualisierten Zeitplan wird jeden Tag zu einer festgelegten Zeit eingelegt. Also haben wir:
  1. Schnittstelle TimetableTrains.
  2. Die Klasse TimetableElectricTrains, die diese Schnittstelle implementiert.
  3. Über diese Klasse interagiert der Clientcode mit dem Festplattendateisystem.
  4. Client-Klasse DisplayTimetable. Seine Methode printTimetable()verwendet Klassenmethoden TimetableElectricTrains.
Das Schema ist einfach: Derzeit greift die Klasse Proxy-Entwurfsmuster – 2bei jedem Aufruf einer Methode auf die Festplatte zu, entlädt Daten und stellt sie dem Client zur Verfügung. Dieses System funktioniert gut, ist aber sehr langsam. Daher wurde beschlossen, die Systemleistung durch Hinzufügen eines Caching-Mechanismus zu steigern. Dies kann mithilfe des Proxy-Musters erfolgen: Auf diese Weise merkt die Klasse nicht einmal, dass sie mit der Klasse und nicht mit der vorherigen interagiert . Die neue Implementierung lädt den Zeitplan einmal täglich und gibt bei wiederholten Anfragen das bereits geladene Objekt aus dem Speicher zurück. printTimetable()TimetableElectricTrainsProxy-Entwurfsmuster – 3DisplayTimetableTimetableElectricTrainsProxy

Für welche Aufgaben ist es besser, Proxy zu verwenden?

Hier sind einige Situationen, in denen dieses Muster auf jeden Fall nützlich sein wird:
  1. Caching.
  2. Die verzögerte Implementierung wird auch als verzögerte Implementierung bezeichnet. Warum ein Objekt auf einmal laden, wenn man es nach Bedarf laden kann?
  3. Protokollierungsanfragen.
  4. Zwischendaten- und Zugriffskontrollen.
  5. Parallelverarbeitungsthreads starten.
  6. Aufzeichnen oder Zählen des Anrufverlaufs.
Es gibt auch andere Anwendungsfälle. Wenn Sie das Funktionsprinzip dieses Musters verstehen, können Sie selbst eine erfolgreiche Anwendung dafür finden. Auf den ersten Blick macht Deputy dasselbe wie Facade , ist es aber nicht. Der Proxy verfügt über dieselbe Schnittstelle wie das Serviceobjekt. Verwechseln Sie das Muster auch nicht mit Decorator oder Adapter . Der Decorator bietet eine erweiterte Schnittstelle, während der Adapter eine Alternative bietet.

Vorteile und Nachteile

  • + Sie können den Zugriff auf das Serviceobjekt nach Ihren Wünschen steuern;
  • + Zusätzliche Funktionen zur Verwaltung des Lebenszyklus eines Serviceobjekts;
  • + Funktioniert ohne Serviceobjekt;
  • + Verbessert die Codeleistung und -sicherheit.
  • - Es besteht die Gefahr einer Leistungsverschlechterung durch zusätzliche Behandlungen;
  • - Kompliziert die Struktur von Programmklassen.

Ersatzmuster in der Praxis

Lassen Sie uns mit Ihnen ein System implementieren, das Zugfahrpläne von der Festplatte liest:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Eine Klasse, die die Hauptschnittstelle implementiert:
public class TimetableElectricTrains implements TimetableTrains {

   @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 versuchen, den Fahrplan aller Züge abzurufen, liest das Programm die Datei von der Festplatte. Aber das sind immer noch Blumen. Die Datei wird auch jedes Mal gelesen, wenn Sie den Fahrplan für nur einen Zug benötigen! Es ist gut, dass es solchen Code nur in schlechten Beispielen gibt :) Client-Klasse:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       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;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
Lass uns testen:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Abschluss:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
Gehen wir nun die Schritte zur Implementierung unseres Musters durch:
  1. Definieren Sie eine Schnittstelle, die es Ihnen ermöglicht, einen neuen Proxy anstelle des ursprünglichen Objekts zu verwenden. In unserem Beispiel ist das TimetableTrains.

  2. Erstellen Sie eine Proxy-Klasse. Es muss einen Verweis auf ein Dienstobjekt enthalten (in einer Klasse erstellen oder in einem Konstruktor übergeben);

    Hier ist unsere Proxy-Klasse:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный ein Objekt
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    In diesem Stadium erstellen wir einfach eine Klasse mit einem Verweis auf das ursprüngliche Objekt und übergeben alle Aufrufe an dieses.

  3. Wir implementieren die Logik der Proxy-Klasse. Grundsätzlich wird der Aufruf immer auf das ursprüngliche Objekt umgeleitet.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный ein Objekt
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

    Die Methode getTimetable()prüft, ob das Zeitplan-Array im Speicher zwischengespeichert ist. Wenn nicht, wird eine Anforderung ausgegeben, die Daten von der Festplatte zu laden und das Ergebnis zu speichern. Wenn die Anfrage bereits ausgeführt wird, wird schnell ein Objekt aus dem Speicher zurückgegeben.

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

    Das kannst du nicht machen. Wenn Sie Code duplizieren oder ähnliche Manipulationen durchführen mussten, bedeutet das, dass etwas schief gelaufen ist und Sie das Problem aus einem anderen Blickwinkel betrachten müssen. In unserem einfachen Beispiel gibt es keine andere Möglichkeit, aber in realen Projekten wird der Code höchstwahrscheinlich korrekter geschrieben.

  4. Ersetzen Sie die Erstellung des Originalobjekts im Clientcode durch ein Ersatzobjekt:

    public class DisplayTimetable {
       // Измененная Verknüpfung
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           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]);
           }
       }
    }

    Untersuchung

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    Super, es funktioniert einwandfrei.

    Sie können auch eine Fabrik in Betracht ziehen, die abhängig von bestimmten Bedingungen sowohl das Originalobjekt als auch ein Ersatzobjekt erstellt.

Nützlicher Link statt eines Punktes

  1. Ausgezeichneter Artikel über Muster und ein wenig über „Deputy“

Das ist alles für heute! Es wäre schön, wieder mit dem Lernen zu beginnen und Ihr neues Wissen in der Praxis zu testen :)
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION