Паттерн Proxy (заместитель, surrogate, суррогат)
Назначение паттерна Proxy
- Паттерн Proxy является суррогатом или замеcтителем другого объекта и контролирует доступ к нему.
- Предоставляя дополнительный уровень косвенности при доступе к объекту, может применяться для поддержки распределенного, управляемого или интеллектуального доступа.
- Являясь "оберткой" реального компонента, защищает его от излишней сложности.
Решаемая проблема
Вам нужно управлять ресурсоемкими объектами. Вы не хотите создавать экземпляры таких объектов до момента их реального использования.
Обсуждение паттерна Proxy
Суррогат или заместитель это объект, интерфейс которого идентичен интерфейсу реального объекта. При первом запросе клиента заместитель создает реальный объект, сохраняет его адрес и затем отправляет запрос этому реальному объекту. Все последующие запросы просто переадресуются инкапсулированному реальному объекту.
Существует четыре ситуации, когда можно использовать паттерн Proxy:
- Виртуальный proxy является заместителем объектов, создание которых обходится дорого. Реальный объект создается только при первом запросе/доступе клиента к объекту.
- Удаленный proxy предоставляет локального представителя для объекта, который находится в другом адресном пространстве ("заглушки" в RPC и CORBA).
- Защитный proxy контролирует доступ к основному объекту. "Суррогатный" объект предоставляет доступ к реальному объекту, только вызывающий объект имеет соответствующие права.
- Интеллектуальный proxy выполняет дополнительные действия при доступе к объекту.
Вот типичные области применения интеллектуальных proxy:
- Подсчет числа ссылок на реальный объект. При отсутствии ссылок память под объект автоматически освобождается (известен также как интеллектуальный указатель или smart pointer).
- Загрузка объекта в память при первом обращении к нему.
- Установка запрета на изменение реального объекта при обращении к нему других объектов.
Структура паттерна Proxy
Заместитель Proxy и реальный объект RealSubject имеют одинаковые интерфейсы класса Subject, поэтому заместитель может использоваться "прозрачно" для клиента вместо реального объекта.
UML-диаграмма классов паттерна Proxy
Пример паттерна Proxy
Паттерн Proxy для доступа к реальному объекту использует его суррогат или заместитель. Банковский чек является заместителем денежных средств на счете. Чек может быть использован вместо наличных денег для совершения покупок и, в конечном счете, контролирует доступ к наличным деньгам на счете чекодателя.
Использование паттерна Proxy
- Определите ту часть системы, которая лучше всего реализуется через суррогата.
- Определите интерфейс, который сделает суррогата и оригинальный компонент взаимозаменяемыми.
- Рассмотрите вопрос об использовании фабрики, инкапсулирующей решение о том, что желательно использовать на практике: оригинальный объект или его суррогат.
- Класс суррогата содержит указатель на реальный объект и реализует общий интерфейс.
- Указатель на реальный объект может инициализироваться в конструкторе или при первом использовании.
- Методы суррогата выполняют дополнительные действия и вызывают методы реального объекта.
Особенности паттерна Proxy
- Adapter предоставляет своему объекту другой интерфейс . Proxy предоставляет тот же интерфейс. Decorator предоставляет расширенный интерфейс.
- Decorator и Proxy имеют разные цели, но схожие структуры. Оба вводят дополнительный уровень косвенности: их реализации хранят ссылку на объект, на который они отправляют запросы.
Реализация паттерна Proxy
Паттерн Proxy: до и после
До
Прямые связи, накладные расходы на запуск и останов.
class Image
{
int m_id;
static int s_next;
public:
Image()
{
m_id = s_next++;
cout << " $$ ctor: " << m_id << '\n';
}
~Image()
{
cout << " dtor: " << m_id << '\n';
}
void draw()
{
cout << " drawing image " << m_id << '\n';
}
};
int Image::s_next = 1;
int main()
{
Image images[5];
for (int i; true;)
{
cout << "Exit[0], Image[1-5]: ";
cin >> i;
if (i == 0)
break;
images[i - 1].draw();
}
}
Вывод программы:
$$ ctor: 1
$$ ctor: 2
$$ ctor: 3
$$ ctor: 4
$$ ctor: 5
Exit[0], Image[1-5]: 2
drawing image 2
Exit[0], Image[1-5]: 4
drawing image 4
Exit[0], Image[1-5]: 2
drawing image 2
Exit[0], Image[1-5]: 0
dtor: 5
dtor: 4
dtor: 3
dtor: 2
dtor: 1
После
Инициализация при первом использовании.
- Спроектируйте класс-обертку с "дополнительным уровнем косвенности".
- Этот класс содержит указатель на реальный класс.
- Этот указатель инициализируется нулевым значением.
- Реальный объект создается при поступлении запроса "на первом использовании" (отложенная инициализация или lazy intialization).
- Запрос всегда делегируется реальному объекту.
class RealImage
{
int m_id;
public:
RealImage(int i)
{
m_id = i;
cout << " $$ ctor: " << m_id << '\n';
}
~RealImage()
{
cout << " dtor: " << m_id << '\n';
}
void draw()
{
cout << " drawing image " << m_id << '\n';
}
};
// 1. Класс-обертка с "дополнительным уровнем косвенности"
class Image
{
// 2. Класс-обертка содержит указатель на реальный класс
RealImage *m_the_real_thing;
int m_id;
static int s_next;
public:
Image()
{
m_id = s_next++;
// 3. Инициализируется нулевым значением
m_the_real_thing = 0;
}
~Image()
{
delete m_the_real_thing;
}
void draw()
{
// 4. Реальный объект создается при поступлении
// запроса "на первом использовании"
if (!m_the_real_thing)
m_the_real_thing = new RealImage(m_id);
// 5. Запрос всегда делегируется реальному объекту
m_the_real_thing->draw();
}
};
int Image::s_next = 1;
int main()
{
Image images[5];
for (int i; true;)
{
cout << "Exit[0], Image[1-5]: ";
cin >> i;
if (i == 0)
break;
images[i - 1].draw();
}
}
Вывод программы:
Exit[0], Image[1-5]: 2
$$ ctor: 2
drawing image 2
Exit[0], Image[1-5]: 4
$$ ctor: 4
drawing image 4
Exit[0], Image[1-5]: 2
drawing image 2
Exit[0], Image[1-5]: 0
dtor: 4
dtor: 2
Паттерн Proxy: защитный proxy контролирует доступ к основному объекту
class Person
{
string nameString;
static string list[];
static int next;
public:
Person()
{
nameString = list[next++];
}
string name()
{
return nameString;
}
};
string Person::list[] =
{
"Tom", "Dick", "Harry", "Bubba"
};
int Person::next = 0;
class PettyCashProtected
{
int balance;
public:
PettyCashProtected()
{
balance = 500;
}
bool withdraw(int amount)
{
if (amount > balance)
return false;
balance -= amount;
return true;
}
int getBalance()
{
return balance;
}
};
class PettyCash
{
PettyCashProtected realThing;
public:
bool withdraw(Person &p, int amount)
{
if (p.name() == "Tom" || p.name() == "Harry"
|| p.name() == "Bubba")
return realThing.withdraw(amount);
else
return false;
}
int getBalance()
{
return realThing.getBalance();
}
};
int main()
{
PettyCash pc;
Person workers[4];
for (int i = 0, amount = 100; i < 4; i++, amount += 100)
if (!pc.withdraw(workers[i], amount))
cout << "No money for " << workers[i].name() << '\n';
else
cout << amount << " dollars for " << workers[i].name() << '\n';
cout << "Remaining balance is " << pc.getBalance() << '\n';
}
Вывод программы:
100 dollars for Tom
No money for Dick
300 dollars for Harry
No money for Bubba
Remaining balance is 100
Паттерн Proxy: операторы "->" and "." дают различные результаты
class Subject
{
public:
virtual void execute() = 0;
};
class RealSubject: public Subject
{
string str;
public:
RealSubject(string s)
{
str = s;
}
/*virtual*/void execute()
{
cout << str << '\n';
}
};
class ProxySubject: public Subject
{
string first, second, third;
RealSubject *ptr;
public:
ProxySubject(string s)
{
int num = s.find_first_of(' ');
first = s.substr(0, num);
s = s.substr(num + 1);
num = s.find_first_of(' ');
second = s.substr(0, num);
s = s.substr(num + 1);
num = s.find_first_of(' ');
third = s.substr(0, num);
s = s.substr(num + 1);
ptr = new RealSubject(s);
}
~ProxySubject()
{
delete ptr;
}
RealSubject *operator->()
{
cout << first << ' ' << second << ' ';
return ptr;
}
/*virtual*/void execute()
{
cout << first << ' ' << third << ' ';
ptr->execute();
}
};
int main()
{
ProxySubject obj(string("the quick brown fox jumped over the dog"));
obj->execute();
obj.execute();
}
Вывод программы:
the quick fox jumped over the dog
the brown fox jumped over the dog