PHP: Заступник

Proxy Заступник Proxy

Заступник — це об'єкт, який виступає прошарком між клієнтом та реальним сервісним об'єктом. Заступник отримує виклики від клієнта, виконує свою функцію (контроль доступу, кешування, зміна запиту та інше), а потім передає виклик сервісному об'єктові.

Заступник має той самий інтерфейс, що і реальний об'єкт, тому для клієнта немає різниці — працювати з реальним об'єктом безпосередньо, чи за допомогою заступника.

Детальніше про Заступника

Особливості паттерна в PHP

Складність:

Популярність:

Застосування: Патерн Заступник застосовується в коді на PHP тоді, коли потрібно замінити дійсний об'єкт його сурогатом, причому, непомітно для клієнтів цього об'єкта. Це дозволить виконати додаткові поведінки до або після основної поведінки дійсного об'єкта.

Приклад: Структура патерну

Цей приклад показує структуру патерну Заступник, а саме — з яких класів він складається, які ролі ці класи виконують і як вони взаємодіють один з одним. Після ознайомлення зі структурою, вам буде легше сприймати наступний приклад, що розглядає реальний випадок використання патерну в світі PHP.

ProxyStructural.php: Приклад структури патерну

<?php

namespace RefactoringGuru\Proxy\Structural;

/**
 * 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();
}

/**
 * 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()
    {
        print("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()
    {
        if ($this->checkAccess()) {
            $this->realSubject->request();
            $this->logAccess();
        }
    }

    private function checkAccess()
    {
        // Some real checks should go here. RU: Некоторые реальные проверки
        // должны проходить здесь.
        print("Proxy: Checking access prior to firing a real request.\n");

        return true;
    }

    private function logAccess()
    {
        print("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)
{
    // ...

    print($subject->request());

    // ...
}

print("Client: Executing the client code with a real subject:\n");
$realSubject = new RealSubject();
clientCode($realSubject);

print("\n");

print("Client: Executing the same client code with a proxy:\n");
$proxy = new Proxy($realSubject);
clientCode($proxy);

Output.txt: Результат виконання

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.

Приклад: Приклад з життя

ProxyRealWorld.php: Приклад з життя

<?php

namespace RefactoringGuru\Proxy\RealWorld;

/**
 * Proxy Design Pattern
 *
 * Intent: Provide a surrogate or placeholder for another object to control
 * access to the original object or to add other responsibilities.
 *
 * Example: There are countless ways proxies can be used: caching, logging,
 * access control, delayed initialization, etc. This example demonstrates how
 * the Proxy pattern can improve the performance of a downloader object by
 * caching its results.
 */

/**
 * 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
    {
        print("Downloading a file from the Internet.\n");
        $result = file_get_contents($url);
        print("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])) {
            print("CacheProxy MISS. ");
            $result = $this->downloader->download($url);
            $this->cache[$url] = $result;
        } else {
            print("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/");

    // ...
}

print("Executing client code with real subject:\n");
$realSubject = new SimpleDownloader();
clientCode($realSubject);

print("\n");

print("Executing the same client code with a proxy:\n");
$proxy = new CachingDownloader($realSubject);
clientCode($proxy);

Output.txt: Результат виконання

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.