CodeGym /Blog Java /Random-PL /Wzorzec projektowy proxy
Autor
Artem Divertitto
Senior Android Developer at United Tech

Wzorzec projektowy proxy

Opublikowano w grupie Random-PL
W programowaniu ważne jest prawidłowe zaplanowanie architektury aplikacji. Wzorce projektowe są niezbędnym sposobem osiągnięcia tego celu. Dzisiaj porozmawiajmy o proxy.

Dlaczego potrzebujesz proxy?

Wzorzec ten pomaga rozwiązywać problemy związane z kontrolowanym dostępem do obiektu. Możesz zapytać: „Dlaczego potrzebujemy kontrolowanego dostępu?” Przyjrzyjmy się kilku sytuacjom, które pomogą ci zrozumieć, co jest czym.

Przykład 1

Wyobraźmy sobie, że mamy duży projekt z kupą starego kodu, w którym znajduje się klasa odpowiedzialna za eksport raportów z bazy danych. Klasa działa synchronicznie. Oznacza to, że cały system jest bezczynny, podczas gdy baza danych przetwarza żądanie. Wygenerowanie raportu zajmuje średnio 30 minut. W związku z tym proces eksportu rozpoczyna się o godzinie 00:30, a kierownictwo otrzymuje raport rano. Audyt wykazał, że lepiej byłoby mieć możliwość natychmiastowego otrzymania raportu w normalnych godzinach pracy. Czas rozpoczęcia nie może zostać przesunięty, a system nie może blokować czasu oczekiwania na odpowiedź z bazy danych. Rozwiązaniem jest zmiana sposobu działania systemu, generowanie i eksportowanie raportu w osobnym wątku. To rozwiązanie sprawi, że system będzie działał normalnie, a kierownictwo będzie otrzymywać świeże raporty. Jednakże, jest problem: bieżącego kodu nie można przepisać, ponieważ inne części systemu korzystają z jego funkcjonalności. W takim przypadku możemy użyć wzorca proxy, aby wprowadzić pośrednią klasę proxy, która będzie otrzymywać żądania eksportu raportów, rejestrować czas rozpoczęcia i uruchamiać osobny wątek. Po wygenerowaniu raportu wątek kończy się i wszyscy są zadowoleni.

Przykład 2

Zespół programistów tworzy witrynę internetową wydarzeń. Aby uzyskać dane o nowych zdarzeniach, zespół wysyła zapytanie do usługi innej firmy. Specjalna prywatna biblioteka ułatwia interakcję z serwisem. Podczas opracowywania wykryto problem: system innej firmy aktualizuje swoje dane raz dziennie, ale za każdym razem, gdy użytkownik odświeża stronę, wysyłane jest do niego żądanie. Powoduje to powstanie dużej liczby żądań, a usługa przestaje odpowiadać. Rozwiązaniem jest buforowanie odpowiedzi usługi i zwracanie buforowanych wyników odwiedzającym, gdy strony są przeładowywane, aktualizując pamięć podręczną w razie potrzeby. W tym przypadku wzorzec projektowy proxy jest doskonałym rozwiązaniem, które nie zmienia dotychczasowej funkcjonalności.

Zasada stojąca za wzorcem projektowym

Aby zaimplementować ten wzorzec, musisz utworzyć klasę proxy. Implementuje interfejs klasy usługi, naśladując jej zachowanie dla kodu klienta. W ten sposób klient wchodzi w interakcję z serwerem proxy zamiast z rzeczywistym obiektem. Z reguły wszystkie żądania są przekazywane do klasy usług, ale z dodatkowymi akcjami przed lub po. Mówiąc najprościej, proxy to warstwa między kodem klienta a obiektem docelowym. Rozważmy przykład buforowania wyników zapytania ze starego i bardzo wolnego dysku twardego. Załóżmy, że mówimy o rozkładzie jazdy pociągów elektrycznych w jakiejś starożytnej aplikacji, której logiki nie można zmienić. Dysk z aktualizowanym harmonogramem jest wkładany codziennie o ustalonej godzinie. Więc mamy:
  1. TrainTimetableinterfejs.
  2. ElectricTrainTimetable, który implementuje ten interfejs.
  3. Kod klienta współdziała z systemem plików za pośrednictwem tej klasy.
  4. TimetableDisplayklasa klienta. Jego printTimetable()metoda wykorzystuje metody klasy ElectricTrainTimetable.
Diagram jest prosty: Proxy wzór projektowy: - 2obecnie przy każdym wywołaniu metody printTimetable()klasa ElectricTrainTimetableuzyskuje dostęp do dysku, ładuje dane i prezentuje je klientowi. System działa dobrze, ale jest bardzo powolny. W rezultacie podjęto decyzję o zwiększeniu wydajności systemu poprzez dodanie mechanizmu buforowania. Można to zrobić za pomocą wzorca proxy: Proxy wzorzec projektowy: - 3w ten sposób TimetableDisplayklasa nawet nie zauważa, że ​​wchodzi w interakcje z ElectricTrainTimetableProxyklasą zamiast ze starą klasą. Nowa implementacja wczytuje plan lekcji raz dziennie. W przypadku powtarzających się żądań zwraca poprzednio załadowany obiekt z pamięci.

Jakie zadania są najlepsze dla proxy?

Oto kilka sytuacji, w których ten wzór na pewno się przyda:
  1. Buforowanie
  2. Opóźniona lub leniwa inicjalizacja Po co ładować obiekt od razu, jeśli można go załadować w razie potrzeby?
  3. Żądania rejestrowania
  4. Pośrednia weryfikacja danych i dostępu
  5. Uruchamianie wątków roboczych
  6. Rejestrowanie dostępu do obiektu
Są też inne przypadki użycia. Rozumiejąc zasadę stojącą za tym wzorcem, możesz zidentyfikować sytuacje, w których można go z powodzeniem zastosować. Na pierwszy rzut oka proxy robi to samo co fasada , ale tak nie jest. Serwer proxy ma taki sam interfejs jak obiekt usługi. Nie należy również mylić tego wzorca z wzorami dekoratora lub adaptera . Dekorator zapewnia rozszerzony interfejs, a adapter zapewnia alternatywny interfejs .

Zalety i wady

  • + Możesz kontrolować dostęp do obiektu usługi w dowolny sposób
  • + Dodatkowe umiejętności związane z zarządzaniem cyklem życia obiektu usługi
  • + Działa bez obiektu usługi
  • + Poprawia wydajność i bezpieczeństwo kodu.
  • - Istnieje ryzyko, że wydajność może się pogorszyć z powodu dodatkowych żądań
  • - To komplikuje hierarchię klas

Wzorzec proxy w praktyce

Zaimplementujmy system, który odczytuje rozkłady jazdy pociągów z dysku twardego:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Oto klasa, która implementuje główny interfejs:

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 "";
   }
}
Za każdym razem, gdy otrzymujesz rozkład jazdy pociągów, program odczytuje plik z dysku. Ale to dopiero początek naszych kłopotów. Cały plik jest odczytywany za każdym razem, gdy otrzymujesz rozkład jazdy nawet jednego pociągu! Dobrze, że taki kod istnieje tylko w przykładach czego nie robić :) Klasa klienta:

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]);
       }
   }
}
Przykładowy plik:

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
przetestujmy to:

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

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
Przejdźmy teraz przez kroki wymagane do wprowadzenia naszego wzorca:
  1. Zdefiniuj interfejs, który pozwala na użycie proxy zamiast oryginalnego obiektu. W naszym przykładzie jest to TrainTimetable.

  2. Utwórz klasę proxy. Powinien mieć odniesienie do obiektu usługi (utwórz go w klasie lub przekaż do konstruktora).

    Oto nasza klasa proxy:

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

    Na tym etapie po prostu tworzymy klasę z odniesieniem do oryginalnego obiektu i przekazujemy do niego wszystkie wywołania.

  3. Zaimplementujmy logikę klasy proxy. Zasadniczo wywołania są zawsze przekierowywane do oryginalnego obiektu.

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

    Sprawdza getTimetable(), czy tablica harmonogramu została zbuforowana w pamięci. Jeśli nie, wysyła żądanie załadowania danych z dysku i zapisuje wynik. Jeśli plan lekcji został już wywołany, szybko zwraca obiekt z pamięci.

    Dzięki swojej prostej funkcjonalności metoda getTrainDepartureTime() nie musiała być przekierowywana do oryginalnego obiektu. Po prostu zduplikowaliśmy jego funkcjonalność w nowej metodzie.

    Nie rób tego. Jeśli musisz zduplikować kod lub zrobić coś podobnego, coś poszło nie tak i musisz ponownie spojrzeć na problem z innej perspektywy. W naszym prostym przykładzie nie mieliśmy innego wyjścia. Ale w prawdziwych projektach kod najprawdopodobniej zostanie napisany bardziej poprawnie.

  4. W kodzie klienta utwórz obiekt proxy zamiast oryginalnego obiektu:

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

    Sprawdzać

    
    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
    

    Świetnie, działa poprawnie.

    Można również rozważyć opcję fabryki, która tworzy zarówno oryginalny obiekt, jak i obiekt zastępczy, w zależności od określonych warunków.

Zanim się pożegnamy, oto pomocny link

To wszystko na dzisiaj! Nie byłoby złym pomysłem wrócić na lekcje i wypróbować nową wiedzę w praktyce :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION