Прокси, Декоратор и их отличия
Искусство есть посредник того, чего нельзя высказать.
Иоганн Вольфганг фон Гёте
Введение
Шаблоны проектирования - это важные инструменты для разработчиков программного обеспечения, позволяющие создавать многократно используемый и сопровождаемый код. Двумя популярными паттернами являются паттерн “Прокси” и паттерн “Декоратор”. Хотя эти два паттерна довольно похожи, у них разное применение и назначение. В этой статье мы рассмотрим различия между паттернами Proxy и Decorator.
Отличия Proxy от Decorator
Шаблон Proxy используется в качестве прослойки для другого объекта, чтобы контролировать доступ к нему. Прокси знает класс которому он принадлежит, обычно создает его сам и может использовать определенные методы этого класса, например, для подготовки данных.
В отличие от Proxy, паттерн Decorator используется для динамического добавления поведения к объекту без изменения его интерфейса. Декоратор работает только с декларируемым интерфейсом, то есть у него нет доступа к базовой реализации объекта.
Хотя эти паттерны имеют различные цели, их часто путают друг с другом. Люди часто не понимают различий между ними, что приводит к путанице.
Популярные причины путаницы:
Оба паттерна подразумевают оборачивание объекта в объект. В паттерне Decorator этот обертка добавляет новое поведение или функциональность исходному объекту, а в паттерне Proxy обертка действует как посредник для контроля доступа к исходному объекту. Такое сходство в структуре может затруднить разработчикам проведение различий между этими двумя паттернами.
Паттерн Decorator иногда называют “динамическим прокси”, что может еще больше размыть различия между Proxy и Decorator. Хотя они оба подразумевают добавление нового поведения к объекту, Decorator делает это, оборачивая объект одним или несколькими декораторами, в то время как Proxy предоставляет суррогат для исходного объекта.
Пример
Пример, включает интерфейс IValueProvider, который определяет одно свойство Value.
public interface IValueProvider
{
public string Value { get; }
}
SlowValueProvider - это реализация этого интерфейса, имитирующая источник данных с большой задержкой при получении значения, перед получением значения вызывается метод LoadData, который использует публичное свойство Url, его нет в наследуемом интерфейсе.
public class SlowValueProvider : IValueProvider
{
public string Url { get; set; } = "localhost:8080";
public string Value => LoadData();
private string LoadData()
{
Thread.Sleep(TimeSpan.FromMinutes(1));
return $"Hello world ({Url})";
}
}
Чтобы добиться увеличения производительности, вызванного SlowValueProvider, мы будем использовать кэширование для хранения результатов предыдущих запросов к свойству Value. Это значительно сократит время, необходимое для получения данных, в результате чего приложение станет более отзывчивым и эффективным.
Первая реализация - это кэширующий прокси. Кэширующий прокси - это объект, который находится между клиентом и реальным объектом и перехватывает обращения к объекту, кэшируя результаты этих обращений.
Вот пример того, как может быть реализован кэширующий прокси:
public class CacheProxyValueProvider : IValueProvider
{
private readonly Lazy<string> _lazyValueProvider;
public CacheProxyValueProvider(string url)
{
_lazyValueProvider = new Lazy<string>(() =>
{
var slowValueProvider = new SlowValueProvider
{
// Используем специфичный функционал
Url = url
};
return slowValueProvider.Value;
});
}
public string Value => _lazyValueProvider.Value;
}
В этом примере класс CacheProxyValueProvider реализует интерфейс IValueProvider и создаёт экземпляр SlowValueProvider, а в качестве аргумента конструктора принимает Url для загрузки данных. Так как наш Proxy знает конкретную реализацию IValueProvider, он может использовать специфичные свойства и методы для этого объекта, которых нет в интерфейсе. В нашем случае, мы можем менять Url, которого нет в интерфейсе.
Вторая реализация - это кэширующий декоратор. Он похож на прокси, но работает только с декорируемой абстракцией, в нашем случае он принимает фабрику по созданию IValueProvider.
Вот пример того, как может быть реализован декоратор:
public class CacheDecoratorValueProvider : IValueProvider
{
private readonly Lazy<string> _lazyValue;
public CacheDecoratorValueProvider(Func<IValueProvider> factory)
{
_lazyValue = new Lazy<string>(() =>
{
// Фабрика может возвращать всё что угодно (хоть прокси, хоть логгирующий декоратор или ещё что-то)
var provider = factory();
return provider.Value;
});
}
public string Value => _lazyValue.Value;
}
В этом примере класс CacheDecoratorValueProvider принимает фабрику по созданию IValueProvider и поэтому не может использовать особенности реализации, но он может принимать в себя всё что угодно, что реализует интерфейс IValueProvider, это в свою очередь значит, что это может быть описанный выше CacheProxyValueProvider или другие декораторы.
Итог
В заключение следует отметить, что хотя паттерны Proxy и Decorator имеют некоторые общие черты, у них разные случаи использования и реализации. Шаблон Proxy используется для контроля доступа к объекту, добавления функциональности или предоставления упрощенного интерфейса, а шаблон Decorator используется для динамического добавления поведения объекту без изменения его интерфейса.
Понимая различия между этими двумя паттернами, разработчики могут выбрать правильный паттерн для конкретного случая использования, что приведет к созданию более эффективного и удобного в обслуживании кода.