Паттерн Заместитель (Proxy) предоставляет объект-заместитель, который управляет доступом к другому объекту. То есть создается объект-суррогат, который может выступать в роли другого объекта и замещать его.
Когда надо осуществлять взаимодействие по сети, а объект-проси должен имитировать поведения объекта в другом адресном пространстве. Использование прокси позволяет снизить накладные издержки при передачи данных через сеть. Подобная ситуация еще называется удалённый заместитель (remote proxies)
Когда нужно управлять доступом к ресурсу, создание которого требует больших затрат. Реальный объект создается только тогда, когда он действительно может понадобится, а до этого все запросы к нему обрабатывает прокси-объект. Подобная ситуация еще называется виртуальный заместитель (virtual proxies)
Когда необходимо разграничить доступ к вызываемому объекту в зависимости от прав вызывающего объекта. Подобная ситуация еще называется защищающий заместитель (protection proxies)
Когда нужно вести подсчет ссылок на объект или обеспечить потокобезопасную работу с реальным объектом. Подобная ситуация называется "умные ссылки" (smart reference)
С помощью UML паттерн может быть описан так:
На C# паттерн формально может выглядеть следующим образом:
class Client { void Main() { Subject subject = new Proxy(); subject.Request(); } } abstract class Subject { public abstract void Request(); } class RealSubject : Subject { public override void Request() {} } class Proxy : Subject { RealSubject realSubject; public override void Request() { if (realSubject == null) realSubject = new RealSubject(); realSubject.Request(); } }
Subject: определяет общий интерфейс для Proxy и RealSubject. Поэтому Proxy может использоваться вместо RealSubject
RealSubject: представляет реальный объект, для которого создается прокси
Proxy: заместитель реального объекта. Хранит ссылку на реальный объект, контролирует к нему доступ, может управлять его созданием и удалением. При необходимости Proxy переадресует запросы объекту RealSubject
Client: использует объект Proxy для доступа к объекту RealSubject
Рассмотрим применение паттерна. Допустим, мы взаимодействуем с базой данных через Entity Framework. У нас есть модель и контекст данных:
class Page { public int Id { get; set; } public int Number { get; set; } public string Text { get; set; } } class PageContext : DbContext { public DbSet<Page> Pages { get; set; } }
Класс Page представляет отдельную страницу книги, у которой есть номер и текст. Взаимодействие с базой данных может уменьшить производительность приложения. Для оптимизации приложения мы можем использовать паттерн Прокси. Для этого определим репозиторий и его прокси-двойник:
class Program { static void Main(string[] args) { using(IBook book = new BookStoreProxy()) { // читаем первую страницу Page page1 = book.GetPage(1); Console.WriteLine(page1.Text); // читаем вторую страницу Page page2 = book.GetPage(2); Console.WriteLine(page2.Text); // возвращаемся на первую страницу page1 = book.GetPage(1); Console.WriteLine(page1.Text); } Console.Read(); } } class Page { public int Id { get; set; } public int Number { get; set; } public string Text { get; set; } } class PageContext : DbContext { public DbSet<Page> Pages { get; set; } } interface IBook : IDisposable { Page GetPage(int number); } class BookStore : IBook { PageContext db; public BookStore() { db = new PageContext(); } public Page GetPage(int number) { return db.Pages.FirstOrDefault(p => p.Number == number); } public void Dispose() { db.Dispose(); } } class BookStoreProxy : IBook { List<Page> pages; BookStore bookStore; public BookStoreProxy() { pages=new List<Page>(); } public Page GetPage(int number) { Page page = pages.FirstOrDefault(p=>p.Number==number); if (page == null) { if (bookStore == null) bookStore = new BookStore(); page= bookStore.GetPage(number); pages.Add(page); } return page; } public void Dispose() { if(bookStore!=null) bookStore.Dispose(); } }
Итак, здесь определен общий интерфейс IBook для реального объекта и для его прокси-класса. Он определяет один метод GetPage()
для получения страницы по номеру.
Реальный объект BookStore использует контекст данных для извлечения информации о странице из базы данных. Действие же прокси-класса отличается. Прокси определяет дополнительный объект - список pages. При получении страницы прокси сначала смотрит в этот список, и если там страницы не окажется, то идет обращение к реальному объекту BookStore и его методу. То есть фактически будет реализована функциональность кэша страниц.
Клиент, в роли которого в данном случае выступает класс Program, вообще не будет знать, использует ли он функционал класса BookStore или его прокси.
В то же время паттерн Прокси имеет недостаток: поскольку иногда будет выполняться сначала функционал прокси, а потом функционал реального объекта, например, если страницы не окажется в списке-кэше, то это может привести к замедлению выполнения программы.