Различия между прокси и шаблоном декоратора

Можете ли вы дать хорошее объяснение, в чем разница между прокси и декоратором?

Основное различие, которое я вижу, состоит в том, что когда мы предполагаем, что Прокси использует композицию, а Декоратор использует агрегирование, тогда становится очевидным, что с помощью нескольких (одного или нескольких) Декораторов вы можете изменять / добавлять функциональные возможности в уже существующий экземпляр (декорация), тогда как Прокси имеет собственный внутренний экземпляр прокси-класса и делегирует ему добавление некоторых дополнительных функций (поведение прокси).

Вопрос в том, является ли Proxy, созданный с агрегацией, все еще Proxy или, вернее, Decorator? Разрешено ли (по определению в шаблонах GoF) создавать прокси с агрегацией?

10 ответов

Решение

ШаблонDecorator ориентирован на динамическое добавление функций к объекту, а шаблон Proxy - на контроль доступа к объекту.

РЕДАКТИРОВАТЬ:-

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

Принятый ответ не совсем корректен. Реальная разница не в принадлежности (состав против агрегации), а в информации о типе.

Декоратору всегда передается его делегат. Прокси-сервер может создать его сам, или он может сделать это.

Но Прокси всегда знает (более) конкретный тип делегата. Другими словами, Прокси и его делегат будут иметь одинаковый базовый тип, но Прокси указывает на некоторый производный тип. Декоратор указывает на свой собственный базовый тип. Таким образом, разница заключается во времени компиляции информации о типе делегата.

В динамическом языке, если делегат вводится и имеет тот же интерфейс, то нет никакой разницы.

Ответ на ваш вопрос - "Да".

Вот прямая цитата из GoF (стр. 216).

Хотя декораторы могут иметь ту же реализацию, что и прокси, у декораторов другое назначение. Декоратор добавляет одну или несколько обязанностей к объекту, тогда как прокси контролирует доступ к объекту.

Прокси-серверы различаются по степени их реализации как декоратора. Прокси-сервер защиты может быть реализован точно так же, как декоратор. С другой стороны, удаленный прокси-сервер не будет содержать прямой ссылки на его реальный объект, а будет содержать только косвенную ссылку, такую ​​как "идентификатор хоста и локальный адрес на хосте". Виртуальный прокси начинается с косвенной ссылки, такой как имя файла, но в конечном итоге получает и использует прямую ссылку.

Популярные ответы показывают, что Прокси-сервер знает конкретный тип своего делегата. Из этой цитаты мы видим, что это не всегда так.

Разница между Proxy и Decorator согласно GoF в том, что Proxy ограничивает клиента. Декоратора нет. Proxy может ограничить то, что клиент делает, контролируя доступ к функциональным возможностям; или он может ограничить то, что клиент знает, выполняя действия, которые невидимы и неизвестны клиенту. Decorator делает обратное: он улучшает то, что делает его делегат, так, чтобы это было видно клиентам.

Можно сказать, что Proxy - это черный ящик, а Decorator - это белый ящик.

Отношения композиции между оболочкой и делегатом - это неправильное отношение, на котором следует сосредоточиться при сравнении прокси с декоратором, потому что композиция - это общая черта этих двух шаблонов. Отношения между оболочкой и клиентом - вот что отличает эти два шаблона.

  • Decorator информирует и уполномочивает своего клиента.
  • Прокси-сервер ограничивает и лишает возможности своего клиента.

Декоратор получает ссылку на оформленный объект (обычно через конструктор), в то время как Прокси отвечает за это самостоятельно.

Прокси может вообще не создавать экземпляр объекта-обёртки (как это делают ORM для предотвращения ненужного доступа к БД, если поля / методы получения объекта не используются), в то время как Decorator всегда держит ссылку на фактический обернутый экземпляр.

Прокси-сервер обычно используется фреймворками для добавления безопасности или кэширования / генерации данных и создается фреймворком (не самим обычным разработчиком).

Декоратор обычно используется для добавления нового поведения к старым или унаследованным классам самим разработчиком на основе интерфейса, а не фактического класса (поэтому он работает с широким диапазоном экземпляров интерфейса, Proxy работает вокруг конкретного класса).

Ключевые отличия:

  1. Прокси предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.
  2. Декоратор и Прокси имеют разные цели, но схожие структуры. Оба описывают, как обеспечить уровень косвенности для другого объекта, и реализации сохраняют ссылку на объект, к которому они направляют запросы.
  3. Декоратор может рассматриваться как вырожденный композит с одним компонентом. Однако декоратор добавляет дополнительные обязанности - он не предназначен для агрегирования объектов.
  4. Декоратор поддерживает рекурсивную композицию
  5. Класс Decorator объявляет композиционные отношения с интерфейсом LCD (самый низкий знаменатель класса), и этот элемент данных инициализируется в своем конструкторе.
  6. Используйте 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 обычно самостоятельно управляет жизненным циклом своего объекта службы, тогда как состав декораторов всегда контролируется клиентом.

Позвольте мне сначала объяснить закономерности, а затем перейти к вашим вопросам.

Судя по диаграмме классов и значениям, они очень похожи:

  1. Оба имеют тот же интерфейс, что и его делегат.
  2. Оба добавляют / улучшают поведение своего делегата.
  3. Оба просят делегата выполнить операции (не должны работать с нулевым делегатом).

Но у них есть разница:

  1. Различные намерения: Прокси-сервер улучшает поведение делегата (переданного объекта) с совершенно другими знаниями предметной области от его делегата. Например, прокси-сервер безопасности добавляет контроль безопасности делегата. Прокси-сервер для отправки удаленного сообщения должен сериализовать / десериализовать данные и иметь знания о сетевом интерфейсе, но не имеет ничего общего с тем, как подготовить исходные данные. Декоратор помогает в той же проблемной области, над которой работает делегат. Например, BufferedInputStreaman(декоратор ввода-вывода) работает с вводом, который является той же проблемной областью (ввод-вывод), что и его делегат, но он не может работать без делегата, который предоставляет данные ввода-вывода.

  2. Зависимость сильная или нет: Decorator полагается на делегата для завершения поведения, и он не может завершить поведение без делегата (Strong). Таким образом, мы всегда используем агрессию, а не композицию. Прокси-сервер может выполнять ложное поведение, даже если ему не нужен делегат (слабый). Например, mockito(фреймворк модульного тестирования) может имитировать / шпионить за поведением только с помощью своего интерфейса. Таким образом, мы используем композицию, чтобы указать, что нет сильной зависимости от реального объекта.

  3. Многократное улучшение (как упоминалось в вопросе): Прокси: мы могли бы использовать прокси для обертывания реального объекта один раз, а не несколько раз. Декоратор: декоратор может обернуть реальный объект несколько раз или может обернуть объект, который уже обернут декоратором (который может быть как другим декоратором, так и тем же декоратором). Например, для системы заказов у ​​декораторов есть скидка.PercentageDiscountDecorator должен сократить 50%, а DeductionAmountDiscountDecorator должен вычесть 5 долларов напрямую, если сумма превышает 10 долларов (). Таким образом, 1). Если вы хотите сократить 50% и вычесть 5 долларов, вы можете сделать: новый DeductionAmountDiscountDecorator(новый PercentageDiscountDecorator(делегат))2). Если вы хотите вычесть 10 долларов, вы можете сделать новый DeductionAmountDiscountDecorator(новый DeductionAmountDiscountDecorator(делегат)).

Ответ на вопрос не имеет ничего общего с разницей между Proxy и Decorator. Почему?

  1. Шаблоны проектирования - это просто шаблоны для людей, не обладающих навыками объектно-ориентированного проектирования, для использования объектно-ориентированных решений. Если вы знакомы с объектно-ориентированным дизайном, вам не нужно знать, сколько здесь шаблонов проектирования (до того, как шаблоны проектирования были изобретены, те же самые опытные специалисты могли бы найти такое же решение).
  2. Нет двух одинаковых листьев, как и проблемы, которые вы видите. Люди всегда обнаруживают, что их проблемы отличаются от проблем, создаваемых шаблонами проектирования.

Если указанная вами проблема действительно отличается от обеих задач, над которыми работают Proxy и Decorator, и действительно требует агрегирования, почему бы не использовать? Я думаю, что применить объектно-ориентированный подход к вашей проблеме гораздо важнее, чем вы пометите его как прокси или декоратор.

Другие вопросы по тегам