Autumn SALE

Pełnomocnik

Znany też jako: Proxy

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.

Wzorzec projektowy Pełnomocnik

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.

Rozwiązanie problemu za pomocą wzorca Pełnomocnik

Zapytania bazodanowe mogą być bardzo powolne.

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ę.

Rozwiązanie z użyciem wzorca Pełnomocnik

Pełnomocnik udaje obiekt bazodanowy. Może zająć się leniwą inicjalizacją i przechowywaniem wyników bez wiedzy klienta i samej bazy danych.

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 służy za pełnomocnika dla zwitka banknotów

Płacić można zarówno kartą kredytową, jak i gotówką.

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

Struktura wzorca projektowego PełnomocnikStruktura wzorca projektowego Pełnomocnik
  1. Interfejs Usługi deklaruje interfejs z którym Pełnomocnik musi być zgodny, aby móc udawać obiekt usługi.

  2. Usługa to klasa udostępniająca jakąś użyteczną logikę biznesową.

  3. 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.

  4. 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.

Struktura przykładu wzorca Pełnomocnik

Przechowywanie w pamięci podręcznej danych uzyskanych od usługi za pomocą pełnomocnika.

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.

// Interfejs zdalnej usługi.
interface ThirdPartyYouTubeLib is
    method listVideos()
    method getVideoInfo(id)
    method downloadVideo(id)

// Konkretna implementacja łącza do usługi. Metody tej klasy
// mogą żądać informacji z YouTube. Szybkość realizacji żądania
// zależy od połączenia internetowego użytkownika oraz od samego
// YouTube. Aplikacja będzie działać wolniej, jeśli wiele żądań
// zostanie wysłanych jednocześnie, nawet jeśli wszystkie
// żądania dotyczą tych samych danych.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
    method listVideos() is
        // Wyślij żądanie do interfejsu programowania aplikacji
        // (API) YouTube.

    method getVideoInfo(id) is
        // Pobierz metadane pliku wideo.

    method downloadVideo(id) is
        // Pobierz plik wideo z YouTube.

// Aby zaoszczędzić nieco przepustowości, możemy stworzyć pamięć
// podręczną do przechowywania pobranych danych i przechowywać
// je jakiś czas. Jednak umieszczenie takiego kodu bezpośrednio
// w klasie usługi może być niemożliwe, jeśli na przykład
// stanowi część biblioteki od zewnętrznego dostawcy i/lub jest
// zdefiniowana jako `final`. Dlatego też umieszczamy kod
// pamięci podręcznej w odrębnej klasie pośredniczącej która
// implementuje taki sam interfejs co klasa usługi. Nowo
// powstała klasa deleguje żądania faktycznemu obiektowi usługi
// tylko wtedy gdy faktycznie trzeba przesłać żądanie przez
// sieć.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
    private field service: ThirdPartyYouTubeLib
    private field listCache, videoCache
    field needReset

    constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
        this.service = service

    method listVideos() is
        if (listCache == null || needReset)
            listCache = service.listVideos()
        return listCache

    method getVideoInfo(id) is
        if (videoCache == null || needReset)
            videoCache = service.getVideoInfo(id)
        return videoCache

    method downloadVideo(id) is
        if (!downloadExists(id) || needReset)
            service.downloadVideo(id)

// Klasa GUI która dotychczas współpracowała bezpośrednio z
// obiektem oferującym usługę pozostaje niezmieniona, o ile
// będzie współpracować z obiektem usługi poprzez interfejs.
// Możemy śmiało przekazać obiekt pośrednika zamiast obiektu
// usługi ponieważ implementują one ten sam interfejs.
class YouTubeManager is
    protected field service: ThirdPartyYouTubeLib

    constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
        this.service = service

    method renderVideoPage(id) is
        info = service.getVideoInfo(id)
        // Renderuj stronę z filmem.

    method renderListPanel() is
        list = service.listVideos()
        // Renderuj listę miniaturek plików wideo.

    method reactOnUserInput() is
        renderVideoPage()
        renderListPanel()

// Aplikacja może konfigurować pośredników w trakcie działania.
class Application is
    method init() is
        aYouTubeService = new ThirdPartyYouTubeClass()
        aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
        manager = new YouTubeManager(aYouTubeProxy)
        manager.reactOnUserInput()

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ć

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Przykłady kodu

Pełnomocnik w języku C# Pełnomocnik w języku C++ Pełnomocnik w języku Go Pełnomocnik w języku Java Pełnomocnik w języku PHP Pełnomocnik w języku Python Pełnomocnik w języku Ruby Pełnomocnik w języku Rust Pełnomocnik w języku Swift Pełnomocnik w języku TypeScript