Введение
Шаблон проектирования в объектно-ориентированном программировании
В объектно-ориентированном программировании шаблон "Декоратор" – это шаблон проектирования, который позволяет динамически добавлять поведение к отдельному объекту, не затрагивая поведение других экземпляров того же класса. Шаблон "Декоратор" часто полезен для соблюдения принципа единственной ответственности, поскольку он позволяет разделять функциональность между классами с чётко определёнными областями ответственности, а также принципа открытости/закрытости, позволяя расширять функциональность класса без его изменения. Использование декоратора может быть более эффективным, чем наследование, так как поведение объекта можно расширить, не определяя совершенно новый объект.
Обзор
Шаблон проектирования "Декоратор" – один из двадцати трех известных шаблонов проектирования; они описывают, как решать часто возникающие задачи проектирования и создавать гибкое и повторно используемое объектно-ориентированное программное обеспечение, то есть объекты, которые проще реализовать, изменять, тестировать и повторно использовать.
Какие проблемы она может решить?
Ответственность должна добавляться к объекту (и удаляться из него) динамически во время выполнения. Должна быть предусмотрена гибкая альтернатива подклассированию для расширения функциональности. При использовании подклассов различные подклассы расширяют класс по-разному. Однако расширение привязывается к классу во время компиляции и не может быть изменено во время выполнения.
Мотивация
В качестве примера рассмотрим окно в оконной системе. Чтобы обеспечить прокрутку содержимого окна, можно добавить горизонтальные или вертикальные полосы прокрутки, в зависимости от необходимости. Предположим, что окна представлены экземплярами интерфейса Window, и предположим, что этот класс не имеет функциональности для добавления полос прокрутки. Можно создать подкласс ScrollingWindow, который предоставляет такую возможность, или создать ScrollingWindowDecorator, который добавляет эту функциональность к существующим объектам Window. На данном этапе оба решения будут приемлемы. Теперь предположим, что также требуется возможность добавления рамок к окнам. Опять же, исходный класс Window не поддерживает эту функцию. Подкласс ScrollingWindow теперь создает проблему, поскольку он фактически создает новый тип окна. Если требуется добавить поддержку рамок ко многим, но не ко всем окнам, необходимо создавать подклассы WindowWithBorder и ScrollingWindowWithBorder и т.д. Эта проблема усугубляется с каждым новым функционалом или подтипом окна, которые добавляются. Для решения с использованием декоратора создается новый BorderedWindowDecorator. Любая комбинация ScrollingWindowDecorator или BorderedWindowDecorator может добавляться к существующим окнам. Если функциональность необходимо добавить ко всем окнам, базовый класс можно изменить. С другой стороны, иногда (например, при использовании внешних фреймворков) изменение базового класса невозможно, недопустимо или неудобно. В предыдущем примере классы SimpleWindow и WindowDecorator реализуют интерфейс Window, который определяет методы draw и getDescription, необходимые в данном сценарии для декорирования элемента управления окном.
Применение декораторов
Добавление или удаление декораторов по команде (например, при нажатии кнопки) — распространенный шаблон пользовательского интерфейса, который часто реализуется совместно с шаблоном проектирования Command. Например, в текстовом редакторе может быть кнопка для выделения текста. При нажатии на эту кнопку отдельные текстовые символы текущего выделения будут обернуты декораторами, изменяющими функцию их отрисовки, что приведет к выделенному отображению (реальная реализация, вероятно, также будет использовать систему разметки для повышения эффективности). Применение или удаление декораторов в зависимости от изменений состояния — еще один распространенный сценарий использования. В зависимости от области действия состояния, декораторы могут применяться или удаляться массово. Кроме того, шаблон State можно реализовать с помощью декораторов вместо подклассов объектов, инкапсулирующих изменяющуюся функциональность. Использование декораторов таким образом делает внутреннее состояние и функциональность объекта State более композиционными и способными обрабатывать произвольную сложность.
Использование в объектах Flyweight
Декорирование также часто используется в шаблоне проектирования Flyweight (лёгковес). Объекты Flyweight делятся на два компонента: инвариантный компонент, который совместно используется всеми объектами Flyweight, и вариант, декорируемый компонент, который может быть частично совместно используемым или полностью независимым. Такое разделение объекта Flyweight предназначено для снижения потребления памяти. Декораторы также обычно кэшируются и повторно используются. Все декораторы будут содержать общую ссылку на совместно используемый, инвариантный объект. Если состояние декорирования изменяется лишь частично, то декораторы также могут быть использованы в определенной степени, однако необходимо следить за тем, чтобы не изменять их состояние во время использования. UITableView в iOS реализует шаблон Flyweight именно таким образом: многократно используемые ячейки таблицы являются декораторами, содержащими ссылки на общий объект строки таблицы, и эти ячейки кэшируются и повторно используются.
Препятствия для взаимодействия с декораторами
Применение комбинаций декораторов различными способами к коллекции объектов создает определенные проблемы при взаимодействии с этой коллекцией с целью полного использования функциональности, добавленной декораторами. В таких случаях полезными могут оказаться шаблоны "Адаптер" или "Посетитель". Взаимодействие с несколькими уровнями декораторов представляет собой дополнительные трудности, и логика адаптеров и посетителей должна быть спроектирована с учетом этого.
Архитектурная значимость
Декораторы поддерживают композиционный подход к расширению функциональности, в отличие от иерархического, "сверху вниз". Декоратор позволяет добавлять или изменять поведение интерфейса во время выполнения. Их можно использовать для обертывания объектов в многослойные, произвольные комбинации способов. Достижение того же результата с помощью подклассов требует реализации сложных сетей множественного наследования, что неэффективно с точки зрения использования памяти и в конечном итоге становится не масштабируемым. Попытка реализовать ту же функциональность с помощью свойств приводит к избыточному утяжелению каждого экземпляра объекта ненужными свойствами. По этим причинам декораторы часто рассматриваются как экономичная с точки зрения памяти альтернатива подклассированию. Декораторы также могут использоваться для специализации объектов, которые невозможно подклассировать, чьи характеристики необходимо изменять во время выполнения (как упоминалось ранее), или, в общем, объектов, которым не хватает необходимой функциональности.
Использование в улучшении API
Декоратор также может расширять возможности шаблона Фасад. Фасад предназначен для упрощенного взаимодействия со сложной системой, которую он инкапсулирует, но не добавляет в систему новую функциональность. Однако, оборачивание сложной системы предоставляет возможность внедрения новой функциональности, основанной на координации подкомпонентов внутри системы. Например, шаблон Фасад может объединить множество различных языковых словарей под единым многоязычным интерфейсом словаря. Этот новый интерфейс может также предоставлять новые функции для перевода слов между языками. Это гибридный шаблон, где единый интерфейс предоставляет пространство для расширения. Рассматривайте декораторы не только как способ оборачивания отдельных объектов, но и как способ оборачивания групп объектов в этом гибридном подходе.
C++
Здесь представлены два варианта: во-первых, динамический декоратор, собираемый во время выполнения (у него возникают проблемы с вызовом декорированных функций, если не использовать явное проксирование), а во-вторых, декоратор, использующий миксин-наследование.