Вопрос новичка о шаблоне дизайна Decorator

18

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

Поэтому я посмотрел Декоратор и нашел статью в Википедии . Теперь я понимаю концепцию шаблона Decorator, но меня немного смутил этот отрывок:

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

Теперь давайте предположим, что мы также хотим добавить возможность добавить границы в наши окна. Опять же, наш оригинальный класс Window не имеет поддержки. Подкласс ScrollingWindow теперь создает проблему, потому что он эффективно создал окно нового типа. Если мы хотим добавить поддержку границ для всех окон, мы должны создать подклассы WindowWithBorder и ScrollingWindowWithBorder. Очевидно, что эта проблема усугубляется с каждой добавляемой новой функцией. Для решения для декоратора мы просто создаем новый BorderedWindowDecorator - во время выполнения мы можем декорировать существующие окна с помощью ScrollingWindowDecorator или BorderedWindowDecorator или обоих, как мы сочтем нужным.

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

В статье была еще одна строка:

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

Я не понимаю, где они говорят «... изменение влияет на все экземпляры исходного класса» - как подклассы изменяют родительский класс? Разве это не весь смысл подкласса?

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

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

Джим
источник
спасибо за редактирование, Уолтер ... кстати, я использовал "парней" не для исключения женщин, а в качестве неофициального приветствия.
Джим
Редактирование будет просто удалить приветствие в целом. Это не стандартный протокол SO / SE, чтобы использовать один в вопросах [не волнуйтесь, мы не думаем, что было бы грубо прыгать прямо в вопрос]
Фаррелл
хорошо, спасибо! просто он пометил это как «удаление пола», и я бы не хотел, чтобы кто-то думал, что я женоненавистник или что-то в этом роде! :)
Джим
Я использую их в значительной степени , чтобы избежать этих юридических процедурных вещей , называемых «услуги»: medium.com/@wrong.about/...
западло

Ответы:

13

Шаблон декоратора - тот, который предпочитает композицию перед наследованием [еще одна парадигма ООП, о которой полезно узнать]

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

С помощью декоратора вы просто добавляете новый класс, который описывает это поведение, и все - шаблон позволяет вам эффективно добавить это без каких-либо изменений в остальной части кода.
С подклассами у вас есть кошмар на руках.
Один вопрос, который вы задали, был: "Как подклассификация меняет родительский класс?" Дело не в том, что это меняет родительский класс; когда он говорит экземпляр, это означает любой объект, который вы «создали» [если вы используете Java или C #, например, с помощью newкоманды]. Это означает, что когда вы добавляете эти изменения в класс, у вас нет выбора для того, чтобы это изменение было, даже если оно вам на самом деле не нужно.

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

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

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

Фаррелл
источник
2
Спасибо, Фаррелл, - когда вы говорите: «В качестве альтернативы вы можете поместить все его функциональные возможности в один класс, включив / выключив его с помощью флагов ... но это заканчивается одним классом, который становится все больше и больше по мере роста вашего проекта». это заставило это щелкнуть для меня. Я думаю, что часть проблемы в том, что я никогда не работал над классом, который стал настолько большим, что декоратор имел для меня смысл. Размышляя масштабно, я определенно вижу преимущества ... спасибо!
Джим
Кроме того, часть о закрытых классах имеет массу смысла ... спасибо!
Джим
3

Рассмотрим возможности добавления полос прокрутки и границ с помощью подклассов. Если вы хотите каждую возможность, вы получаете четыре класса (Python):

class Window(object):
    def draw(self):
        "do actual drawing of window"

class WindowWithScrollBar(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of scrollbar"

class WindowWithBorder(Window):
    def draw(self):
        Window.draw(self)
        "do actual drawing of border"

class WindowWithScrollBarAndBorder(Window):
    def draw(self):
        WindowWithScrollBar.draw(self)
        WindowWithBorder.draw(self)

Теперь WindowWithScrollBarAndBorder.draw()окно get рисуется дважды, и во второй раз оно может или не может перезаписать уже нарисованную полосу прокрутки, в зависимости от реализации. Таким образом, ваш производный код тесно связан с реализацией другого класса, и вам нужно заботиться об этом каждый раз, когда вы изменяете Windowповедение класса. Решение состоит в том, чтобы скопировать и вставить код из суперклассов в производные классы и настроить его в соответствии с потребностями производных классов, но каждое изменение в суперклассе должно быть снова скопировано и снова откорректировано, поэтому снова производные классы тесно связан с базовым классом (через необходимость копирования-вставки-настройки). Другая проблема заключается в том, что если вам нужно еще одно свойство, которое может иметь или не иметь окно, вам придется удваивать каждый класс:

class Window(object):
    ...

class WindowWithGimmick(Window):
    ...

class WindowWithScrollBar(Window):
    ...

class WindowWithBorder(Window):
    ...

class WindowWithScrollBarAndBorder(Window):
    ...

class WindowWithScrollBarAndGimmick(Window):
    ...

class WindowWithBorderAndGimmick(Window):
    ...

class WindowWithScrollBarAndBorderAndGimmick(Window):
    ...

Это означает, что для набора взаимно независимых функций f с | f | число функций, вы должны определить 2 ** | f | классы, например если у вас есть 10 функций, вы получаете 1024 класса с жесткой связью. Если вы используете шаблон Decorator, каждая функция get имеет свой независимый и слабо связанный класс, и у вас есть только 1 + | f | классы (это 11 для примера выше).

pillmuncher
источник
Видите, я бы не думал продолжать добавлять классы - это было бы добавить функциональность к исходному (под) классу. так в классе Window (объект): у вас есть scrollBar = true, border = false и т. д. Ответ Фаррелла показывает мне, где это может пойти не так - просто приводит к раздутым классам и тому подобное ... спасибо за ответ, +1!
Джим
ну, +1, как только у меня появится представитель, чтобы сделать это!
Джим
2

Я не эксперт по этому конкретному шаблону, но, как я вижу, шаблон Decorator можно применять к классам, которые вы не можете изменять или подклассить (например, они могут не быть вашим кодом и быть запечатаны). ). В вашем примере, что если вы не написали класс Window, а просто его потребляете? Пока класс Window имеет интерфейс, и вы программируете на этом интерфейсе, ваш Декоратор может использовать тот же интерфейс, но расширять функциональность.

Пример, который вы упомянули, на самом деле описан здесь довольно аккуратно:

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

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

http://www.oodesign.com/decorator-pattern.html

Дэн Диплом
источник