
Pełnomocnik w języku PHP
Pełnomocnik to strukturalny wzorzec projektowy według którego obiekt-usługodawca używany przez klienta jest zastępowany przez obiekt zastępczy, zwany pełnomocnikiem. Pełnomocnik przechwytuje żądania od klienta, wykonuje jakąś pracę (kontrola dostępu, zarządzanie pamięcią podręczną, itp.) a następnie przekazuje żądanie usługodawcy.
Obiekt będący pełnomocnikiem ma ten sam interfejs co usługodawca, co czyni go wymienialnym z obiektem usługodawcy dotychczas przekazywanym klientowi.
Złożoność:
Popularność:
Przykłady użycia: Pełnomocnik nie jest częstym gościem w aplikacjach napisanych w PHP, ale w niektórych wyjątkowych sytuacjach bardzo się przydaje. Jest niezastąpiony wszędzie tam, gdzie trzeba dodać jakąś funkcjonalność obiektowi istniejącej klasy bez zmiany kodu klienta.
Identyfikacja: Pełnomocnicy delegują całą faktyczną pracę innemu obiektowi. Każda metoda pełnomocnika powinna odnosić się do obiektu-usługodawcy, chyba, że pełnomocnik jest klasą pochodną usługi.
Przykład koncepcyjny
Poniższy przykład ilustruje strukturę wzorca Pełnomocnik ze szczególnym naciskiem na następujące kwestie:
- Z jakich składa się klas?
- Jakie role pełnią te klasy?
- W jaki sposób elementy wzorca są ze sobą powiązane?
Poznawszy strukturę wzorca będzie ci łatwiej zrozumieć następujący przykład, oparty na prawdziwym przypadku użycia PHP.
index.php: Przykład koncepcyjny
<?php namespace RefactoringGuru\Proxy\Conceptual; /** * The Subject interface declares common operations for both RealSubject and the * Proxy. As long as the client works with RealSubject using this interface, * you'll be able to pass it a proxy instead of a real subject. */ interface Subject { public function request(): void; } /** * The RealSubject contains some core business logic. Usually, RealSubjects are * capable of doing some useful work which may also be very slow or sensitive - * e.g. correcting input data. A Proxy can solve these issues without any * changes to the RealSubject's code. */ class RealSubject implements Subject { public function request(): void { echo "RealSubject: Handling request.\n"; } } /** * The Proxy has an interface identical to the RealSubject. */ class Proxy implements Subject { /** * @var RealSubject */ private $realSubject; /** * The Proxy maintains a reference to an object of the RealSubject class. It * can be either lazy-loaded or passed to the Proxy by the client. */ public function __construct(RealSubject $realSubject) { $this->realSubject = $realSubject; } /** * The most common applications of the Proxy pattern are lazy loading, * caching, controlling the access, logging, etc. A Proxy can perform one of * these things and then, depending on the result, pass the execution to the * same method in a linked RealSubject object. */ public function request(): void { if ($this->checkAccess()) { $this->realSubject->request(); $this->logAccess(); } } private function checkAccess(): bool { // Some real checks should go here. echo "Proxy: Checking access prior to firing a real request.\n"; return true; } private function logAccess(): void { echo "Proxy: Logging the time of request.\n"; } } /** * The client code is supposed to work with all objects (both subjects and * proxies) via the Subject interface in order to support both real subjects and * proxies. In real life, however, clients mostly work with their real subjects * directly. In this case, to implement the pattern more easily, you can extend * your proxy from the real subject's class. */ function clientCode(Subject $subject) { // ... $subject->request(); // ... } echo "Client: Executing the client code with a real subject:\n"; $realSubject = new RealSubject(); clientCode($realSubject); echo "\n"; echo "Client: Executing the same client code with a proxy:\n"; $proxy = new Proxy($realSubject); clientCode($proxy);
Output.txt: Wynik działania
Client: Executing the client code with a real subject: RealSubject: Handling request. Client: Executing the same client code with a proxy: Proxy: Checking access prior to firing a real request. RealSubject: Handling request. Proxy: Logging the time of request.
Przykład z prawdziwego życia
Pełnomocnik może pełnić niezliczoną ilość ról: pamięć podręczna, prowadzenie dziennika, kontrola dostępu, opóźniona inicjalizacja, itd. Poniższy przykład pokazuje jak wzorzec Pełnomocnik poprawia wydajność obiektu pobierającego dane z sieci poprzez dodanie pamięci podręcznej.
index.php: Przykład z prawdziwego życia
<?php namespace RefactoringGuru\Proxy\RealWorld; /** * The Subject interface describes the interface of a real object. * * The truth is that many real apps may not have this interface clearly defined. * If you're in that boat, your best bet would be to extend the Proxy from one * of your existing application classes. If that's awkward, then extracting a * proper interface should be your first step. */ interface Downloader { public function download(string $url): string; } /** * The Real Subject does the real job, albeit not in the most efficient way. * When a client tries to download the same file for the second time, our * downloader does just that, instead of fetching the result from cache. */ class SimpleDownloader implements Downloader { public function download(string $url): string { echo "Downloading a file from the Internet.\n"; $result = file_get_contents($url); echo "Downloaded bytes: " . strlen($result) . "\n"; return $result; } } /** * The Proxy class is our attempt to make the download more efficient. It wraps * the real downloader object and delegates it the first download calls. The * result is then cached, making subsequent calls return an existing file * instead of downloading it again. * * Note that the Proxy MUST implement the same interface as the Real Subject. */ class CachingDownloader implements Downloader { /** * @var SimpleDownloader */ private $downloader; /** * @var string[] */ private $cache = []; public function __construct(SimpleDownloader $downloader) { $this->downloader = $downloader; } public function download(string $url): string { if (!isset($this->cache[$url])) { echo "CacheProxy MISS. "; $result = $this->downloader->download($url); $this->cache[$url] = $result; } else { echo "CacheProxy HIT. Retrieving result from cache.\n"; } return $this->cache[$url]; } } /** * The client code may issue several similar download requests. In this case, * the caching proxy saves time and traffic by serving results from cache. * * The client is unaware that it works with a proxy because it works with * downloaders via the abstract interface. */ function clientCode(Downloader $subject) { // ... $result = $subject->download("http://example.com/"); // Duplicate download requests could be cached for a speed gain. $result = $subject->download("http://example.com/"); // ... } echo "Executing client code with real subject:\n"; $realSubject = new SimpleDownloader(); clientCode($realSubject); echo "\n"; echo "Executing the same client code with a proxy:\n"; $proxy = new CachingDownloader($realSubject); clientCode($proxy);
Output.txt: Wynik działania
Executing client code with real subject: Downloading a file from the Internet. Downloaded bytes: 1270 Downloading a file from the Internet. Downloaded bytes: 1270 Executing the same client code with a proxy: CacheProxy MISS. Downloading a file from the Internet. Downloaded bytes: 1270 CacheProxy HIT. Retrieving result from cache.