Proxy

2 minute read

Strukturalny wzorzec który pozwala wykorzystać placeholder lub zamiennik innego obiektu. Z pomocą Proxy można kontrolować dostęp oraz wykonywać operacje przed i po zakończeniu pracy przez orginalny obiekt.

Dobrym przykładem dla zastosowania wzorca są obiekty aplikacji używające dużej ilości zasobów systemowych, które dostarczają usługe wykorzystywaną nie przez cały czas pracy programu lecz od czasu do czasu. Nie chcemy w tym przypadku duplikować kodu, który będzie taką usługe inicjalizować na żadanie w każdym obiekcie klienckim. Idealnym rozwiązaniem byłoby zarządzanie inicjalizacją i cyklem życia bezpośrednio w obiekcie usługi ale to nie zawsze jest możliwe, na przykład gdy działamy z zamknientym kodem 3-rd party.

Z rozwiązaniem przychodzi wzorzec Proxy który sugeruje utworzenie nowej klasy z takim samym interfejsem co obiekt danej usługi. Następnie przekazujemy wszystkim klientom obiekt proxy zamiast używanego wcześniej orginalnego obiektu. Po otrzymaniu zapytania od klienta klasa proxy inicjalizuje obiekt usługi i wykorzystuje go do wykonania żądanej akcji.

Głównymi benefitami rozwiązania jest możliwość wykonania własnej logiki zarówno przed i po pracy wykonanej przez usługe bez ingerencji w jej implementacje. Ponieważ proxy wykorzystuje ten sam interface co orginał - może być przekazane do każdego klienta oczekującego prawdziwego obiektu usługi.

Wzorzec Proxy ma wiele przydatnych zastosowań, poniżej najbardziej popularne z nich:

  • Virtual proxy - implementuje opóźnienie inicjalizacji (lazy initialization) obiektu usługi. Przydatne w przypadku gdy usługa wykorzystuje dużą ilość zasobów systemowych i nie warto jej inicjalizować ze startem aplikacji. Wykorzystując proxy możemy utworzyć obiekt dopiero gdy będzie potrzebny.
  • Protection proxy - kontroluje dostęp do usługi. Czasami tylko wybrani klienci powinni mieć dostęp do obiektu usługi, możemy wtedy w klasie proxy sprawdzić różne kryteria i tylko po pozytywnej weryfikacji przekazać żądanie dalej.
  • Remote proxy - w przypadku gdy obiekt usługi znajduje się na innym serwerze. Proxy będzie odpowiedzialne za obsługe detali komunikacji między serwerami.
  • Logging proxy - wykorzystywane do zapisywania historii żądań i innych informacji związanych z używaną usługą.
  • Caching proxy - implementuje strategie cache`owania rezultatów zwróconych przez usługe dla poszczególnych zapytań.
  • Inteligentna referencja - w niektórych przypadkach chcemy zwolnić zasoby wykorzystywane przez obiekt usługi gdy ten nie jest już potrzebny. Obiekt Proxy może sprawdzić liste klientów odwołujących się do usługi i jeśli ta została wyczyszczona pozbędzie się ciężkiego obiektu usługi.

Sposób implementacji:

  1. Jeśli interface usługi nie istnieje, stwórz go aby proxy i orginalnego można było używać zamiennie. Wydzielenie takiego interfejsu nie zawsze będzie możliwe, czasami nie można zmienić wszystkich klas klientów tak by korzystały z tego interfejsu. Drugą opcją jest utworzenie proxy jako klasy dziedziczącej po klasie usługi, w ten sposób odziedziczy również jej interfejs.
  2. Stwórz klase proxy. Powinna zawierać pole które bedzie przechowywać referencje do obiektu danej usługi. Zazwyczaj klasa proxy jest odpowiedzialna za tworzenie i zarządzanie cyklem życia obiektu usługi. W żadkich przypadkach usługa jest przekazywana do proxy przez konstruktor.
  3. Zaimplementuj metody proxy zgodnie z ich przeznaczeniem. W większości przypadków, po wykonaniu jakieś pracy proxy powinno oddelegować procesowanie żądania do obiektu usługi którą obsługuje.
  4. Warto rozważyć implementacje metody odpowiedzialnej za tworzenie obiektu, która decyduje o tym czy klient otrzyma obiekt proxy czy prawdziwą usługe. Może to być zarówno prosta statyczna metoda lub pełna implementacja Factory Method.
  5. Zastanów się nad implementacją opóźnienia inicjalizacji (lazy initialization) obiektu serwisu.

Zalety i wady:

  • Możliwość kontroli orginalnego obiektu bez świadomości klientów
  • Dowolne zarządzanie cyklem życiem obiektu w przypadku gdy kod kliencki o to nie dba
  • Klasa proxy działa nawet gdy orginalny obiekt nie jest gotów lub nie jest dostępny
  • Open/Closed Principle. Kolejne klasy proxy mogą być wprowadzone bez zmiany w kodzie klienckim
  • Poziom skomplikowania kodu rośnie w związku z dodaniem nowych klas
  • Dostęp do orginalnego obiektu może zostać opóźniony

Przykładowy kod: https://github.com/rafczow/php-patterns-practice/tree/master/src/Proxy

Powiązane wzorce:

  • Adapter
  • Facade