Заместитель — это объект, который выступает прослойкой между клиентом и реальным сервисным объектом. Заместитель получает вызовы от клиента, выполняет свою функцию (контроль доступа, кеширование, изменение запроса и прочее), а затем передаёт вызов сервисному объекту.
Заместитель имеет тот же интерфейс, что и реальный объект, поэтому для клиента нет разницы — работать через заместителя или напрямую.
Концептуальный пример
Этот пример показывает структуру паттерна Заместитель , а именно — из каких классов он состоит, какие роли эти классы выполняют и как они взаимодействуют друг с другом.
После ознакомления со структурой, вам будет легче воспринимать второй пример, который рассматривает реальный случай использования паттерна в мире PHP.
index.php: Пример структуры паттерна
<?php
namespace RefactoringGuru\Proxy\Conceptual;
/**
* Интерфейс Субъекта объявляет общие операции как для Реального Субъекта, так и
* для Заместителя. Пока клиент работает с Реальным Субъектом, используя этот
* интерфейс, вы сможете передать ему заместителя вместо реального субъекта.
*/
interface Subject
{
public function request(): void;
}
/**
* Реальный Субъект содержит некоторую базовую бизнес-логику. Как правило,
* Реальные Субъекты способны выполнять некоторую полезную работу, которая к
* тому же может быть очень медленной или точной – например, коррекция входных
* данных. Заместитель может решить эти задачи без каких-либо изменений в коде
* Реального Субъекта.
*/
class RealSubject implements Subject
{
public function request(): void
{
echo "RealSubject: Handling request.\n";
}
}
/**
* Интерфейс Заместителя идентичен интерфейсу Реального Субъекта.
*/
class Proxy implements Subject
{
/**
* @var RealSubject
*/
private $realSubject;
/**
* Заместитель хранит ссылку на объект класса РеальныйСубъект. Клиент может
* либо лениво загрузить его, либо передать Заместителю.
*/
public function __construct(RealSubject $realSubject)
{
$this->realSubject = $realSubject;
}
/**
* Наиболее распространёнными областями применения паттерна Заместитель
* являются ленивая загрузка, кэширование, контроль доступа, ведение журнала
* и т.д. Заместитель может выполнить одну из этих задач, а затем, в
* зависимости от результата, передать выполнение одноимённому методу в
* связанном объекте класса Реального Субъект.
*/
public function request(): void
{
if ($this->checkAccess()) {
$this->realSubject->request();
$this->logAccess();
}
}
private function checkAccess(): bool
{
// Некоторые реальные проверки должны проходить здесь.
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";
}
}
/**
* Клиентский код должен работать со всеми объектами (как с реальными, так и
* заместителями) через интерфейс Субъекта, чтобы поддерживать как реальные
* субъекты, так и заместителей. В реальной жизни, однако, клиенты в основном
* работают с реальными субъектами напрямую. В этом случае, для более простой
* реализации паттерна, можно расширить заместителя из класса реального
* субъекта.
*/
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: Результат выполнения
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.
Пример из реальной жизни
index.php: Пример из реальной жизни
<?php
namespace RefactoringGuru\Proxy\RealWorld;
/**
* Интерфейс Субъекта описывает интерфейс реального объекта.
*
* Дело в том, что у большинства приложений нет чётко определённого интерфейса.
* В этом случае лучше было бы расширить Заместителя за счёт существующего
* класса приложения. Если это неудобно, тогда первым шагом должно быть
* извлечение правильного интерфейса.
*/
interface Downloader
{
public function download(string $url): string;
}
/**
* Реальный Субъект делает реальную работу, хотя и не самым эффективным
* способом. Когда клиент пытается загрузить тот же самый файл во второй раз,
* наш загрузчик именно это и делает, вместо того, чтобы извлечь результат из
* кэша.
*/
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;
}
}
/**
* Класс Заместителя – это попытка сделать загрузку более эффективной. Он
* обёртывает реальный объект загрузчика и делегирует ему первые запросы на
* скачивание. Затем результат кэшируется, что позволяет последующим вызовам
* возвращать уже имеющийся файл вместо его повторной загрузки.
*/
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];
}
}
/**
* Клиентский код может выдать несколько похожих запросов на загрузку. В этом
* случае кэширующий заместитель экономит время и трафик, подавая результаты из
* кэша.
*
* Клиент не знает, что он работает с заместителем, потому что он работает с
* загрузчиками через абстрактный интерфейс.
*/
function clientCode(Downloader $subject)
{
// ...
$result = $subject->download("http://example.com/");
// Повторяющиеся запросы на загрузку могут кэшироваться для увеличения
// скорости.
$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: Результат выполнения
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.