PHP: Заместитель

Proxy Заместитель Proxy

Заместитель — это объект, который выступает прослойкой между клиентом и реальным сервисным объектом. Заместитель получает вызовы от клиента, выполняет свою функцию (контроль доступа, кеширование, изменение запроса и прочее), а затем передаёт вызов сервисному объекту.

Заместитель имеет тот же интерфейс, что и реальный объект, поэтому для клиента нет разницы — работать через заместителя или напрямую.

Подробней о Заместителе

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

Сложность:

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

Применимость: Паттерн Заместитель применяется в PHP коде тогда, когда надо заменить настоящий объект его суррогатом, причём незаметно для клиентов настоящего объекта. Это позволит выполнить какие-то добавочные поведения до или после основного поведения настоящего объекта.

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

Этот пример показывает структуру паттерна Заместитель, а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом. После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.

ProxyStructural.php: Пример структуры паттерна

<?php

namespace RefactoringGuru\Proxy\Structural;

/**
 * Интерфейс Субъекта объявляет общие операции как для Реального Субъекта, так и
 * для Заместителя. Пока клиент работает с Реальным Субъектом, используя этот
 * интерфейс,  вы сможете передать ему заместителя вместо реального субъекта.
 */
interface Subject
{
    public function request();
}

/**
 * Реальный Субъект содержит некоторую базовую бизнес-логику. Как правило,
 * Реальные Субъекты способны выполнять некоторую полезную работу, которая к
 * тому же может быть очень медленной или точной – например, коррекция входных
 * данных. Заместитель может решить эти задачи без каких-либо изменений в коде
 * Реального Субъекта.
 */
class RealSubject implements Subject
{
    public function request()
    {
        print("RealSubject: Handling request.\n");
    }
}

/**
 * Интерфейс Заместителя идентичен интерфейсу Реального Субъекта.
 */
class Proxy implements Subject
{
    /**
     * @var RealSubject
     */
    private $realSubject;

    /**
     * Заместитель хранит ссылку на объект класса РеальныйСубъект.  Клиент может
     * либо лениво загрузить его, либо передать Заместителю.
     */
    public function __construct(RealSubject $realSubject)
    {
        $this->realSubject = $realSubject;
    }

    /**
     * Наиболее распространёнными областями применения паттерна Заместитель
     * являются ленивая загрузка, кэширование, контроль доступа, ведение журнала
     * и т.д. Заместитель может выполнить одну из этих задач, а затем, в
     * зависимости от результата, передать выполнение одноимённому методу в
     * связанном объекте класса РеальныйСубъект.
     */
    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");
    }
}

/**
 * Клиентский код должен работать со всеми объектами (как с реальными, так и
 * заместителями) через интерфейс Субъекта, чтобы поддерживать как реальные
 * субъекты, так и заместителей. В реальной жизни, однако, клиенты в основном
 * работают с реальными субъектами напрямую. В этом случае, для более простой
 * реализации паттерна, можно расширить заместителя из класса реального
 * субъекта.
 */
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;

/**
 * Паттерн Заместитель
 *
 * Назначение: Предоставляет заменитель или местозаполнитель для другого
 * объекта, чтобы контролировать доступ к оригинальному объекту или добавлять
 * другие обязанности.
 *
 * Пример: Существует бесчисленное множество направлений, где могут быть
 * использованы заместители: кэширование, логирование, контроль доступа,
 * отложенная инициализация и т.д.  В этом примере показано, как паттерн
 * Заместитель может повысить производительность объекта-загрузчика путём
 * кэширования его результатов.
 */

/**
 * Интерфейс Субъекта описывает интерфейс реального объекта.
 *
 * Дело в том, что у большинства приложений нет чётко определённого интерфейса.
 * В этом случае лучше было бы расширить Заместителя за счёт существующего
 * класса приложения.  Если это неудобно, тогда первым шагом должно быть
 * извлечение правильного интерфейса.
 */
interface Downloader
{
    public function download(string $url): string;
}

/**
 * Реальный Субъект делает реальную работу, хотя и не самым эффективным
 * способом. Когда клиент пытается загрузить тот же самый файл во второй раз,
 * наш загрузчик именно это и делает, вместо того, чтобы извлечь результат из
 * кэша.
 */
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;
    }
}

/**
 * Класс Заместителя – это попытка сделать загрузку более эффективной. Он
 * обёртывает реальный объект загрузчика и делегирует ему первые запросы на
 * скачивание. Затем результат кэшируется, что позволяет последующим вызовам
 * возвращать уже имеющийся файл вместо его повторной загрузки.
 */
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];
    }
}

/**
 * Клиентский код может выдать несколько похожих запросов на загрузку. В этом
 * случае кэширующий заместитель экономит время и трафик, подавая результаты из
 * кэша.
 *
 * Клиент не знает, что он работает с заместителем, потому что он работает с
 * загрузчиками через абстрактный интерфейс.
 */
function clientCode(Downloader $subject)
{
    // ...

    $result = $subject->download("http://example.com/");

    // Повторяющиеся запросы на загрузку могут кэшироваться для увеличения
    // скорости.

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