JavaRush /Blog Java /Random-PL /Wzór projektu proxy

Wzór projektu proxy

Opublikowano w grupie Random-PL
W programowaniu ważne jest odpowiednie zaplanowanie architektury aplikacji. Niezbędnym do tego narzędziem są wzorce projektowe. Dziś porozmawiamy o Proxy, czyli inaczej Zastępcy.

Dlaczego potrzebujesz zastępcy?

Wzorzec ten pomaga rozwiązywać problemy związane z kontrolowanym dostępem do obiektu. Możesz mieć pytanie: „Po co nam taki kontrolowany dostęp?” Przyjrzyjmy się kilku sytuacjom, które pomogą Ci dowiedzieć się, co jest co.

Przykład 1

Wyobraźmy sobie, że mamy duży projekt ze stertą starego kodu, w którym znajduje się klasa odpowiedzialna za pobieranie raportów z bazy danych. Klasa działa synchronicznie, czyli cały system jest bezczynny, podczas gdy baza danych przetwarza żądanie. Raport generowany jest średnio w ciągu 30 minut. Dzięki tej funkcji przesyłanie rozpoczyna się o godzinie 00:30, a kierownictwo otrzymuje ten raport rano. W trakcie analizy okazało się, że konieczne jest otrzymanie raportu natychmiast po jego wygenerowaniu, czyli w ciągu jednego dnia. Nie ma możliwości zmiany terminu uruchomienia, gdyż system będzie oczekiwał na odpowiedź z bazy danych. Rozwiązaniem jest zmiana zasady działania poprzez rozpoczęcie przesyłania i generowania raportów w osobnym wątku. Dzięki takiemu rozwiązaniu system będzie mógł działać normalnie, a kadra zarządzająca otrzyma świeże raporty. Pojawia się jednak problem: obecnego kodu nie można przepisać, ponieważ jego funkcje są wykorzystywane przez inne części systemu. W takim przypadku można wprowadzić pośrednią klasę proxy korzystając ze wzorca Zastępca, który otrzyma żądanie przesłania raportu, zarejestrowania czasu rozpoczęcia i uruchomienia osobnego wątku. Kiedy raport zostanie wygenerowany, wątek zakończy swoją pracę i wszyscy będą zadowoleni.

Przykład 2

Zespół programistów tworzy witrynę plakatową. Aby uzyskać dane o nowych wydarzeniach, zwracają się do usługi strony trzeciej, z którą interakcja odbywa się za pośrednictwem specjalnej zamkniętej biblioteki. Podczas programowania pojawił się problem: system innej firmy aktualizuje dane raz dziennie, a żądanie ich pojawia się za każdym razem, gdy użytkownik odświeży stronę. Powoduje to utworzenie dużej liczby żądań i usługa przestaje odpowiadać. Rozwiązaniem jest buforowanie odpowiedzi usługi i udostępnianie odwiedzającym zapisanego wyniku przy każdym ponownym uruchomieniu, aktualizując tę ​​pamięć podręczną w razie potrzeby. W tym przypadku wykorzystanie wzorca Zastępca jest doskonałym rozwiązaniem bez zmiany gotowej funkcjonalności.

Jak działa wzór

Aby zaimplementować ten wzorzec, musisz utworzyć klasę proxy. Implementuje interfejs klasy usług, symulując jego zachowanie dla kodu klienta. Zatem zamiast z rzeczywistym obiektem klient wchodzi w interakcję ze swoim serwerem proxy. Zwykle wszystkie żądania są przekazywane do klasy usługi, ale z dodatkowymi akcjami przed lub po jej wywołaniu. Mówiąc najprościej, ten obiekt proxy jest warstwą pomiędzy kodem klienta a obiektem docelowym. Spójrzmy na przykład buforowania żądania z bardzo wolnego starego dysku. Niech będzie to rozkład jazdy pociągów elektrycznych w jakiejś starożytnej aplikacji, której zasady działania nie da się zmienić. Dysk z aktualnym harmonogramem wkładany jest codziennie o ustalonej godzinie. Więc mamy:
  1. Interfejs TimetableTrains.
  2. Klasa TimetableElectricTrainsimplementująca ten interfejs.
  3. To za pośrednictwem tej klasy kod klienta wchodzi w interakcję z dyskowym systemem plików.
  4. Klasa klienta DisplayTimetable. Jego metoda printTimetable()wykorzystuje metody klasowe TimetableElectricTrains.
Schemat jest prosty: Wzorzec projektowy proxy – 2obecnie przy każdym wywołaniu metody printTimetable()klasa TimetableElectricTrainsuzyskuje dostęp do dysku, wyładowuje dane i udostępnia je klientowi. Ten system działa dobrze, ale jest bardzo powolny. Dlatego zdecydowano się zwiększyć wydajność systemu poprzez dodanie mechanizmu buforującego. Można to zrobić za pomocą wzorca Proxy: Wzorzec projektowy proxy – 3w ten sposób klasa DisplayTimetablenawet nie zauważy, że wchodzi w interakcję z klasą TimetableElectricTrainsProxy, a nie z poprzednią. Nowa implementacja ładuje harmonogram raz dziennie i przy wielokrotnych żądaniach zwraca już załadowany obiekt z pamięci.

Do jakich zadań lepiej używać proxy?

Oto kilka sytuacji, w których ten wzór na pewno się przyda:
  1. Buforowanie.
  2. Leniwa implementacja jest również nazywana leniwą implementacją. Po co ładować obiekt na raz, skoro można go załadować w razie potrzeby?
  3. Rejestrowanie żądań.
  4. Tymczasowe kontrole danych i dostępu.
  5. Uruchamianie wątków przetwarzania równoległego.
  6. Nagrywanie lub zliczanie historii połączeń.
Istnieją również inne przypadki użycia. Rozumiejąc zasadę działania tego wzoru, sam możesz znaleźć dla niego udane zastosowanie. Na pierwszy rzut oka Zastępca robi to samo, co Fasada , ale tak nie jest. Serwer proxy ma ten sam interfejs co obiekt usługi. Nie należy też mylić wzoru z Dekoratorem lub Adapterem . Dekorator zapewnia rozszerzony interfejs, natomiast Adapter zapewnia alternatywny.

Zalety i wady

  • + Możesz dowolnie kontrolować dostęp do obiektu usługi;
  • + Dodatkowe możliwości zarządzania cyklem życia obiektu usługi;
  • + Działa bez obiektu usługi;
  • + Poprawia wydajność i bezpieczeństwo kodu.
  • - Istnieje ryzyko pogorszenia wyników w wyniku dodatkowych zabiegów;
  • - Komplikuje strukturę klas programu.

Wzór zastępczy w praktyce

Wdrożymy u Ciebie system czytający rozkłady jazdy pociągów z dysku:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
Klasa implementująca główny interfejs:
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 "";
   }
}
Za każdym razem, gdy próbujesz uzyskać rozkład wszystkich pociągów, program odczytuje plik z dysku. Ale to wciąż kwiaty. Plik jest również odczytywany za każdym razem, gdy trzeba uzyskać rozkład jazdy tylko dla jednego pociągu! Dobrze, że taki kod istnieje tylko w złych przykładach :) Klasa klienta:
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]);
       }
   }
}
Przykładowy plik:

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
Przetestujmy:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
Wniosek:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
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
Przejdźmy teraz przez kolejne etapy implementacji naszego wzorca:
  1. Zdefiniuj interfejs, który pozwala na użycie nowego proxy zamiast oryginalnego obiektu. W naszym przykładzie tak TimetableTrains.

  2. Utwórz klasę proxy. Musi zawierać referencję do obiektu usługi (utwórz w klasie lub przekaż w konstruktorze);

    Oto nasza klasa proxy:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный obiekt
       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;
       }
    }

    Na tym etapie po prostu tworzymy klasę z referencją do oryginalnego obiektu i przekazujemy do niej wszystkie wywołania.

  3. Implementujemy logikę klasy proxy. Zasadniczo wywołanie jest zawsze przekierowywane do oryginalnego obiektu.

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный obiekt
       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;
       }
    }

    Metoda getTimetable()sprawdza, czy tablica harmonogramu jest buforowana w pamięci. Jeśli nie, wysyła żądanie załadowania danych z dysku, zapisując wynik. Jeśli żądanie jest już uruchomione, szybko zwróci obiekt z pamięci.

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

    Nie możesz tego zrobić. Jeśli musiałeś powielić kod lub wykonać podobne manipulacje, oznacza to, że coś poszło nie tak i musisz spojrzeć na problem z innej perspektywy. W naszym prostym przykładzie nie ma innego wyjścia, ale w rzeczywistych projektach najprawdopodobniej kod zostanie napisany bardziej poprawnie.

  4. Zastąp utworzenie oryginalnego obiektu w kodzie klienta obiektem zastępczym:

    public class DisplayTimetable {
       // Измененная połączyć
       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]);
           }
       }
    }

    Badanie

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    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

    Świetnie, działa poprawnie.

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

Przydatny link zamiast kropki

  1. Świetny artykuł o wzorach i trochę o „Zastępcy”

To wszystko na dzisiaj! Miło byłoby wrócić do nauki i sprawdzić zdobytą wiedzę w praktyce :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION