Заместитель (Proxy) — шаблон проектирования
27 января 2020
Шаблон предлагает класс-заместитель для перехвата вызываемых методов исходного класса. Паттерн используется для действий, которые нужно выполнить до или после вызова методов исходного класса — например, для логирования, кеширования, проверки прав доступа и других действий. А так как класс-заместитель повторяет публичные свойства и методы исходного класса, то клиентский код не требует внесения больших изменений. По факту, Заместитель выступает в роли прокси-агента между клиентским кодом и исходным классом.
Паттерн хоть и похож на шаблон Адаптера и Декоратора, но всё же имеет отличия. Например, в сравнении с Адаптером, Заместитель обязан повторять внешний интерфейс исходного класса. А если сравнивать с Декоратором, то Заместитель не изменяет поведенческую логику исходного класса.
Код реализации паттерна на языке PHP (требуется включение модуля Memcached). Скачать исходники (ZIP, 3 Кб)
Результат работы скрипта test_simple.php
Теперь нужно решить задачу кэширования статей, в этом нам поможет модуль Memcached и шаблон Заместитель. Мы не будет менять класс Articles, а создадим для него замещающий класс ArticlesCached, который будет выполнять всю работу, связанную с кэшированием.
Важный момент! Чтобы клиентский код не пришлось сильно переделывать, класс ArticlesCached должен содержать все публичные методы класса Articles.
Как можно увидеть, класс ArticlesCached не заменяет функционал класса Articles. Напротив, Articles продолжает использоваться, только теперь между клиентским кодом и классом статей Acrticles появилась прослойка в виде класса ArticlesCached и клиентский код теперь станет работать с прослойкой, а не напрямую с классом Articles.
Результат работы скрипта test_cached.php
Паттерн хоть и похож на шаблон Адаптера и Декоратора, но всё же имеет отличия. Например, в сравнении с Адаптером, Заместитель обязан повторять внешний интерфейс исходного класса. А если сравнивать с Декоратором, то Заместитель не изменяет поведенческую логику исходного класса.
Пример задачи: сделать кэширование статей.
Код реализации паттерна на языке PHP (требуется включение модуля Memcached). Скачать исходники (ZIP, 3 Кб)

libs.php
Содержит функции, которые имитируют выборку статей из базы данных:- getArticlesFromDB() – возвращает список статей;
- getArticleFromDB($id) – возвращает статью по её ID.
<?
//Глобальная переменная для имитации хранилища статей
GLOBAL $tableArticles;
$tableArticles = array(
1 => array(
'title' => 'Тестовая статья 1',
'description' => 'Тестовый текст с описанием статьи 1',
),
2 => array(
'title' => 'Тестовая статья 2',
'description' => 'Тестовый текст с описанием статьи 2',
),
3 => array(
'title' => 'Тестовая статья 3',
'description' => 'Тестовый текст с описанием статьи 3',
),
4 => array(
'title' => 'Тестовая статья 4',
'description' => 'Тестовый текст с описанием статьи 4',
),
5 => array(
'title' => 'Тестовая статья 5',
'description' => 'Тестовый текст с описанием статьи 5',
)
);
/**
* Получает статьи из базы данных.
* Это виртуальная функция, имитирующая запрос в БД
*
* @return array - вернёт массив статей
*/
function getArticlesFromDB()
{
GLOBAL $tableArticles;
return $tableArticles;
}
/**
* Возвращает статью по её идентификатору (ID).
* Это виртуальная функция, имитирующая запрос в БД
*
* @param int $id - ID статьи
* @return array|bool - вернёт массив в случае успеха или 'false' в случае, если статья не будет найдена
*/
function getArticleFromDB($id)
{
GLOBAL $tableArticles;
$id = (int)$id;
if (isset($tableArticles[$id])) {
return $tableArticles[$id];
} else {
return false;
}
}
?>
articles.class.php
Содержит класс Articles для работы со статьями. Класс состоит из следующих методов:- getList() — возвращает список статей, используя функцию getArticlesFromDB() для выборки статей из БД;
- getById($id) — возвращает статью по её идентификатору (ID), используя функцию getArticleFromDB($id) для выборки статьи из БД.
<?
//Подключаем библиотеку дополнительных функций
require_once(__DIR__.'/libs.php');
//Класс статей
class Articles
{
/**
* Возвращает список статей
* @return array
*/
public function getList()
{
//Получаем список статей из БД (функция имитирует выборку из базы данных)
$items = getArticlesFromDB();
return $items;
}
/**
* Возвращает статью по её идентификатору (ID).
*
* @param int $id - ID статьи
* @return array|bool - вернёт массив в случае успеха или 'false' в случае, если статья не будет найдена
*/
public function getById($id)
{
//Приведение типов
$id = (int)$id;
$item = getArticleFromDB($id);
return $item;
}
}
?>
test_simple.php
Содержит тестовый скрипт, в котором создаётся объект класса Articles, затем через него происходит получение сначала списка статей, а потом одной статьи с ID = 3. В конце полученные данные выводятся на экран.<?
//Подключаем класс для работы со статьями
require_once(__DIR__.'/articles.class.php');
$articles = new Articles();
$items = $articles->getList();
$item = $articles->getById(3);
echo '<b>Список статей:</b><br>';
echo '<pre>'.print_r($items, true).'</pre><br>';
echo '<b>Статья с ID = 3:</b><br>';
echo '<pre>'.print_r($item, true).'</pre>';
?>
Результат работы скрипта test_simple.php
Список статей:
Array
(
[1] => Array
(
[title] => Тестовая статья 1
[description] => Тестовый текст с описанием статьи 1
)
[2] => Array
(
[title] => Тестовая статья 2
[description] => Тестовый текст с описанием статьи 2
)
[3] => Array
(
[title] => Тестовая статья 3
[description] => Тестовый текст с описанием статьи 3
)
[4] => Array
(
[title] => Тестовая статья 4
[description] => Тестовый текст с описанием статьи 4
)
[5] => Array
(
[title] => Тестовая статья 5
[description] => Тестовый текст с описанием статьи 5
)
)
Статья с ID = 3:
Array
(
[title] => Тестовая статья 3
[description] => Тестовый текст с описанием статьи 3
)
Теперь нужно решить задачу кэширования статей, в этом нам поможет модуль Memcached и шаблон Заместитель. Мы не будет менять класс Articles, а создадим для него замещающий класс ArticlesCached, который будет выполнять всю работу, связанную с кэшированием.
Важный момент! Чтобы клиентский код не пришлось сильно переделывать, класс ArticlesCached должен содержать все публичные методы класса Articles.
articles_cached.class.php
Содержит класс ArticlesCached для работы со статьями и их кэшированием. Класс состоит из следующих элементов:- articles — свойство, объект класса Articles, используется для получения статей;
- memcached — свойство, объект кэша Memcached;
- cacheTime — свойство, время, в течение которого данные из кэша считаются актуальными;
- __construct($cacheTime = 3600) — конструктор, в котором создаётся объект класса Articles и затем помещается в свойство articles;
- getList() — метод возвращает список статей.
Метод запрашивает статьи из кэша и если их там нету или данные в кэше уже устарели, то получает статьи через свойство articles (объект класса Articles), вызывая его метод getList(). Затем полученные актуальные данные сохраняются в кэш; - getById($id) — метод возвращает статью по её идентификатору (ID).
Аналогично, метод запрашивает статью из кэша и в случае её отсутствия или когда данные кэша уже неактуальны, то получает статью через свойство articles (объект класса Articles), вызывая его метод getById($id). Затем полученные актуальные данные сохраняются в кэш.
Как можно увидеть, класс ArticlesCached не заменяет функционал класса Articles. Напротив, Articles продолжает использоваться, только теперь между клиентским кодом и классом статей Acrticles появилась прослойка в виде класса ArticlesCached и клиентский код теперь станет работать с прослойкой, а не напрямую с классом Articles.
<?
//Класс статей Articles
require_once(__DIR__.'/articles.class.php');
/**
* Класс статей с поддержкой кэширования.
* В качестве кэша используется система Memcached
*/
class ArticlesCached
{
/**
* Объект исходного класса Articles для работы со статьями
* @var Articles
*/
private $articles;
/**
* Объект кэша Memcached
* @var Memcached
*/
private $memcached;
/**
* Вмемя жизни кэша
* @var int
*/
private $cacheTime;
/**
* Конструктор объекта
* @param int $cacheTime - время жизни кэша в секундах. По умолчанию время жизни составляет 1 час (3600 секунд)
*/
public function __construct($cacheTime = 3600)
{
if (class_exists('Memcached') == false) {
throw new Exception('Для работы с классом ArticlesCached нужно включить поддержку системы кэширования Memcached');
}
//Создаём объект Memcached и соединяемся с сервером
$this->memcached = new Memcached();
if ($this->memcached->addServer('localhost', 11211) == false) {
throw new Exception('Не удалось соедениться с сервером Memcached');
}
$this->articles = new Articles();
$this->cacheTime = (int)$cacheTime;
}
/**
* Возвращает список статей
* @return array
*/
public function getList()
{
//Сначала запрашиваем статьи из кэша. Если их там нету, то получаем статьи напрямую из объекта класса Articles
$items = $this->memcached->get('articles');
if (is_array($items) == false) {
$items = $this->articles->getList();
$this->memcached->set('articles', $items, $this->cacheTime);
}
return $items;
}
/**
* Возвращает статью по её идентификатору (ID).
*
* @param int $id - ID статьи
* @return array|bool - вернёт массив в случае успеха или 'false' в случае, если статья не будет найдена
*/
public function getById($id)
{
//Приведение типов
$id = (int)$id;
//Ключ статьи в кэше
$keyArticle = 'article_'.$id;
//Сначала запрашиваем статью из кэша. Если её там нету, то получаем статью напрямую из объекта класса Articles
$item = $this->memcached->get($keyArticle);
if (is_array($item) == false) {
$item = $this->articles->getById($id);
if (is_array($item) == true) {
$this->memcached->set($keyArticle, $item, $this->cacheTime);
}
}
return $item;
}
}
?>
test_cached.php
Скрипт содержит такой же код, как в скрипте test_simple.php, только класс Articles мы заменили на ArticlesCached и теперь статьи кэшируются.<?
//Подключаем класс для работы со статьями с поддержкой кэширования
require_once(__DIR__.'/articles_cached.class.php');
$articles = new ArticlesCached();
$items = $articles->getList();
$item = $articles->getById(3);
echo '<b>Список статей:</b><br>';
echo '<pre>'.print_r($items, true).'</pre><br>';
echo '<b>Статья с ID = 3:</b><br>';
echo '<pre>'.print_r($item, true).'</pre>';
?>
Результат работы скрипта test_cached.php
Список статей:
Array
(
[1] => Array
(
[title] => Тестовая статья 1
[description] => Тестовый текст с описанием статьи 1
)
[2] => Array
(
[title] => Тестовая статья 2
[description] => Тестовый текст с описанием статьи 2
)
[3] => Array
(
[title] => Тестовая статья 3
[description] => Тестовый текст с описанием статьи 3
)
[4] => Array
(
[title] => Тестовая статья 4
[description] => Тестовый текст с описанием статьи 4
)
[5] => Array
(
[title] => Тестовая статья 5
[description] => Тестовый текст с описанием статьи 5
)
)
Статья с ID = 3:
Array
(
[title] => Тестовая статья 3
[description] => Тестовый текст с описанием статьи 3
)