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

Случай использования шаблона Decorator для шаблона "Head First: Design Patterns" заставил меня задать этот вопрос. Я попытаюсь записать его:

Это кофейная система с кофе и много приправ вы можете ввести их (за дополнительную плату), вы должны иметь возможность заказать и взимать плату за кофе с любыми приправами, которые пожелает заказчик, и чтобы избежать полного хаоса (например, boolean, чтобы отслеживать приправы). Используется узор декоратора. У нас есть абстрактный напиток класс, каждый тип кофе в качестве конкретных компонентов и каждая приправа как бетонные декораторы, обертывающие напиток, например:

Beverage Class Diagram

Итак, у нас есть следующий процесс, возвращающий стоимость кофе:

Coffee Cost Delegation

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

// Using second image example
Beverage beverage = new DarkRoast(beverage);
beverage = new Mocha(beverage);
beverage = new Whip(beverage);

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

Ответ 1

Шаблон Decorator предназначен для украшения (улучшения) объекта дополнительными функциями во время выполнения.

Предположим, у вас уже есть класс, давайте назовем его классом A, который реализует интерфейс IA. Теперь, если есть требование добавить дополнительную функцию, для которой мы хотим, чтобы у нее был метод, someAlignFeatureToA(), которого не было в A. Теперь у вас есть возможность расширить класс A. Другой подход - Composition, который следует отдавать предпочтение перед Inheritance. Вы можете обернуть объект класса A в другой класс B с тем же Interface, что и A, т.е. IA. Таким образом, для клиентского кода было бы легко принять объект класса B, поскольку он имеет тот же интерфейс, что и у A. (Предполагается, что код хорошо написан, что зависит от абстракций (интерфейс IA), а не от конкретного классы, подобные классу A).

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

Теперь перейдем к вашему вопросу: Да, в примере, который вы взяли в своем вопросе, список подходит лучше, но я чувствую, что автор намерен объяснить использование шаблона Decorator на простом примере. Предположим, что кофе состоит из молока, кофейной смеси и сахара. Кофейная смесь состоит из более мелких компонентов. Здесь решение возникает аналогично составному шаблону.

Шаблон декоратора - это шаблон дизайна, основанный на улучшении поведения. Java IO-потоки используют это там, где поведение (реализация метода) улучшено классом украшения (один поток обернут другим, чтобы улучшить предыдущий)

Ответ 2

Вы должны отличать данные от поведения.

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

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

Ответ 3

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

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

Итак, о том, как мы можем управлять Object Evolution по Object Lifecycle strong > . Надеюсь, у вас есть идея.:))

Ответ 4

Отказ от ответственности: используемые мной терминология и фрагменты кода взяты из этой статьи.

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

Вертикальный декоратор

Numbers numbers = new Sorted(
  new Unique(
    new Odds(
      new Positive(
        new ArrayNumbers(
          new Integer[] {
            -1, 78, 4, -34, 98, 4,
          }
        )
      )
    )
  )
);

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

Горизонтальный декоратор

Numbers numbers = new Modified(
  new ArrayNumbers(
    new Integer[] {
      -1, 78, 4, -34, 98, 4,
    }
  ),
  new Diff[] {
    new Positive(),
    new Odds(),
    new Unique(),
    new Sorted(),
  }
);

В очень редких случаях (когда число декораторов начинает расти), я делаю выбор дизайна, чтобы "сплющить" декораторы в виде списков. Это имеет преимущество в том, что упрощает чтение кода, но имеет недостаток, заключающийся в потере гибкости во время выполнения: при создании объекта необходимо знать, какой декоратор применить (это особенно верно в контексте неизменяемости, которую я стараюсь поддерживать), так что это может быть запретом в зависимости от приложения.

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

Ответ 5

Я знаю, что опоздал, но я попытаюсь объяснить это.

Я думаю, что пример из книги Head First - довольно хорошая попытка объяснить этот паттерн, хотя многие люди запутываются, когда находят легкую альтернативу, то есть коллекцию для этого примера.

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

  1. Предложение на день рождения
  2. Предложение воскресного вечера
  3. 10 лет запуска приложения.
  4. 100-й заказ клиента

и список можно продолжить.

Если кто-то имеет право на несколько предложений, и WhySoHungry хочет, чтобы они применяли все предложения одновременно, то расчетная часть становится трудной. Это лучшее место для нанесения рисунка декоратора.