Pełnomocnik
Cel
Pełnomocnik to strukturalny wzorzec projektowy pozwalający stworzyć obiekt zastępczy w miejsce innego obiektu. Pełnomocnik nadzoruje dostęp do pierwotnego obiektu, pozwalając na wykonanie jakiejś czynności przed lub po przekazaniu do niego żądania.
Problem
Po co właściwie kontrolować dostęp do obiektu? Załóżmy, że mamy duży obiekt, który zużywa dużo zasobów. Potrzebujesz go co jakiś czas, ale nie ciągle.
Moglibyśmy zastosować leniwą inicjalizację: tworzyć ten obiekt tylko gdy staje się faktycznie potrzebny. Wszystkie jego klienty musiałyby wykonać jakiś opóźniony kod inicjalizujący. Niestety doprowadziłoby to do powielania kodu.
W idealnym świecie, chcielibyśmy umieścić ten kod w klasie obiektu, ale nie zawsze jest to możliwe. Klasa może być bowiem częścią zamkniętej biblioteki innego dostawcy.
Rozwiązanie
Wzorzec Pełnomocnik zakłada stworzenie nowej klasy pośredniczącej, o takim samym interfejsie co pierwotny obiekt udostępniający usługę. Następnie aktualizujemy nasz program tak, aby przekazywał obiekt pełnomocnika wszystkim klientom pierwotnego obiektu. Otrzymawszy żądanie od klienta, pełnomocnik tworzy prawdziwy obiekt usługi i deleguje mu całą pracę.
Ale co z tego mamy? Jeśli musisz uruchomić coś albo przed, albo po głównej logice klasy, pełnomocnik pozwala uczynić to bez modyfikowania tej klasy. Ponieważ pełnomocnik implementuje ten sam interfejs, co pierwotna klasa, może być przekazany do dowolnego klienta wymagającego prawdziwego obiektu udostępniającego usługę.
Analogia do prawdziwego życia
Karta kredytowa jest pełnomocnikiem konta bankowego, które z kolei jest pełnomocnikiem gotówki. Oba implementują taki sam interfejs: można za ich pomocą płacić. Klient jest zadowolony, gdyż nie musi nosić przy sobie gotówki. Sklepikarz również jest zadowolony, bo przychody z transakcji są elektronicznie dodawane do konta bankowego sklepu bez ryzyka zagubienia czy kradzieży utargu.
Struktura
-
Interfejs Usługi deklaruje interfejs z którym Pełnomocnik musi być zgodny, aby móc udawać obiekt usługi.
-
Usługa to klasa udostępniająca jakąś użyteczną logikę biznesową.
-
Klasa Pełnomocnik zawiera pole z odniesieniem do konkretnego obiektu udostępniającego usługę. Po wykonaniu swoich zadań (leniwa inicjalizacja, zapis w dzienniku, kontrola dostępu, przechowanie w pamięci podręcznej, itp.) Pełnomocnik przekazuje żądanie do tego obiektu.
Pośrednicy zazwyczaj zarządzają całym cyklem życia swych obiektów usługowych.
-
Klient powinien móc pracować zarówno z usługami, jak i pełnomocnikami za pośrednictwem takiego samego interfejsu. Dzięki temu można umieścić pełnomocnika w dowolnym kodzie, który ma współdziałać z obiektem usługowym.
Pseudokod
Poniższy przykład ilustruje jak wzorzec Pełnomocnik może pomóc dodać obsługę leniwej inicjalizacji i pamięci podręcznej do biblioteki innego producenta, odpowiedzialnej za integrację z YouTube.
Biblioteka udostępnia klasę do pobierania filmów wideo. Jest jednak bardzo nieefektywna. W przypadku otrzymania żądania pobrania drugi raz tego samego filmu, biblioteka pobierze go od nowa, zamiast buforować dane pozyskane podczas poprzedniego pobrania.
Klasa pośrednicząca implementuje ten sam interfejs, co pierwotne narzędzie do pobierania i deleguje mu całą pracę. Zapamiętuje jednak pobrane pliki i zwraca dane z pamięci podręcznej, jeśli otrzyma żądanie pobrania tego samego filmu kolejny raz.
Zastosowanie
Wzorzec Pełnomocnik może przydać się w wielu przypadkach. Przejrzyjmy najpopularniejsze przypadki użycia tej koncepcji.
Leniwa inicjalizacja (wirtualny pełnomocnik). Gdy masz do czynienia z zasobożernym obiektem usługi, którego potrzebujesz jedynie co jakiś czas.
Zamiast tworzyć obiekt podczas uruchamiania aplikacji, możesz opóźnić inicjalizację obiektu do momentu gdy faktycznie staje się potrzebny.
Kontrola dostępu (pełnomocnik ochronny). Przydatne, gdy chcesz pozwolić tylko niektórym klientom na korzystanie z obiektu usługi. Na przykład, gdy usługi stanowią kluczową część systemu operacyjnego, a klienci to różne uruchamiane aplikacje (również te szkodliwe).
Pełnomocnik może przekazać żądanie obiektowi usługi tylko wtedy, gdy klient przedstawi odpowiednie poświadczenia.
Lokalne uruchamianie zdalnej usługi (pełnomocnik zdalny). Użyteczne, gdy obiekt udostępniający usługę znajduje się na zdalnym serwerze.
W takim przypadku, pełnomocnik przekazuje żądania klienta przez sieć, biorąc na siebie kłopotliwe szczegóły przesyłu.
Prowadzenie dziennika żądań (pełnomocnik prowadzący dziennik). Pozwala prowadzić rejestr żądań przesyłanych do obiektu usługi.
Pełnomocnik może zapisywać do dziennika każde żądanie przed przekazaniem go usłudze.
Przechowywanie w pamięci podręcznej wyników działań (pełnomocnik z pamięcią podręczną). Pozwala przechować wyniki przekazywanych żądań i zarządzać cyklem życia pamięci podręcznej. Szczególnie ważne przy dużych wielkościach danych wynikowych.
Pełnomocnik może implementować pamięć podręczną często powtarzających się żądań dających ten sam wynik. Można wykorzystać parametry żądania w charakterze kluczy identyfikujących odpowiedni obszar pamięci podręcznej.
Sprytne referencje. Można likwidować zasobożerny obiekt, gdy nie ma klientów którzy go potrzebują.
Pełnomocnik może pozwolić na śledzenie klientów którzy otrzymali referencję do obiektu usługi lub wyników jego pracy. Co jakiś czas pełnomocnik może przejrzeć listę klientów, sprawdzając czy wciąż są aktywni. Jeśli lista klientów okazuje się pusta, pełnomocnik może zlikwidować obiekt usługi i tym samym zwolnić zasoby systemowe.
Pełnomocnik może też pamiętać, że klient zmodyfikował obiekt-usługę. Dzięki temu niezmienione obiekty mogą być ponownie wykorzystane przez innych klientów.
Jak zaimplementować
-
Jeśli nie ma istniejącego interfejsu usługi, stwórz go, aby uczynić pełnomocnika i obiekt usługi wymiennymi. Ekstrakcja interfejsu z klasy usługi nie zawsze jest możliwa, ponieważ trzeba by było zmienić wszystkich klientów usługi tak, by komunikowali się przez ten interfejs. Plan B to stworzenie pełnomocnika w formie podklasy klasy usługi. Dzięki temu pełnomocnik odziedziczy interfejs usługi.
-
Stwórz klasę pełnomocnika. Powinna posiadać pole służące do przechowywania odniesienia do usługi. Zazwyczaj pośrednicy zarządzają całym cyklem życia usługodawców, włącznie z tworzeniem ich obiektów. W pewnych przypadkach obiekt usługi jest przekazywany przez klienta pełnomocnikowi za pośrednictwem konstruktora.
-
Zaimplementuj metody pełnomocnika zgodnie z ich przeznaczeniem. W większości przypadków, po wykonaniu jakiejś części pracy, pełnomocnik powinien oddelegować jej resztę obiektowi usługi.
-
Rozważ utworzenie metody kreacyjnej która decyduje o tym, czy klient otrzyma obiekt pełnomocnika, czy faktyczny obiekt usługi. Może to być prosta, statyczna metoda w klasie pełnomocnika, albo w pełni rozwinięta metoda wytwórcza.
-
Przemyśl implementację leniwej inicjalizacji obiektu usługi.
Zalety i wady
- Można sterować obiektem usługi bez wiedzy klientów.
- Można zarządzać cyklem życia obiektu usługi, gdy klientów to nie interesuje.
- Pełnomocnik działa nawet wtedy, gdy obiekt udostępniający usługę nie jest jeszcze gotowy lub dostępny.
- Zasada otwarte/zamknięte. Można wprowadzać nowych pełnomocników do aplikacji bez modyfikowania usług lub klientów.
- Kod może ulec skomplikowaniu, ponieważ trzeba wprowadzić wiele nowych klas.
- Odpowiedzi ze strony usługi mogą ulec opóźnieniu.
Powiązania z innymi wzorcami
-
Za pomocą Adapter można uzyskać dostęp do istniejącego obiektu za pośrednictwem innego interfejsu. W przypadku Pełnomocnik interfejs pozostaje taki sam. Za pomocą Dekorator uzyskuje się dostęp do obiektu za pośrednictwem ulepszonego interfejsu.
-
Fasada przypomina wzorzec Pełnomocnik w tym sensie, że oba buforują złożony podmiot i inicjalizują go samodzielnie. W przeciwieństwie do Fasady, Pełnomocnik ma taki sam interfejs jak obiekt udostępniający usługę który ją reprezentuje, co czyni je wymienialnymi.
-
Dekorator i Pełnomocnik mają podobne struktury, ale inne cele. Oba wzorce bazują na zasadzie kompozycji — jeden obiekt deleguje część zadań innemu. Pełnomocnik dodatkowo zarządza cyklem życia obiektu udostępniającego jakąś usługę, zaś komponowanie Dekoratorów leży w gestii klienta.