CodeLAB
на главную карта сайта обратная связь
каталог | задачи | паттерны | исходники | стат | форумы | ссылки
 гость
искать в
Главная >> Каталог задач >> Паттерны >> Структурные >> Декоратор (Decorator)

<< назад
распечатать обсудить >>


Декоратор (Decorator)
реализации: java, количество: 4

Aвтор: this
Дата: 18.08.2007
Просмотров: 87029
Рейтинг: 0/0,0(0)
+
реализации(исходники) +добавить

Имя

«Паттерн Decorator» codelab.ru codelab.ru источник оригинал
Декоратор - паттерн, структурирующий объекты, расширяя их функциональность. Также фигурирует под именем Wrapper. codelab.ru codelab.ru оригинал источник

Условия, Задача, Назначение

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

Мотивация

Иногда бывает нужно возложить дополнительные обязанности на отдельный объект, а не на класс в целом. Так, библиотека для построения графических интерфейсов пользователя должна «уметь» добавлять новое свойство, скажем, рамку или новое поведение (например, возможность прокрутки к любому элементу интерфейса). codelab.ru источник оригинал codelab.ru

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

Декоратор переадресует запросы внутреннему компоненту, но может выполнять и дополнительные действия (например, рисовать рамку) до или после переадресации. Поскольку декораторы прозрачны, они могут вкладываться друг в друга, добавляя тем самым какое-угодно число новых возможностей. оригинал codelab.ru источник codelab.ru

codelab.ru оригинал codelab.ru источник

Предположим, что имеется объект класса TextView, который отображает текст в окне. По умолчанию TextView не имеет полос прокрутки, поскольку они не всегда нужны. Но при необходимости их удастся добавить с помощью декоратора ScrollDecorator. Допустим, что еще мы хотим добавить жирную сплошную рамку вокруг объекта TextView. Здесь может помочь декоратор BorderDecorator. оригинал источник codelab.ru codelab.ru
Мы просто передаем сначала объект TextView в декоратор ScrollDecorator, и затем полученный результирующий объект передаем далее в BorderDecorator – получаем искомый результат. codelab.ru оригинал codelab.ru источник

Ниже на диаграмме показано, как композиция объекта TextView с объектами BorderDecorator и ScrollDecorator порождает элемент для ввода текста, окруженный рамкой и снабженный полосой прокрутки. codelab.ru оригинал источник codelab.ru

codelab.ru codelab.ru источник оригинал

Классы ScrollDecorator и BorderDecorator являются подклассами Decorator - абстрактного класса, который представляет визуальные компоненты, применяемые для оформления других визуальных компонентов. codelab.ru источник codelab.ru оригинал
VisualComponent – это абстрактный класс для представления визуальных объектов. В нем определен интерфейс для рисования и обработки событий. Отметим, что класс Decorator просто переадресует запросы на рисование своему компоненту, а его подклассы могут расширять эту операцию. codelab.ru источник оригинал codelab.ru

источник codelab.ru codelab.ru оригинал

Подклассы Decorator могут добавлять любые операции для обеспечения необходимой функциональности. Так, операция ScrollTo объекта ScrollDecorator позволяет другим объектам выполнять прокрутку, если им известно о присутствии объекта ScrollDecorator. codelab.ru оригинал источник codelab.ru
Важная особенность этого паттерна состоит в том, что декораторы могут употребляться везде, где возможно появление самого объекта VisualComponent. Клиенту неважно с каким объектом работать: декорированным или недекорированным, он никоим образом не зависит от наличия или отсутствия оформлений. codelab.ru codelab.ru источник оригинал

Признаки применения, использования паттерна Декоратор (Decorator)

Используйте паттерн декоратор когда: источник codelab.ru codelab.ru оригинал
  1. Для динамического и прозрачного для клиентов добавления новых возможностей, «фич» объектам; codelab.ru оригинал источник codelab.ru
  2. Для реализации возможностей, которые нужны не всем объектам и не всегда, и потом можно было легко их исключить; codelab.ru источник codelab.ru оригинал
  3. Когда расширение путем порождения подклассов по каким-то причинам неудобно или невозможно. Иногда приходится реализовывать много независимых расширений, так что порождение подклассов для поддержки всех возможных комбинаций приведет к комбинаторному росту их числа («комбинаторный взрыв»). В других случаях определение класса может быть скрыто или почему-либо еще недоступно, так что породить от него подкласс нельзя. codelab.ru оригинал codelab.ru источник

Решение

codelab.ru codelab.ru источник оригинал

источник codelab.ru оригинал codelab.ru

Участники паттерна Декоратор (Decorator)

оригинал источник codelab.ru codelab.ru

  1. Component (VisualComponent) – компонент.
    Класс, задает интерфейс для объектов, на которые могут быть динамически возложены дополнительные обязанности, ровно как задает его и для будущих декораторов. источник codelab.ru codelab.ru оригинал
  2. ConcreteComponent (TextView) - конкретный компонент.
    Определяет объект, на который возлагаются дополнительные обязанности, вешаются дополнительные возможности. оригинал codelab.ru источник codelab.ru
  3. Decorator – декоратор.
    Хранит ссылку на объект Component и наследует реализацию его интерфейса по-умалчанию; codelab.ru источник оригинал codelab.ru
  4. ConcreteDecorator (BorderDecorator, ScrollDecorator) - конкретный декоратор.
    Возлагает дополнительные обязанности на компонент. codelab.ru оригинал codelab.ru источник

Схема использования паттерна Декоратор (Decorator)

Клиенты работают только с интерфейсом Component. Когда нужно добавить функциональность в объект типа ConcreteComponent – берут соответствующий ConcreteDecorator, реализующий эту функциональность, конфигурируют, передают ему ссылку на первоначальный объект ConcreteComponent и прозрачно подсовывают клиенту. Клиент, вызывая те же методы, начинает вызывать их у ConcreteDecorator-а, тот, выполнив свою миссию, далее делегирует их первоначальному объекту ConcreteComponent. codelab.ru оригинал codelab.ru источник
  источник codelab.ru codelab.ru оригинал

Вопросы, касающиеся реализации паттерна Декоратор (Decorator)

Применение паттерна декоратор требует рассмотрения следующих вопросов: источник codelab.ru оригинал codelab.ru
  1. Соответствие интерфейсов.
    Интерфейс декоратора должен соответствовать интерфейсу декорируемого компонента. Поэтому классы ConcreteDecorator должны наследоваться от общего класса; источник codelab.ru оригинал codelab.ru
  2. Отсутствие абстрактного класса Decorator.
    Нет необходимости определять класс Decorator абстрактным, если планируется добавить всего одну обязанность. Так часто происходит, когда вы работаете с уже существующей иерархией классов, а не проектируете новую. В таком случае ответственность за переадресацию запросов, которую обычно несет класс Decorator, можно возложить непосредственно на ConcreteDecorator, что конечно не помешает потом добавить новые декораторы, если потребуется: просто будет уже присутствовать некоторая реализация по-умалчанию. codelab.ru источник codelab.ru оригинал
  3. Облегченные классы Component.
    Чтобы можно было гарантировать соответствие интерфейсов, компоненты и декораторы должны наследовать общему классу Component. Важно, чтобы этот класс был настолько легким, насколько возможно. Иными словами, он должен определять интерфейс, а не хранить данные. В противном случае декораторы могут стать весьма тяжеловесными, и применять их в большом количестве будет накладно. Включение большого числа функций в класс Component также увеличивает вероятность, что конкретным подклассам придется платить за то, что им не нужно; codelab.ru источник codelab.ru оригинал
  4. Изменение представления, а не внутреннего устройства объекта.
    Декоратор можно рассматривать как появившуюся у объекта оболочку, которая изменяет его поведение. Противовес – изменение внутреннего устройства объекта, хорошим примером чего может служить паттерн стратегия.
    Стратегии лучше подходят в ситуациях, когда класс Component уже достаточно тяжел, так что применение паттерна декоратор обходится слишком дорого. В паттерне стратегия компоненты передают часть своей функциональности отдельному объекту-стратегии, поэтому изменить или расширить поведение компонента можно, просто заменив этот объект.
    Например, мы можем поддержать разные стили рамок, поручив рисование рамки специальному объекту Border. Объект Border является примером объекта-стратегии: в данном случае он инкапсулирует стратегию рисования рамки. Число стратегий может быть любым, поэтому эффект такой же, как от рекурсивной вложенности декораторов. оригинал источник codelab.ru codelab.ru

Рассмотрим дальше это интересное сравнение декораторов и стратегий.
Поскольку паттерн декоратор изменяет лишь внешний облик компонента-объекта, последнему ничего не надо «знать» о своих декораторах, то есть декораторы прозрачны для компонента. оригинал источник codelab.ru codelab.ru

оригинал codelab.ru источник codelab.ru

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

оригинал codelab.ru источник codelab.ru

источник codelab.ru оригинал codelab.ru

При использовании подхода, основанного на стратегиях, может возникнуть необходимость модифицировать компонент, чтобы он соответствовал новому расширению. В общем случае у стратегии может быть свой собственный специализированный интерфейс, тогда как интерфейс декоратора должен повторять интерфейс компонента. Например, стратегии рисования рамки необходимо определить всего лишь интерфейс для этой операции (DrawBorder, GetWidth и т.д.), то есть класс стратегии может быть легким, несмотря на тяжеловесность компонента.
Системы МасАрр и Bedrock применяют такой подход не только для оформления видов, но и для расширения особенностей поведения объектов, связанных с обработкой событий. В обеих системах вид ведет список объектов поведения, которые могут модифицировать и перехватывать события. Каждому зарегистрированному объекту поведения вид предоставляет возможность обработать событие до того, как оно будет передано незарегистрированным объектам такого рода, за счет чего достигается переопределение поведения. Можно, например, декорировать вид специальной поддержкой работы с клавиатурой, если зарегистрировать объект поведения, который перехватывает и обрабатывает события нажатия клавиш. codelab.ru codelab.ru источник оригинал
источник codelab.ru codelab.ru оригинал

Результаты

У паттерна декоратор есть, по крайней мере, 2 плюса и два минуса, плюсы: codelab.ru источник codelab.ru оригинал
  1. Большая гибкость, нежели у статического наследования.
    Паттерн декоратор позволяет более гибко добавлять объекту новые обязанности, чем это было бы возможно в случае статического (множественного) наследования. Декоратор может добавлять и удалять эту функциональность во время выполнения программы. При использовании же наследования требуется создавать новый класс для каждой дополнительной обязанности (например, Bor.deredScrollableTextView, BorderedTextView), что ведет к увеличению числа классов и, как следствие, к возрастанию сложности системы. Кроме того, применение нескольких декораторов к одному компоненту позволяет произвольным образом сочетать обязанности. Декораторы позволяют легко добавить одно и то же свойство дважды. Например, чтобы окружить объект TextView двойной рамкой, нужно просто добавить два декоратора BorderDecorators. Двойное наследование классу Border в лучшем случае чревато ошибками; codelab.ru codelab.ru источник оригинал
  2. Позволяет избежать перегруженных функциями классов на верхних уровнях иерархии.
    Декоратор разрешает добавлять новые обязанности по мере необходимости. Вместо того чтобы пытаться поддержать все мыслимые возможности в одном сложном, допускающем разностороннюю настройку классе, вы можете определить простой класс и постепенно наращивать его функциональность с помощью декораторов. В результате приложение уже не платит за неиспользуемые функции. Нетрудно также определять новые виды декораторов независимо от классов, которые они расширяют, даже если первоначально такие расширения не планировались. При расширении же сложного класса обычно приходится вникать в детали, не имеющие отношения к добавляемой функции; оригинал codelab.ru codelab.ru источник
  оригинал codelab.ru codelab.ru источник
и минусы: оригинал codelab.ru codelab.ru источник
  1. Декоратор и его компонент не идентичны.
    Декоратор действует как прозрачное обрамление. Но декорированный компонент все же не идентичен исходному. При использовании декораторов это следует иметь в виду; codelab.ru codelab.ru оригинал источник
  2. Множество мелких объектов.
    При использовании в проекте паттерна декоратор нередко получается система, составленная из большого числа мелких объектов, которые похожи друг на друга и различаются только способом взаимосвязи, а не классом и не значениями своих внутренних переменных. Хотя проектировщик, разбирающийся в устройстве такой системы, может легко настроить ее, но изучать и отлаживать ее довольно обременительно. оригинал codelab.ru codelab.ru источник
оригинал источник codelab.ru codelab.ru

Пример

codelab.ru codelab.ru источник оригинал

Применение паттерна декоратор довольно распространено. Один из ближайших примеров – фреймворк автоматизированного тестирования JUnit. codelab.ru источник codelab.ru оригинал
Паттерн декоратор здесь используется для прозрачного добавления новых возможностей в процесс выполнения теста. codelab.ru источник codelab.ru оригинал

Интерфейс для теста выглядит просто: junit.framework.Test.java. оригинал источник codelab.ru codelab.ru

Декоратор к нему: junit.extensions.TestDecorator.java. codelab.ru оригинал codelab.ru источник

Конфигурирующийся соответственно объектом типа Test (JUnit4TestAdapter например). codelab.ru оригинал источник codelab.ru

Фрейворк добавляет такие конкретные виды декораторов, как: junit.extensions.RepeatedTest.java - позволяющий многократное выполнение теста, а также другой декоратор:
junit.extensions.TestSetup.java
- добавляющий возможность выполнять дополнительные действия до и после выполнения теста.

Соответственно, чтобы добавить эту функциональность, можно, например, создать объект типа TestSetup, передав ему имеющийся объект типа Test, далее чтобы добавить еще и многократное выполнение, создаем объект типа RepeatedTest, передавая туда созданный ранее объект типа TestSetup.  codelab.ru codelab.ru оригинал источник

Известные применения паттерна Декоратор (Decorator)

Во многих библиотеках для построения объектно-ориентированных интерфейсов пользователя декораторы применяются для добавления к виджетам графических оформлений. В качестве примеров можно назвать InterviewsЕТ++ и библиотеку классов Smalltalk. Другие варианты применения паттерна декоратор - это класс DebuggingGlyph из библиотеки Interviews и PassivityWrapper из ParcPlace Smalltalk. DebuggingGlyph печатает отладочную информацию до и после того, как переадресует запрос на размещение своему компоненту. Эта информация может быть полезна для анализа и отладки стратегии размещения объектов в сложном контейнере. Класс PassivityWrapper позволяет разрешить или запретить взаимодействие компонента с пользователем. оригинал источник codelab.ru codelab.ru
Но применение паттерна декоратор никоим образом не ограничивается графическими интерфейсами пользователя, как показывает следующий пример, основанный на потоковых классах из каркаса ЕТ++. оригинал источник codelab.ru codelab.ru
Поток - это фундаментальная абстракция в большинстве средств ввода/вывода. Он может предоставлять интерфейс для преобразования объектов в последовательность байтов или символов. Это позволяет записать объект в файл или буфер в памяти и впоследствии извлечь его оттуда. Самый очевидный способ сделать это - определить абстрактный класс Stream с подклассами MemoryStream и FileStream. Предположим, однако, что нам хотелось бы еще уметь: codelab.ru источник оригинал codelab.ru
  • компрессировать данные в потоке, применяя различные алгоритмы сжатия (кодирование повторяющихся серий, алгоритм Лемпеля-Зива и т.д.) оригинал источник codelab.ru codelab.ru
  • преобразовывать данные в 7-битные символы кода ASCII для передачи по каналу связи оригинал источник codelab.ru codelab.ru
Паттерн декоратор позволяет весьма элегантно добавить такие обязанности потокам. На диаграмме ниже показано одно из возможных решений задачи: оригинал codelab.ru codelab.ru источник

codelab.ru codelab.ru оригинал источник

Абстрактный класс Stream имеет внутренний буфер и предоставляет операции для помещения данных в поток (Putlnt, PutString). Как только буфер заполняется, Stream вызывает абстрактную операцию HandleBufferFull, которая выполняет реальное перемещение данных. В классе FileStream эта операция замещается так, что буфер записывается в файл. оригинал codelab.ru codelab.ru источник
Ключевым здесь является класс StreamDecorator. Именно в нем хранится ссылка на тот поток-компонент, которому переадресуются все запросы. Подклассы StreamDecorator замещают операцию HandleBufferFull и выполняют дополнительные действия, перед тем как вызвать реализацию этой операции в классе StreamDecorator. codelab.ru оригинал источник codelab.ru
Например, подкласс CompressingStream сжимает данные, a ASCII7Stream конвертирует их в 7-битный код ASCII. Теперь, для того чтобы создать объект FileStream, который одновременно сжимает данные и преобразует результат в 7-битный код, достаточно просто декорировать FileStream с использованием CompressingStream и ASCII7Stream: источник codelab.ru оригинал codelab.ru

 декоратор на примере сжатия потока и преобразования в 7 битный код [C++]  ссылка
  1. Stream* aStream = new CompressingStream (
  2. new ASCII7Stream(
  3. new FileStream ( "aFileName")
  4. )
  5. );
  6. aStream->PutInt(12);
  7. aStream->PutString("aString");
оригинал codelab.ru источник codelab.ru

codelab.ru codelab.ru оригинал источник

Родственные паттерны

Адаптер: если декоратор изменяет только обязанности объекта, но не его интерфейс, то адаптер придает объекту совершенно новый интерфейс. источник codelab.ru codelab.ru оригинал
Компоновщик: декоратор можно считать вырожденным случаем составного объекта, у которого есть только один компонент. Однако декоратор добавляет новые обязанности, агрегирование объектов не является его целью. источник codelab.ru оригинал codelab.ru
Стратегия: декоратор позволяет изменить внешний облик объекта, стратегия - его внутреннее содержание. Это два взаимодополняющих способа изменения объекта. codelab.ru источник оригинал codelab.ru


codelab.ru codelab.ru оригинал источник



Реализации: java(4)   +добавить реализацию

1) junit.framework.Test.java, code #459[автор:this]
2) junit.extensions.TestDecorator.java, code #460[автор:this]
3) junit.extensions.RepeatedTest.java, code #461[автор:this]
4) junit.extensions.TestSetup.java, code #462[автор:this]


<< назад наверх
распечатать обсудить >>

 
каталог | задачи | паттерны | исходники | стат | форумы | карта сайта | контакты | ссылки 
© 2000-2017 CodeLAB Group
  Все права защищены
Страница сгенерирована за 0.039833 секунд
Количество запросов к БД: 14, gzip: 19.8kb/83.8kb(77%)