Заместитель (Proxy)
Заместитель (Proxy)
Суть паттерна
Заместитель — это структурный паттерн проектирования, который позволяет подставлять вместо реальных объектов специальные объекты-заменители. Эти объекты перехватывают вызовы к оригинальному объекту, позволяя сделать что-то до или после передачи вызова оригиналу.

Проблема
Для чего вообще контролировать доступ к объектам? Рассмотрим такой пример: у вас есть внешний ресурсоёмкий объект, который нужен не все время, а изредка.

Мы могли бы создавать этот объект не в самом начале программы, а только тогда, когда он кому-то реально понадобится. Каждый клиент объекта получил бы некий код отложенной инициализации. Но, вероятно, это привело бы к множественному дублированию кода.
В идеале, этот код хотелось бы поместить прямо в служебный класс, но это не всегда возможно. Например, код класса может находиться в закрытой сторонней библиотеке.
Решение
Паттерн Заместитель предлагает создать новый класс-дублёр, имеющий тот же интерфейс, что и оригинальный служебный объект. При получении запроса от клиента объект-заместитель сам бы создавал экземпляр служебного объекта и переадресовывал бы ему всю реальную работу.

Но в чём же здесь польза? Вы могли бы поместить в класс заместителя какую-то промежуточную логику, которая выполнялась бы до (или после) вызовов этих же методов в настоящем объекте. А благодаря одинаковому интерфейсу, объект-заместитель можно передать в любой код, ожидающий сервисный объект.
Аналогия из жизни

Платёжная карточка — это заместитель пачки наличных. И карточка, и наличные имеют общий интерфейс — ими можно оплачивать товары. Для покупателя польза в том, что не надо таскать с собой тонны наличных, а владелец магазина рад, что ему не нужно делать дорогостоящую инкассацию наличности в банк — деньги поступают к нему на счёт напрямую.
Структура

Интерфейс сервиса определяет общий интерфейс для сервиса и заместителя. Благодаря этому, объект заместителя можно использовать там, где ожидается объект сервиса.
Сервис содержит полезную бизнес-логику.
Заместитель хранит ссылку на объект сервиса. После того как заместитель заканчивает свою работу (например, инициализацию, логирование, защиту или другое), он передаёт вызовы вложенному сервису.
Заместитель может сам отвечать за создание и удаление объекта сервиса.
Клиент работает с объектами через интерфейс сервиса. Благодаря этому, его можно «одурачить», подменив объект сервиса объектом заместителя.
Применимость
Ленивая инициализация (виртуальный прокси). Когда у вас есть тяжёлый объект, грузящий данные из файловой системы или базы данных.
Вместо того, чтобы грузить данные сразу после старта программы, можно сэкономить ресурсы и создать объект тогда, когда он действительно понадобится.
Защита доступа (защищающий прокси). Когда в программе есть разные типы пользователей, и вам хочется защищать объект от неавторизованного доступа. Например, если ваши объекты — это важная часть операционной системы, а пользователи — сторонние программы (хорошие или вредоносные).
Прокси может проверять доступ при каждом вызове и передавать выполнение служебному объекту, если доступ разрешён.
Локальный запуск сервиса (удалённый прокси). Когда настоящий сервисный объект находится на удалённом сервере.
В этом случае заместитель транслирует запросы клиента в вызовы по сети в протоколе, понятном удалённому сервису.
Логирование запросов (логирующий прокси). Когда требуется хранить историю обращений к сервисному объекту.
Заместитель может сохранять историю обращения клиента к сервисному объекту.
Кеширование объектов («умная» ссылка). Когда нужно кешировать результаты запросов клиентов и управлять их жизненным циклом.
Заместитель может подсчитывать количество ссылок на сервисный объект, которые были отданы клиенту и остаются активными. Когда все ссылки освобождаются, можно будет освободить и сам сервисный объект (например, закрыть подключение к базе данных).
Кроме того, Заместитель может отслеживать, не менял ли клиент сервисный объект. Это позволит использовать объекты повторно и здóрово экономить ресурсы, особенно если речь идёт о больших прожорливых сервисах.
Шаги реализации
Определите интерфейс, который бы сделал заместитель и оригинальный объект взаимозаменяемыми.
Создайте класс заместителя. Он должен содержать ссылку на сервисный объект. Чаще всего, сервисный объект создаётся самим заместителем. В редких случаях заместитель получает готовый сервисный объект от клиента через конструктор.
Реализуйте методы заместителя в зависимости от его предназначения. В большинстве случаев, проделав какую-то полезную работу, методы заместителя должны передать запрос сервисному объекту.
Подумайте о введении фабрики, которая решала бы, какой из объектов создавать — заместитель или реальный сервисный объект. Но, с другой стороны, эта логика может быть помещена в создающий метод самого заместителя.
Подумайте, не реализовать ли вам ленивую инициализацию сервисного объекта при первом обращении клиента к методам заместителя.
Преимущества и недостатки
Позволяет контролировать сервисный объект незаметно для клиента.
Может работать, даже если сервисный объект ещё не создан.
Может контролировать жизненный цикл служебного объекта.
Усложняет код программы из-за введения дополнительных классов.
Увеличивает время отклика от сервиса.
Пример
# include <iostream>
# include <memory>
# include <map>
# include <random>
using namespace std;
class Subject
{
public:
virtual ~Subject() = default;
virtual pair<bool, double> request(size_t index) = 0;
virtual bool changed() { return true; }
};
class RealSubject : public Subject
{
private:
bool flag{ false };
size_t counter{ 0 };
public:
virtual pair<bool, double> request(size_t index) override;
virtual bool changed() override;
};
class Proxy : public Subject
{
protected:
shared_ptr<RealSubject> realsubject;
public:
Proxy(shared_ptr<RealSubject> real) : realsubject(real) {}
};
class ConProxy : public Proxy
{
private:
map<size_t, double> cache;
public:
using Proxy::Proxy;
virtual pair<bool, double> request(size_t index) override;
};
#pragma region Methods
bool RealSubject::changed()
{
if (counter == 0)
{
flag = true;
}
if (++counter == 7)
{
counter = 0;
flag = false;
}
return flag;
}
pair<bool, double> RealSubject::request(size_t index)
{
random_device rd;
mt19937 gen(rd());
return pair<bool, double>(true, generate_canonical<double, 10>(gen));
}
pair<bool, double> ConProxy::request(size_t index)
{
pair<bool, double> result;
if (!realsubject)
{
cache.clear();
result = pair<bool, double>(false, 0.);
}
else if (!realsubject->changed())
{
cache.clear();
result = realsubject->request(index);
cache.insert(map<size_t, double>::value_type(index, result.second));
}
else
{
map<size_t, double>::const_iterator it = cache.find(index);
if (it != cache.end())
{
result = pair<bool, double>(true, it->second);
}
else
{
result = realsubject->request(index);
cache.insert(map<size_t, double>::value_type(index, result.second));
}
}
return result;
}
#pragma endregion
int main()
{
shared_ptr<RealSubject> subject = make_shared<RealSubject>();
shared_ptr<Subject> proxy = make_shared<ConProxy>(subject);
for (size_t i = 0; i < 21; ++i)
{
cout << "( " << i + 1 << ", " << proxy->request(i % 3).second << " )" << endl;
if ((i + 1) % 3 == 0)
cout << endl;
}
}
Last updated