Различия между прокси и шаблоном декоратора
Можете ли вы дать хорошее объяснение, в чем разница между прокси и декоратором?
Основное различие, которое я вижу, состоит в том, что когда мы предполагаем, что Прокси использует композицию, а Декоратор использует агрегирование, тогда становится очевидным, что с помощью нескольких (одного или нескольких) Декораторов вы можете изменять / добавлять функциональные возможности в уже существующий экземпляр (декорация), тогда как Прокси имеет собственный внутренний экземпляр прокси-класса и делегирует ему добавление некоторых дополнительных функций (поведение прокси).
Вопрос в том, является ли Proxy, созданный с агрегацией, все еще Proxy или, вернее, Decorator? Разрешено ли (по определению в шаблонах GoF) создавать прокси с агрегацией?
10 ответов
ШаблонDecorator ориентирован на динамическое добавление функций к объекту, а шаблон Proxy - на контроль доступа к объекту.
РЕДАКТИРОВАТЬ:-
Отношения между прокси и реальным субъектом обычно устанавливаются во время компиляции, прокси создает его каким-то образом, тогда как Decorator назначается субъекту во время выполнения, зная только интерфейс субъекта.
Принятый ответ не совсем корректен. Реальная разница не в принадлежности (состав против агрегации), а в информации о типе.
Декоратору всегда передается его делегат. Прокси-сервер может создать его сам, или он может сделать это.
Но Прокси всегда знает (более) конкретный тип делегата. Другими словами, Прокси и его делегат будут иметь одинаковый базовый тип, но Прокси указывает на некоторый производный тип. Декоратор указывает на свой собственный базовый тип. Таким образом, разница заключается во времени компиляции информации о типе делегата.
В динамическом языке, если делегат вводится и имеет тот же интерфейс, то нет никакой разницы.
Ответ на ваш вопрос - "Да".
Вот прямая цитата из GoF (стр. 216).
Хотя декораторы могут иметь ту же реализацию, что и прокси, у декораторов другое назначение. Декоратор добавляет одну или несколько обязанностей к объекту, тогда как прокси контролирует доступ к объекту.
Прокси-серверы различаются по степени их реализации как декоратора. Прокси-сервер защиты может быть реализован точно так же, как декоратор. С другой стороны, удаленный прокси-сервер не будет содержать прямой ссылки на его реальный объект, а будет содержать только косвенную ссылку, такую как "идентификатор хоста и локальный адрес на хосте". Виртуальный прокси начинается с косвенной ссылки, такой как имя файла, но в конечном итоге получает и использует прямую ссылку.
Популярные ответы показывают, что Прокси-сервер знает конкретный тип своего делегата. Из этой цитаты мы видим, что это не всегда так.
Разница между Proxy и Decorator согласно GoF в том, что Proxy ограничивает клиента. Декоратора нет. Proxy может ограничить то, что клиент делает, контролируя доступ к функциональным возможностям; или он может ограничить то, что клиент знает, выполняя действия, которые невидимы и неизвестны клиенту. Decorator делает обратное: он улучшает то, что делает его делегат, так, чтобы это было видно клиентам.
Можно сказать, что Proxy - это черный ящик, а Decorator - это белый ящик.
Отношения композиции между оболочкой и делегатом - это неправильное отношение, на котором следует сосредоточиться при сравнении прокси с декоратором, потому что композиция - это общая черта этих двух шаблонов. Отношения между оболочкой и клиентом - вот что отличает эти два шаблона.
- Decorator информирует и уполномочивает своего клиента.
- Прокси-сервер ограничивает и лишает возможности своего клиента.
Декоратор получает ссылку на оформленный объект (обычно через конструктор), в то время как Прокси отвечает за это самостоятельно.
Прокси может вообще не создавать экземпляр объекта-обёртки (как это делают ORM для предотвращения ненужного доступа к БД, если поля / методы получения объекта не используются), в то время как Decorator всегда держит ссылку на фактический обернутый экземпляр.
Прокси-сервер обычно используется фреймворками для добавления безопасности или кэширования / генерации данных и создается фреймворком (не самим обычным разработчиком).
Декоратор обычно используется для добавления нового поведения к старым или унаследованным классам самим разработчиком на основе интерфейса, а не фактического класса (поэтому он работает с широким диапазоном экземпляров интерфейса, Proxy работает вокруг конкретного класса).
Ключевые отличия:
- Прокси предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.
- Декоратор и Прокси имеют разные цели, но схожие структуры. Оба описывают, как обеспечить уровень косвенности для другого объекта, и реализации сохраняют ссылку на объект, к которому они направляют запросы.
- Декоратор может рассматриваться как вырожденный композит с одним компонентом. Однако декоратор добавляет дополнительные обязанности - он не предназначен для агрегирования объектов.
- Декоратор поддерживает рекурсивную композицию
- Класс Decorator объявляет композиционные отношения с интерфейсом LCD (самый низкий знаменатель класса), и этот элемент данных инициализируется в своем конструкторе.
- Используйте Proxy для отложенной инициализации, повышения производительности за счет кэширования объекта и контроля доступа к клиенту / вызывающей стороне.
Sourcemaking статья цитирует сходства и различия в отличной форме.
Связанные вопросы SE / ссылки:
Потребовалось время, чтобы понять этот ответ и то, что он на самом деле означает. Несколько примеров должны прояснить ситуацию.
Proxy
первый:
public interface Authorization {
String getToken();
}
А также:
// goes to the DB and gets a token for example
public class DBAuthorization implements Authorization {
@Override
public String getToken() {
return "DB-Token";
}
}
И есть звонящий из этого Authorization
, довольно тупой:
class Caller {
void authenticatedUserAction(Authorization authorization) {
System.out.println("doing some action with : " + authorization.getToken());
}
}
Пока что ничего необычного, правда? Получите токен от определенной службы, используйте этот токен. Теперь появляется еще одно требование к картинке, добавление регистрации: это означает, что каждый раз регистрировать токен. В этом случае все просто, просто создайтеProxy
:
public class LoggingDBAuthorization implements Authorization {
private final DBAuthorization dbAuthorization = new DBAuthorization();
@Override
public String getToken() {
String token = dbAuthorization.getToken();
System.out.println("Got token : " + token);
return token;
}
}
Как нам это использовать?
public static void main(String[] args) {
LoggingDBAuthorization loggingDBAuthorization = new LoggingDBAuthorization();
Caller caller = new Caller();
caller.authenticatedUserAction(loggingDBAuthorization);
}
Заметить, что LoggingDBAuthorization
содержит экземплярDBAuthorization
. ОбеLoggingDBAuthorization
а также DBAuthorization
осуществлять Authorization
.
- Прокси-сервер будет содержать конкретную реализацию (
DBAuthorization
) базового интерфейса (Authorization
). Другими словами, Прокси-сервер точно знает , что проксируется.
Decorator
:
Он начинается примерно так же, как Proxy
, с интерфейсом:
public interface JobSeeker {
int interviewScore();
}
и его реализация:
class Newbie implements JobSeeker {
@Override
public int interviewScore() {
return 10;
}
}
А теперь мы хотим добавить более опытного кандидата, который добавляет свой балл на собеседовании плюс баллы другого. JobSeeker
:
@RequiredArgsConstructor
public class TwoYearsInTheIndustry implements JobSeeker {
private final JobSeeker jobSeeker;
@Override
public int interviewScore() {
return jobSeeker.interviewScore() + 20;
}
}
Обратите внимание, как я сказал это плюс одно от другого ищущего работу, а не Newbie
. АDecorator
не знает точно, что он украшает, он знает только контракт этого украшенного экземпляра (он знает оJobSeeker
). Обратите внимание, что это не похоже наProxy
; который, напротив, точно знает, что украшает.
Вы можете спросить, есть ли на самом деле разница между двумя шаблонами проектирования в этом случае? Что, если бы мы попытались написатьDecorator
как Proxy
?
public class TwoYearsInTheIndustry implements JobSeeker {
private final Newbie newbie = new Newbie();
@Override
public int interviewScore() {
return newbie.interviewScore() + 20;
}
}
Это определенно вариант, который подчеркивает, насколько близки эти модели; они по-прежнему предназначены для разных сценариев, как описано в других ответах.
Прокси и Декоратор различаются по назначению и тому, где они сосредоточены на внутренней реализации. Прокси-сервер предназначен для использования удаленного, межпроцессного или межсетевого объекта, как если бы это был локальный объект. Декоратор предназначен для добавления нового поведения в исходный интерфейс.
Хотя оба шаблона схожи по структуре, основная сложность Proxy заключается в обеспечении правильной связи с исходным объектом. Декоратор, с другой стороны, фокусируется на реализации добавленного поведения.
A добавляет дополнительную ответственность объекту, в то время как прокси-сервер контролирует доступ к объекту, они оба используют композицию. Если ваш класс-оболочка не соответствует теме, очевидно, что это прокси. Позвольте мне объяснить на примере кода на PHP:
Пример кода
Дано следующее
CarRepository
:
interface CarRepositoryInterface
{
public function getById(int $id) : Car
}
class CarRepository implements CarRepositoryInterface
{
public function getById(int $id) : Car
{
sleep(3); //... fake some heavy db call
$car = new Car;
$car->setId($id);
$car->setName("Mercedes Benz");
return $car;
}
}
Авторепозиторий-
А
Proxy
часто используется как отложенная загрузка или прокси-сервер кеширования:
class CarRepositoryCacheProxy implements CarRepositoryInterface
{
private $carRepository;
private function getSubject() : CarRepositoryInterface
{
if($this->carRepository == null) {
$this->carRepository = new CarRepository();
}
return $this->carRepository;
}
/**
* This method controls the access to the subject
* based on if there is cache available
*/
public function getById(int $id) : Car
{
if($this->hasCache(__METHOD__)) {
return unserialize($this->getCache(__METHOD__));
}
$response = $this->getSubject()->getById($id);
$this->writeCache(__METHOD__, serialize($response));
return $response;
}
private function hasCache(string $key) : bool
{
//... implementation
}
private function getCache(string $key) : string
{
//... implementation
}
private function writeCache(string $key, string $result) : string
{
//... implementation
}
}
Авторепозиторий-
А
Decorator
можно использовать до тех пор, пока добавленное поведение не «контролирует» субъект:
class CarRepositoryEventManagerDecorator implements CarRepositoryInterface
{
private $subject, $eventManager;
/**
* Subjects in decorators are often passed in the constructor,
* where a proxy often takes control over the invocation behavior
* somewhere else
*/
public function __construct(CarRepositoryInterface $subject, EventManager $eventManager)
{
$this->subject = $subject;
$this->eventManager = $eventManager;
}
public function getById(int $id) : Car
{
$this->eventManager->trigger("pre.getById");
//this method takes no control over the subject
$result = $this->subject->getById($id);
$this->eventManager->trigger("post.getById");
return $result;
}
}
Прокси предоставляет тот же интерфейс для обернутого объекта, Decorator предоставляет ему расширенный интерфейс, а Proxy обычно самостоятельно управляет жизненным циклом своего объекта службы, тогда как состав декораторов всегда контролируется клиентом.
Позвольте мне сначала объяснить закономерности, а затем перейти к вашим вопросам.
Судя по диаграмме классов и значениям, они очень похожи:
- Оба имеют тот же интерфейс, что и его делегат.
- Оба добавляют / улучшают поведение своего делегата.
- Оба просят делегата выполнить операции (не должны работать с нулевым делегатом).
Но у них есть разница:
Различные намерения: Прокси-сервер улучшает поведение делегата (переданного объекта) с совершенно другими знаниями предметной области от его делегата. Например, прокси-сервер безопасности добавляет контроль безопасности делегата. Прокси-сервер для отправки удаленного сообщения должен сериализовать / десериализовать данные и иметь знания о сетевом интерфейсе, но не имеет ничего общего с тем, как подготовить исходные данные. Декоратор помогает в той же проблемной области, над которой работает делегат. Например, BufferedInputStreaman(декоратор ввода-вывода) работает с вводом, который является той же проблемной областью (ввод-вывод), что и его делегат, но он не может работать без делегата, который предоставляет данные ввода-вывода.
Зависимость сильная или нет: Decorator полагается на делегата для завершения поведения, и он не может завершить поведение без делегата (Strong). Таким образом, мы всегда используем агрессию, а не композицию. Прокси-сервер может выполнять ложное поведение, даже если ему не нужен делегат (слабый). Например, mockito(фреймворк модульного тестирования) может имитировать / шпионить за поведением только с помощью своего интерфейса. Таким образом, мы используем композицию, чтобы указать, что нет сильной зависимости от реального объекта.
Многократное улучшение (как упоминалось в вопросе): Прокси: мы могли бы использовать прокси для обертывания реального объекта один раз, а не несколько раз. Декоратор: декоратор может обернуть реальный объект несколько раз или может обернуть объект, который уже обернут декоратором (который может быть как другим декоратором, так и тем же декоратором). Например, для системы заказов у декораторов есть скидка.PercentageDiscountDecorator должен сократить 50%, а DeductionAmountDiscountDecorator должен вычесть 5 долларов напрямую, если сумма превышает 10 долларов (). Таким образом, 1). Если вы хотите сократить 50% и вычесть 5 долларов, вы можете сделать: новый DeductionAmountDiscountDecorator(новый PercentageDiscountDecorator(делегат))2). Если вы хотите вычесть 10 долларов, вы можете сделать новый DeductionAmountDiscountDecorator(новый DeductionAmountDiscountDecorator(делегат)).
Ответ на вопрос не имеет ничего общего с разницей между Proxy и Decorator. Почему?
- Шаблоны проектирования - это просто шаблоны для людей, не обладающих навыками объектно-ориентированного проектирования, для использования объектно-ориентированных решений. Если вы знакомы с объектно-ориентированным дизайном, вам не нужно знать, сколько здесь шаблонов проектирования (до того, как шаблоны проектирования были изобретены, те же самые опытные специалисты могли бы найти такое же решение).
- Нет двух одинаковых листьев, как и проблемы, которые вы видите. Люди всегда обнаруживают, что их проблемы отличаются от проблем, создаваемых шаблонами проектирования.
Если указанная вами проблема действительно отличается от обеих задач, над которыми работают Proxy и Decorator, и действительно требует агрегирования, почему бы не использовать? Я думаю, что применить объектно-ориентированный подход к вашей проблеме гораздо важнее, чем вы пометите его как прокси или декоратор.