Паттер Декоратор - описание (шаблон проектирования)
Primary tabs
Forums:
Название и классификация
Декоратор - паттерн, структурирующий объекты - относится к группе структурных паттернов.
Назначение
Динамически добавляет объекту новые обязанности. Является гибкой альтернативой порождению подклассов с целью расширения функциональности.
Псевдоним
Паттерн также встречается под названием Wrapper (обертка) - то есть тут важно не спутать с Адаптером.
Мотивация
Иногда бывает нужно возложить дополнительные обязанности на отдельный объект, а не на класс в целом. Так, библиотека для построения графических интерфейсов пользователя должна «уметь» добавлять новое свойство, скажем, рамку или новое поведение (например, возможность прокрутки к любому элементу интерфейса).
Добавить новые обязанности допустимо с помощью наследования. При наследовании классу с рамкой вокруг каждого экземпляра подкласса будет рисоваться рамка. Однако это решение статическое, а значит, недостаточно гибкое.
Клиент не может управлять оформлением компонента рамкой.
Более гибким является другой подход: поместить компонент в другой объект, называемый декоратором, который как раз и добавляет рамку. Декоратор следует интерфейсу декорируемого объекта, поэтому его присутствие прозрачно для
клиентов компонента. Декоратор переадресует запросы внутреннему компоненту, но может выполнять и дополнительные действия (например, рисовать рамку) до или после переадресации.
Поскольку декораторы прозрачны, они могут вкладываться друг в друга, добавляя тем самым любое число новых обязанностей.
Например - как показано на сземе ниже можно добавить полосы прокрутки одним декоратором, а рамку другим:
Ниже на диаграмме показано, как композиция объекта TextView с объектами BorderDecorator и ScrollDecorator порождает элемент для ввода текста, окруженный рамкой и снабженный полосой прокрутки:
Классы ScrollDecorator и BorderDecorator являются подклассами Decorator - абстрактного класса, который представляет визуальные компоненты, применяемые для оформления других визуальных компонентов.
VisualComponent - это абстрактный класс для представления визуальных объектов. В нем определен интерфейс для рисования и обработки событий. Отметим, что класс Decorator просто переадресует запросы на рисование своему компоненту, а его подклассы могут расширять эту операцию.
Подклассы Decorator могут добавлять любые операции для обеспечения необходимой функциональности. Так, операция ScrollTo объекта ScrollDecorator позволяет другим объектам выполнять прокрутку, если им известно о присутствии объекта ScrollDecorator. Важная особенность этого паттерна состоит в том, что декораторы могут употребляться везде, где возможно появление самого объекта VisualComponent. Поэтому клиент не может отличить декорированный
объект от недекорированного, а значит, и никоим образом не зависит от наличия или отсутствия оформлений.
Применимость
Используйте паттерн декоратор:
- для динамического, прозрачного для клиентов добавления обязанностей объектам;
- для реализации обязанностей, которые могут быть сняты с объекта;
- когда расширение путем порождения подклассов по каким-то причинам неудобно или невозможно. Иногда приходится реализовывать много независимых расширений, так что порождение подклассов для поддержки всех возможных комбинаций приведет к комбинаторному росту их числа. В других случаях определение класса может быть скрыто или почему-либо еще недоступно, так что породить от него подкласс нельзя.
Структура
Струткуру паттерна декоратор можно представить в виде схемы:
Участники
- Component (VisualComponent) – компонент - Класс, задает интерфейс для объектов, на которые могут быть динамически возложены дополнительные обязанности, ровно как задает его и для будущих декораторов.
- ConcreteComponent (TextView) - конкретный компонент - Определяет объект, на который возлагаются дополнительные обязанности, вешаются дополнительные возможности.
- Decorator – декоратор - Хранит ссылку на объект Component и наследует реализацию его интерфейса по-умалчанию;
- ConcreteDecorator (BorderDecorator, ScrollDecorator) - конкретный декоратор - Возлагает дополнительные обязанности на компонент.
Отношения
Клиенты работают "исключительно" с интерфейсом Component. Как видно из структуры паттерна декоратор соответствует этому интерфейсу в силу наследования, а потому клиенту можно передать и декоратор, сконфигурированный особым образом и имеющий ссылку на ConcreteComponent - в этом случае клиент не заметит "подмены", а функционал будет расширен.
Результаты
У шаблона проектирования Декоратор есть как минимум два плюса и два минуса. Поговорим о них подробнее.
- Декоратор обеспечивает большую гибкость, чем при статическом наследовании - можно добавлять "только то, что нужно" и даже более того - во время выполнения можно "ослаблять" или "усиливать" функционал объекта - при этом нет необходимости описывать все конкретные комбинации "функционалов" в разных классах путём наследования и уточнения
- можно применить несколько дектораторов к одному объекту (см. пример)
- ничто не мешает нам добавить один и тот же функционал дважды (напр. нарисовать двойную рамку вокруг объекта) - в то время как двойное наследование от класса в лучшем случае черевато ошибками =) - Декоратор позволяет избежать перегруженных функциями классов на верхних уровнях иерархии - так как необходимый функционал можно "хранить отдельно [как раз таки в декораторе] и добавлять по мере необходимости"
- как уже было сказано - нет нужды поддерживать в классе все мыслимые возможности
- приложение перестаёт "платить" за неиспользуемые функции
- новые виды декораторов объявляются независимо от расширяемых классов - и помогут нам в даже в том случае, когда подобные расширения не планировались - когда же мы расширяем сложный класс (наследование) нам приходится вникать в детали его реализации - Декоратор и его компонент всё таки не идентичны - следует помнить, что "прозрачность" использования декоратора всё же является условной и зависит от реализации проброса запросов самим декторатором -
при этом некоторые операции могут быть заменены то есть их реализация в любом случае оказывается различной (даже если возвращаемые значения одинаковы) - об этом не следует забывать, особенно если используете чью-то уже написанную библиотеку - Множество мелких объектов - в каком-то смысле это "естественное" следствие применение декоратора является противовесом ещё большему множеству "крупных" объектов, состоящих в сложной иерархии, но тем не менее - если известно что система оперирует только несколькими классами и резкий рост их числа не планируется, то, возможно, более логичным оказывается всё же сгруппировать функционал в несколько более крупных объектах.
Если же у нас много мелких классов - то мы сможем собирать много "оптимальных" объектов путём агрегации (напр. "декорирования" по примеру этой статьи), но разобраться в такой системе - как и отлаживать её оказывается более сложным, чем в случае с меньшим числом более крупных классов
Реализация
При реализации паттерна Декторатор следует поразмыслить над следующими моментами:
- Соответствие интерфейсов - интерфейсы декторатора и декорируемого (/потенциально декорируемого) им объекта должны совпадать (основания для этого изложены выше).
- Отсутствие абстрактного класса Decorator - если планируется добавить только одну обязанность (а в общем случае только один фиксированный их набор) то вам вполне хватит одного конкретного (без абстрактного) класс для декоратора -в этом случае ответственность за переадресацию запросов ложится на ConcreteDecorator
- Облегчённые классы Component - класс Component должен быть максимально "лёгким" - просто опредалять интерфейс, а не хранить данные -иначе декораторы могут стать весбма тяжеловесными - и применять их в большом количестве будет накладно - в частности порождения класса, с инициаллизацией множества полей требует ресурса, особенно если эти поля соответствуют типам сложных структур (которые в некоторых язык описываются опять же классами - то есть представляют собой объекты во время исполнения)
- но и включение большого числа функций в Component увеличивает вероятность того, что декораторам - да и другим подклассам тоже придётся "платить за то, что им не нужно". - Изменение облика, а не внутреннего устройства объекта - следует помнить, что декоратор - не зря периодически называют "оболочкой" (обёрткой) - концепция такова, что изменяется "облик" объекта - но не его внутреннее поведение. То есть функционал добавляется путём помещения исходного компонента в специальную оболочку.
Альтернативой последнему является изменение внутреннего устройства объекта, изменение, хорошим примером которого может служить паттерн Стратегия. Стратегию лучше использовать в ситуации, когда Component уже достаточно "тяжёл" ,так что применение паттерна декоратор оказывается слишком накладным.
Пример кода
Рассмотрим пример реализации шаблона проектирования Декоратор на языке РHP
Известные применения
В каркасе ET++ декораторы используются для декорирования потоков
Родственные паттерны
Декоратор можно "сравнить" со следующими паттернами:
- Паттерн Адаптер в отличии от декоратора, который вовсе не изменяеть интерфейс объекта, придаёт объекту совершенно новый интерфейс.
- Паттерн Компоновщик создаёт комбинированные объекты - в этом смысле декоратор можно считать вырожденным случаем составного (комбинированного) объекта - в данном случае имеется только один компонент (декорируемый элемент). Декоратор добавляет новые обязанности (возможности) - агрегирование объектов не является его целью
- Паттерн Стратегия изменяет внутреннее устройство объекта, а декоратор его закрывает его "внешней" оболочкой - эти два способа можно использовать как взаимодополняющие.
----------------------
Источники:
- silversoft.net/docs/dp/hires/pat4dfso.htm
- Паттерн декоратор: codelib.ru/pattern/decorator/java/?addcode#newFormLnk
- Обзор паттернов проектирования: citforum.ru/SE/project/pattern/index.shtml#3.1.2
- Log in to post comments
- 6555 reads