Чем отличаются шаблоны Proxy, Decorator, Adapter и Bridge?


403

Я смотрел на Proxy Pattern, и мне он кажется очень похожим на паттерны Decorator, Adapter и Bridge. Я что-то неправильно понимаю? Какая разница? Зачем мне использовать шаблон Proxy по сравнению с другими? Как вы использовали их в прошлом в реальных проектах?


4
Часто существуют модели, которые выглядят очень похожими, но различаются по своему предназначению (на ум приходят стратегии и модели состояния). Я думаю, что это часто связано с тем, что шаблоны проектирования основаны на общих основательных принципах проектирования.
Джейсон Даун

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

Ответы:


648

Прокси, Декоратор, Адаптер и Мост - все это варианты «обёртывания» класса. Но их использование отличается.

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

  • Декоратор также называется «Smart Proxy». Это используется, когда вы хотите добавить функциональность к объекту, но не расширять тип этого объекта. Это позволяет вам делать это во время выполнения.

  • Адаптер используется, когда у вас есть абстрактный интерфейс, и вы хотите отобразить этот интерфейс на другой объект, который имеет аналогичную функциональную роль, но другой интерфейс.

  • Bridge очень похож на Adapter, но мы называем его Bridge, когда вы определяете как абстрактный интерфейс, так и базовую реализацию. Т.е. вы не адаптируетесь к какому-либо унаследованному или стороннему коду, вы являетесь разработчиком всего кода, но вам нужно иметь возможность менять различные реализации.

  • Фасад - это высокоуровневый (читай: более простой) интерфейс для подсистемы одного или нескольких классов. Предположим, у вас есть сложная концепция, которая требует представления нескольких объектов. Внесение изменений в этот набор объектов сбивает с толку, потому что вы не всегда знаете, какой объект имеет метод, который вам нужно вызвать. Настало время написать Facade, который предоставляет высокоуровневые методы для всех сложных операций, которые вы можете выполнять с коллекцией объектов. Пример: Модель предметной области для школьной секции, с помощью методов , таких как countStudents(), reportAttendance(), assignSubstituteTeacher(), и так далее.


7
Хороший ответ. Может быть, стоит добавить несколько примеров того, где вы видите это в дикой природе? например, прокси-классы в веб-сервисах. +1 от меня.
Роб Купер

5
@Rob: спасибо, но я бы предпочел, чтобы этот ответ был коротким и приятным. Я призываю вас написать еще один ответ с примерами в дикой природе!
Билл Карвин

8
@RobertDailey Декоратор также хорош, чтобы избежать неконтролируемых иерархий типов. Например , скажем, у вас есть окно в графическом интерфейсе, и вы хотите иметь дополнительные полосы прокрутки. Вы можете иметь классы Window, VScrollWindow, HScrollWindow и VHScrollWindow или создавать декораторы VScroll и HScroll для Window.
Ева

1
@RobertDailey, Декоратор это композиция.
Билл Карвин

1
А что, если вы хотите продублировать интерфейс обернутого объекта 1: 1, но затем добавить несколько дополнительных методов? Это декоратор или адаптер?
Donquixote

198

Как говорится в ответе Билла, их варианты использования различны .

Как и их структуры.

  • Прокси и Декоратор имеют тот же интерфейс, что и их обернутые типы, но прокси создает экземпляр под капотом, тогда как декоратор берет экземпляр в конструкторе.

  • Adapter и Facade имеют интерфейс, отличный от того, что они обертывают. Но адаптер происходит от существующего интерфейса, тогда как фасад создает новый интерфейс.

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


30
Ваш ответ в сочетании с Биллом очень красиво завершает 5 глав. Их можно назвать интерфейсом к книге более высокого уровня (читай: проще).
Джонас Эйкер

54

Мой взгляд на предмет.

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

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

адаптер

Адаптер адаптирует предмет (adaptee) к другому интерфейсу. Таким образом, мы можем добавить объект в коллекцию номинально разных типов.

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

Адаптеры защищают одну команду от изменчивого кода других команд; инструмент спасения жизни при работе с оффшорными командами ;-)

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

Адаптер помогает обойти ограничение Java только одного наследования. Он может объединять несколько адаптеров в одном конверте, создавая впечатление множественного наследования.

Код мудрый, адаптер «тонкий». Он не должен добавлять много кода к классу adaptee, кроме простого вызова метода adaptee и случайных преобразований данных, необходимых для таких вызовов.

В JDK или базовых библиотеках не так много хороших примеров адаптеров. Разработчики приложений создают адаптеры, чтобы адаптировать библиотеки к интерфейсам приложений.

декоратор

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

Декораторы обычно добавляют (прозрачно) функциональность к обернутому объекту, такую ​​как регистрация, шифрование, форматирование или сжатие в тему. Эта новая функциональность может принести много нового кода. Следовательно, декораторы обычно намного «толще», чем адаптеры.

Декоратор должен быть подклассом интерфейса субъекта. Их можно использовать прозрачно вместо своих предметов. Посмотрите BufferedOutputStream, это все еще OutputStream и может использоваться как таковой. Это основное техническое отличие от адаптеров.

Примеры учебников целого семейства декораторов легко в JDK - Java IO. Все классы , как BufferedOutputStream , FilterOutputStream и ObjectOutputStream являются декораторы OutputStream . Они могут быть наслоены луком, где один декоратор украшен снова, добавляя больше функциональности.

полномочие

Прокси не типичная оболочка. Обернутый объект, субъект прокси, может еще не существовать во время создания прокси. Прокси часто создает его внутри. Это может быть тяжелый объект, созданный по требованию, или это удаленный объект в другой JVM или другом сетевом узле и даже не-Java-объект, компонент в собственном коде. Он вообще не должен оборачиваться или делегироваться другому объекту.

Наиболее типичными примерами являются удаленные прокси, инициализаторы тяжелых объектов и прокси доступа.

  • Удаленный прокси - субъект находится на удаленном сервере, другой JVM или даже не Java-системе. Прокси переводит вызовы методов в вызовы RMI / REST / SOAP или что угодно, защищая клиента от воздействия базовой технологии.

  • Lazy Load Proxy - полностью инициализировать объект только при первом или первом интенсивном использовании.

  • Access Proxy - контроль доступа к теме.

Фасад

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

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

Мост

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

Отличия в конструкторах

Различия в шаблонах также очевидны при взгляде на их конструкторы.

  • Прокси не оборачивает существующий объект. В конструкторе нет темы.

  • Декоратор и Адаптер обертывают уже существующий объект, и это обычно
    предоставляется в конструкторе.

  • Конструктор фасада берет корневой элемент целого графа объекта, в противном случае он выглядит так же, как Adapter.

Пример из реальной жизни - JAXB Marshalling Adapter . Назначение этого адаптера - сопоставление простого плоского класса с более сложной структурой, необходимой извне, и для предотвращения «загрязнения» предметного класса чрезмерными аннотациями.


30

Существует много совпадений во многих шаблонах GoF. Все они построены на силе полиморфизма и иногда действительно различаются только намерениями. (стратегия против государства)

Мое понимание шаблонов увеличилось в 100 раз после прочтения Head First Design Patterns .

Я очень рекомендую это!


9

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

Я украслю ключевые моменты.

Decorator:

  1. Добавить поведение к объекту во время выполнения . Наследование является ключом для достижения этой функциональности, что является как преимуществом, так и недостатком этого шаблона.
  2. Это изменяет поведение интерфейса.

например (с цепочкой): java.ioклассы пакетов, связанные сInputStream & OutputStreamинтерфейсам

FileOutputStream fos1 = new FileOutputStream("data1.txt");  
ObjectOutputStream out1 = new ObjectOutputStream(fos1);

Прокси-сервер:

  1. Используйте его для отложенной инициализации, повышения производительности за счет кэширования объекта и контроля доступа к клиенту / вызывающей стороне . Это может обеспечить альтернативное поведение или вызвать реальный объект. Во время этого процесса он может создать новый объект.
  2. В отличие от декоратора , который позволяет связывать объекты, Proxy не позволяет связывать.

например: java.rmi пакеты классов.

адаптер:

  1. Это позволяет двум несвязанным интерфейсам работать вместе через разные объекты , возможно, играя одну и ту же роль.
  2. Это изменяет оригинальный интерфейс .

например java.io.InputStreamReader( InputStreamвозвращает Reader)

мост:

  1. Это позволяет абстракциям и реализациям варьироваться независимо .
  2. Он использует композицию над наследованием .

например, коллекции классов в java.util. ListосуществляетсяArrayList .

Ключевые примечания:

  1. Адаптер предоставляет другой интерфейс для своей темы. Прокси предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.
  2. Адаптер изменяет интерфейс объекта, Decorator расширяет обязанности объекта.
  3. Декоратор и Прокси имеют разные цели, но схожие структуры
  4. Адаптер заставляет вещи работать после того, как они разработаны; Бридж заставляет их работать раньше, чем они.
  5. Мост спроектирован заранее, чтобы позволить абстракции и реализации варьироваться независимо. Адаптер модернизирован для совместной работы несвязанных классов
  6. Декоратор разработан, чтобы позволить вам добавлять обязанности к объектам без создания подклассов.

Посмотрите на отличные вопросы / статьи по SE, касающиеся примеров различных шаблонов дизайна.

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

Когда вы используете шаблон моста? Чем он отличается от шаблона адаптера?

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


8

Они очень похожи, а линии между ними довольно серые. Я предлагаю вам прочитать записи « Прокси шаблон» и « Шаблон декоратора» в вики c2.

Записи и обсуждения там довольно обширны, и они также ссылаются на другие соответствующие статьи. Кстати, c2 wiki отлично подходит для размышлений о нюансах между различными шаблонами.

Чтобы суммировать записи c2, я бы сказал, что декоратор добавляет / изменяет поведение, но прокси имеет больше общего с контролем доступа (ленивая реализация, удаленный доступ, безопасность и т. Д.). Но, как я уже сказал, линии между ними серые, и я вижу ссылки на прокси, которые можно легко рассматривать как декораторы, и наоборот.


4

Все четыре шаблона предполагают наложение внутреннего объекта / класса на внешний, поэтому они очень похожи по структуре. Я бы обозначил разницу по цели:

  • Прокси инкапсулирует доступ снаружи внутрь.
  • Декоратор изменяет или расширяет поведение внутреннего с внешним.
  • адаптер преобразует интерфейс из внутреннего в внешний.
  • Bridge отделяет неизменную часть поведения (внешнюю) от переменной или зависимой от платформы части (внутренней).

И путем изменения интерфейса между внутренними и внешними объектами:

  • в прокси интерфейсы одинаковые.
  • в Decorator интерфейсы одинаковые.
  • В адаптере интерфейсы формально отличаются, но выполняют одну и ту же цель.
  • В Bridge интерфейсы разные концептуально.

4

Это цитата из Head First Design Patterns

Определения принадлежит книге. Примеры принадлежат мне.

Декоратор - не изменяет интерфейс, но добавляет ответственность. Предположим, у вас есть автомобильный интерфейс, когда вы реализуете это для другой модели автомобиля (s, sv, sl), вам может потребоваться добавить больше ответственности для некоторых моделей. Как есть люк, подушка безопасности и т.д ..

Адаптер - конвертирует один интерфейс в другой. У вас есть автомобильный интерфейс, и вы хотели бы, чтобы он действовал как джип. Итак, вы берете машину, модифицируете ее и превращаетесь в джип. Так как это не настоящий джип. Но действует как джип.

Фасад - упрощает интерфейс. Предположим, у вас есть интерфейс автомобиля, самолета, корабля. На самом деле все, что вам нужно, это класс, который отправляет людей из одного места в другое. Вы хотите, чтобы фасад решал, какой автомобиль использовать. Затем вы собираете все эти ссылки на интерфейсы под 1 зонтиком и позволяете ему решить / делегировать для простоты.

Глава первая: «Фасад не только упрощает интерфейс, он отделяет клиента от подсистемы компонентов. Фасады и адаптеры могут объединять несколько классов, но цель фасада состоит в том, чтобы упростить, а адаптер - преобразовать интерфейс во что-то другое. "


1

Я использую его довольно часто при использовании веб-сервисов. Шаблон Proxy, вероятно, следует переименовать во что-то более прагматичное, например, «Шаблон Wrapper». У меня также есть библиотека, которая является прокси-сервером для MS Excel. Это позволяет очень легко автоматизировать Excel, не беспокоясь о таких деталях, как версия установлена ​​(если есть).


Разве это не просто шаблон адаптера?
Чарльз Грэхем

1
Веб-служба используется прокси-сервером, а шаблон адаптера используется в большей степени для преобразования или перевода данных из одной формы в другую.
hmcclungiii

1

Говоря подробно о реализации, я нахожу разницу между Proxy и Decorator, Adapter, Facade ... В обычной реализации этих шаблонов есть целевой объект, обернутый вмещающим объектом. Клиент использует включающий объект вместо целевого объекта. И целевой объект на самом деле играет важную роль в некоторых методах включения объекта.

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

Другое дело, что прокси делает именно то, что делает цель, тогда как другие шаблоны добавляют больше функциональности к цели.


1

Я хотел бы добавить примеры к ответу Билла Карвинга (кстати, это здорово). Я также добавил некоторые ключевые отличия в реализации, которые, по моему мнению, отсутствуют

Цитируемые части взяты из ответа [ https://stackoverflow.com/a/350471/1984346] (Билл Карвинг)

Прокси, Декоратор, Адаптер и Мост - все это варианты «обёртывания» класса. Но их использование отличается.

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

ProxyClass и ObjectClass, которые проксируются, должны реализовывать один и тот же интерфейс, поэтому они взаимозаменяемы

Пример - прокси дорогой объект

class ProxyHumanGenome implements GenomeInterface  {
    private $humanGenome = NULL; 

    // humanGenome class is not instantiated at construct time
    function __construct() {
    }

    function getGenomeCount() {
        if (NULL == $this->humanGenome) {
            $this->instantiateGenomeClass(); 
        }
        return $this->humanGenome->getGenomeCount();
    }
} 
class HumanGenome implement GenomeInterface { ... }
  • Декоратор также называется «Smart Proxy». Это используется, когда вы хотите добавить функциональность к объекту, но не расширять тип этого объекта. Это позволяет вам делать это во время выполнения.

DecoratorClass должен (мог) реализовывать расширенный интерфейс ObjectClass. Таким образом, ObjectClass может быть заменен DecoratorClass, но не наоборот.

Пример - добавление дополнительных функций

class DecoratorHumanGenome implements CheckGenomeInterface  {

    // ... same code as previous example

    // added functionality
    public function isComplete() {
        $this->humanGenome->getCount >= 21000
    }
}

interface CheckGenomeInterface extends GenomeInterface {

    public function isComplete();

}

class HumanGenome implement GenomeInterface { ... }
  • Адаптер используется, когда у вас есть абстрактный интерфейс, и вы хотите отобразить этот интерфейс на другой объект, который имеет аналогичную функциональную роль, но другой интерфейс.

Различия в имплантации Прокси, Декоратор, Адаптер

Адаптер предоставляет другой интерфейс для своей темы. Прокси предоставляет тот же интерфейс. Декоратор предоставляет расширенный интерфейс.

  • Bridge очень похож на Adapter, но мы называем его Bridge, когда вы определяете как абстрактный интерфейс, так и базовую реализацию. Т.е. вы не адаптируетесь к какому-либо унаследованному или стороннему коду, вы являетесь разработчиком всего кода, но вам нужно иметь возможность менять различные реализации.

  • Фасад - это высокоуровневый (читай: более простой) интерфейс для подсистемы одного или нескольких классов. Предположим, у вас есть сложная концепция, которая требует представления нескольких объектов. Внесение изменений в этот набор объектов сбивает с толку, потому что вы не всегда знаете, какой объект имеет метод, который вам нужно вызвать. Настало время написать Facade, который предоставляет высокоуровневые методы для всех сложных операций, которые вы можете выполнять с коллекцией объектов. Пример: Модель предметной области для школьной секции, с помощью методов , таких как countStudents(), reportAttendance(), assignSubstituteTeacher(), и так далее.

Большая часть информации в этом ответе взята с https://sourcemaking.com/design_patterns , которую я рекомендую как отличный ресурс для шаблонов проектирования.


0

Я верю, что код даст ясные идеи (чтобы дополнить ответы других). Пожалуйста, смотрите ниже, (Сосредоточьтесь на типах, которые класс реализует и переносит)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TestConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            /* Proxy */

            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("PROXY");
            Console.WriteLine(Environment.NewLine);

            //instead of creating here create using a factory method, the facory method will return the proxy
            IReal realProxy = new RealProxy();
            Console.WriteLine("calling do work with the proxy object ");
            realProxy.DoWork();

            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("ADAPTER");
            Console.WriteLine(Environment.NewLine);

            /*Adapter*/
            IInHand objectIHave = new InHand();
            Api myApi = new Api();
            //myApi.SomeApi(objectIHave); /*I cant do this, use a adapter then */
            IActual myAdaptedObject = new ActualAdapterForInHand(objectIHave);
            Console.WriteLine("calling api with  my adapted obj");
            myApi.SomeApi(myAdaptedObject);


            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("DECORATOR");
            Console.WriteLine(Environment.NewLine);

            /*Decorator*/
            IReady maleReady = new Male();
            Console.WriteLine("now male is going to get ready himself");
            maleReady.GetReady();

            Console.WriteLine(Environment.NewLine);

            IReady femaleReady = new Female();
            Console.WriteLine("now female is going to get ready her self");
            femaleReady.GetReady();

            Console.WriteLine(Environment.NewLine);

            IReady maleReadyByBeautician = new Beautician(maleReady);
            Console.WriteLine("now male is going to get ready by beautician");
            maleReadyByBeautician.GetReady();

            Console.WriteLine(Environment.NewLine);

            IReady femaleReadyByBeautician = new Beautician(femaleReady);
            Console.WriteLine("now female is going to get ready by beautician");
            femaleReadyByBeautician.GetReady();

            Console.WriteLine(Environment.NewLine);

            Console.ReadLine();


        }
    }

    /*Proxy*/

    public interface IReal
    {
        void DoWork();
    }

    public class Real : IReal
    {
        public void DoWork()
        {
            Console.WriteLine("real is doing work ");
        }
    }


    public class RealProxy : IReal
    {
        IReal real = new Real();

        public void DoWork()
        {
            real.DoWork();
        }
    }

    /*Adapter*/

    public interface IActual
    {
        void DoWork();
    }

    public class Api
    {
        public void SomeApi(IActual actual)
        {
            actual.DoWork();
        }
    }

    public interface IInHand
    {
        void DoWorkDifferently();
    }

    public class InHand : IInHand
    {
        public void DoWorkDifferently()
        {
            Console.WriteLine("doing work slightly different ");
        }
    }

    public class ActualAdapterForInHand : IActual
    {
        IInHand hand = null;

        public ActualAdapterForInHand()
        {
            hand = new InHand();
        }

        public ActualAdapterForInHand(IInHand hnd)
        {
            hand = hnd;
        }

        public void DoWork()
        {
            hand.DoWorkDifferently();
        }
    }

    /*Decorator*/

    public interface IReady
    {
        void GetReady();
    }

    public class Male : IReady
    {
        public void GetReady()
        {
            Console.WriteLine("Taking bath.. ");
            Console.WriteLine("Dress up....");
        }
    }

    public class Female : IReady
    {
        public void GetReady()
        {
            Console.WriteLine("Taking bath.. ");
            Console.WriteLine("Dress up....");
            Console.WriteLine("Make up....");
        }
    }

    //this is a decorator
    public class Beautician : IReady
    {
        IReady ready = null;

        public Beautician(IReady rdy)
        {
            ready = rdy;
        }

        public void GetReady()
        {
            ready.GetReady();
            Console.WriteLine("Style hair ");

            if (ready is Female)
            {
                for (int i = 1; i <= 10; i++)
                {
                    Console.WriteLine("doing ready process " + i);
                }

            }
        }
    }

}

-3

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

Следовательно, сконцентрируйтесь больше на основополагающих принципах дизайна, принципах чистого кодирования и ттд


Согласитесь, хотя это не ответ на вопрос.
Леон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.