Замісник (шаблон проєктування)
Шаблон Proxy (Заступник) — Шаблон проєктування. Надає об'єкт, що контролює доступ, перехоплюючи всі виклики до нього.
Проблема
Необхідно керувати доступом до об'єкта так, щоб створювати громіздкі об'єкти «на вимогу».
Вирішення
Створити сурогат громіздкого об'єкта. «Заступник» зберігає посилання, яке дозволяє заступникові звернутися до реального суб'єкта (об'єкт класу «Заступник» може звертатися до об'єкта класу «Суб'єкт», якщо інтерфейси «Реального Суб'єкта» і «Суб'єкта» однакові). Оскільки інтерфейс «Реального Суб'єкта» ідентичний інтерфейсу «Суб'єкта», так, що «Заступника» можна підставити замість «Реального Суб'єкта», контролює доступ до «Реального Суб'єкта», може відповідати за створення або видалення «Реального Суб'єкта». «Суб'єкт» визначає загальний для «Реального Суб'єкта» і «Заступника» інтерфейс, так, що «Заступник» може бути використаний скрізь, де очікується «Реальний Суб'єкт».
«Заступник» може мати і інші обов'язки, а саме:
- віддалений «Заступник» може відповідати за кодування запиту і його аргументів і відправку закодованого запиту реальному «Суб'єктові»
- віртуальний «Заступник» може кешувати додаткову інформацію про реального «Суб'єкта», щоб відкласти його створення
- захищаючий «Заступник» може перевіряти, чи має клієнтський об'єкт необхідні для виконання запиту права.
Типи замісника
- Кеш проксі. Кеш проксі покращує продуктивність реального об'єкта, коли вони виконують довгі операції, а результат цих операцій рідко змінюється. Наприклад, реальний об'єкт містить метод, що рахує прості числа. При першому виклику проксі делегує операцію реальному об'єкту і зберігає результат повернення. При всіх наступних викликах, проксі повертає кешовану інформацію.
- Проксі захисту. Проксі захисту, додає рівень захисту над реальним об'єктом. Наприклад, реальний об'єкт може отримати доступ до бази даних і повернути конфіденційні дані користувачу. Проксі-захисник міг би додати методи або властивості, які дозволяють клієнтському об'єкту забезпечити відповідну аутентифікацію, перш ніж повернути дані. Він також може фільтрувати дані відповідно до прав автентифікованого користувача.
- Віддалений проксі. Віддалений проксі-сервер надає локальний об'єкт, який посилається на реальний об'єкт в іншому місці, як правило, через мережеве з'єднання. Проксі виконує необхідні дії для кодування запитів на передачу мережі і приймає результати від віддаленого ресурсу, перш ніж повертати їх клієнту. Звичайний приклад віддаленого проксі-сервера - це локальний клас, створений Visual Studio для забезпечення доступу до веб-служби.
- Розумний проксі. Розумний проксі додає додаткову функціональність реальному об'єкта. Ця функціональність часто невидима для клієнта. Наприклад, підрахунок існуючих посилань на ресурсомісткий об'єкт таким чином, що коли лічильник досягне нуля, дані об'єкта можуть бути видалені з пам'яті. Можна також використовувати розумний проксі для логування викликів користувача до реального об'єкта.
- Віртуальний проксі. Віртуальний проксі забезпечує спрощену версію складного об'єкта. Тільки тоді, коли потрібні дані реального об'єкта, лише тоді він створюється, забезпечуючи форму лінивої ініціалізації. Наприклад, провідник Windows, може відобразити файли, який видно на екрані. При отриманні списку файлів, ім'я файлу, розмір та інша легка для отримання інформація буде зберігатися в об'єктах проксі. Тільки при команді "перегляд документа", реальний об'єкт буде створено і заповнено повним вмістом файлу, оскільки вони доступні повільніше і потребують більше пам'яті.
Переваги
- віддалений замісник;
- віртуальний замісник може виконувати оптимізацію;
- захищає замісника;
- «Розумне» посилання (вказівник);
Недоліки
- Різко збільшує час відгуку
Реалізація
C++
#include <iostream>
using namespace std;
struct ProxyBase
{
virtual void f() = 0;
virtual void g() = 0;
virtual void h() = 0;
virtual ~ProxyBase() {}
};
struct Implementation1 : public ProxyBase
{
void f() { cout << " Implementation1.f()" << endl; }
void g() { cout << " Implementation1.g()" << endl; }
void h() { cout << " Implementation1.h()" << endl; }
};
struct Implementation2 : public ProxyBase
{
void f() { cout << " Implementation2.f()" << endl; }
void g() { cout << " Implementation2.g()" << endl; }
void h() { cout << " Implementation2.h()" << endl; }
};
class Proxy : public ProxyBase
{
private:
ProxyBase* implementation;
public:
Proxy(ProxyBase* pb) { implementation = pb; }
~Proxy() { delete implementation; }
void f() { implementation->f(); }
void g() { implementation->g(); }
void h() { implementation->h(); }
};
void main()
{
Proxy p(new Implementation2);
p.f();
p.g();
p.h();
}
C#
class MainApp
{
static void Main()
{
// Create math proxy
MathProxy p = new MathProxy();
// Do the math
Console.WriteLine("4 + 2 = " + p.Add(4, 2));
Console.WriteLine("4 - 2 = " + p.Sub(4, 2));
Console.WriteLine("4 * 2 = " + p.Mul(4, 2));
Console.WriteLine("4 / 2 = " + p.Div(4, 2));
// Wait for user
Console.Read();
}
}
// "Subject"
public interface IMath
{
double Add(double x, double y);
double Sub(double x, double y);
double Mul(double x, double y);
double Div(double x, double y);
}
// "RealSubject"
class Math : IMath
{
public double Add(double x, double y){return x + y;}
public double Sub(double x, double y){return x - y;}
public double Mul(double x, double y){return x * y;}
public double Div(double x, double y){return x / y;}
}
// "Proxy Object"
class MathProxy : IMath
{
Math math;
public MathProxy()
{
math = new Math();
}
public double Add(double x, double y)
{
return math.Add(x,y);
}
public double Sub(double x, double y)
{
return math.Sub(x,y);
}
public double Mul(double x, double y)
{
return math.Mul(x,y);
}
public double Div(double x, double y)
{
return math.Div(x,y);
}
}
using System;
using System.Threading;
namespace Proxy
{
public abstract class CalculatorBase
{
public abstract int Calculate();
}
public class NumberMultiplier : CalculatorBase
{
public override int Calculate()
{
int result = 1;
for (int i = 2; i <= 5; i++)
{
result *= i;
// імітуємо важку операцію
Thread.Sleep(500);
}
return result;
}
}
// Кеш проксі
public class NumberMultiplierProxy : CalculatorBase
{
private NumberMultiplier realNumberMultiplier;
private int cachedValue;
public override int Calculate()
{
if (realNumberMultiplier == null)
{
realNumberMultiplier = new NumberMultiplier();
cachedValue = realNumberMultiplier.Calculate();
}
return cachedValue;
}
}
class Program
{
static void Main(string[] args)
{
CalculatorBase cb;
// перевіряємо роботу реального об'єкта
cb = new NumberMultiplier();
Console.WriteLine("Real object");
for (int i = 0; i < 3; i++)
{
Console.WriteLine(cb.Calculate());
}
// перевірямо роботу проксі
cb = new NumberMultiplierProxy();
Console.WriteLine("Cache proxy");
for (int i = 0; i < 3; i++)
{
Console.WriteLine(cb.Calculate());
}
Console.ReadLine();
}
}
}
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.