Заместитель (Proxy)

Паттерн Заместитель (Proxy) предоставляет объект-заместитель, который управляет доступом к другому объекту. То есть создается объект-суррогат, который может выступать в роли другого объекта и замещать его.

Когда использовать прокси?

  • Когда надо осуществлять взаимодействие по сети, а объект-проси должен имитировать поведения объекта в другом адресном пространстве. Использование прокси позволяет снизить накладные издержки при передачи данных через сеть. Подобная ситуация еще называется удалённый заместитель (remote proxies)
  • Когда нужно управлять доступом к ресурсу, создание которого требует больших затрат. Реальный объект создается только тогда, когда он действительно может понадобится, а до этого все запросы к нему обрабатывает прокси-объект. Подобная ситуация еще называется виртуальный заместитель (virtual proxies)
  • Когда необходимо разграничить доступ к вызываемому объекту в зависимости от прав вызывающего объекта. Подобная ситуация еще называется защищающий заместитель (protection proxies)
  • Когда нужно вести подсчет ссылок на объект или обеспечить потокобезопасную работу с реальным объектом. Подобная ситуация называется «умные ссылки» (smart reference)

С помощью UML паттерн может быть описан так:

Паттерн Заместитель или Прокси в C# и .NET

На 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 или его прокси.

В то же время паттерн Прокси имеет недостаток: поскольку иногда будет выполняться сначала функционал прокси, а потом функционал реального объекта, например, если страницы не окажется в списке-кэше, то это может привести к замедлению выполнения программы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *