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

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


Компоновщик (Composite)
реализации: java, количество: 7

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

Имя

«Паттерн
Composite» источник codelab.ru оригинал codelab.ru

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

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

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

Мотивация

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

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

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

Ключом к паттерну компоновщик является абстрактный класс, который является одновременно и примитивом, и контейнером. В графической системе этот класс может называться Graphic. В нем объявлены операции, специфичные для каждого вида графического объекта (такие как Draw) и общие для всех составных объектов, например операции для доступа и управления потомками. оригинал источник codelab.ru codelab.ru
Подклассы Line, Rectangle и Text (см. диаграмму выше) определяют примитивные графические объекты. В них операция Draw реализована соответственно для рисования прямых, прямоугольников и текста. Поскольку у примитивных объектов нет потомков, то ни один из этих подклассов не реализует операции, относящиеся к управлению потомками(Add, Remove, GetChild). codelab.ru оригинал источник codelab.ru
Класс Picture агрегирует, состоит из других более примитивных объектов Graphic. Реализованная в нем операция Draw вызывает одноименную функцию отрисовки для каждого потомка, а операции для работы с потомками уже не пусты. Поскольку интерфейс класса Picture соответствует интерфейсу Graphic, то в состав объекта Picture могут входить и другие такие же объекты. источник codelab.ru codelab.ru оригинал
Ниже на диаграмме показана типичная структура составного объекта, рекурсивно скомпонованного из объектов класса Graphic: codelab.ru оригинал источник codelab.ru

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

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

Используйте паттерн компоновщик, когда нужно: codelab.ru оригинал codelab.ru источник
  1. Представить иерархию различных объектов так, чтобы с каждой ее веткой можно было работать одинаково, как с одной и той же сущностью. codelab.ru оригинал codelab.ru источник
  2. Чтобы клиенты единообразно трактовали составные и индивидуальные объекты. оригинал codelab.ru источник codelab.ru

Решение

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

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

При этом структура типичного составного объекта могла бы выглядеть так: codelab.ru источник codelab.ru оригинал

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

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

Участники паттерна Компоновщик (Composite)

codelab.ru оригинал источник codelab.ru
  1. Component (Graphic) – компонент.
    Объявляет интерфейс для компонуемых объектов;
    Предоставляет подходящую реализацию операций по умолчанию, общую для всех классов;
    Объявляет единый интерфейс для доступа к потомкам и управления ими;
    Определяет интерфейс для доступа к родителю компонента в рекурсивной структуре и при необходимости реализует его (возможность необязательна); codelab.ru источник codelab.ru оригинал
  2. Leaf (Rectangle, Line, Text) – лист.
    Объект того же типа что и Composite, но без реализации контейнерных функций;
    Представляет листовые узлы композиции и не имеет потомков;
    Определяет поведение примитивных объектов в композиции;
    Входит в состав контейнерных объектов; источник codelab.ru codelab.ru оригинал
  3. Composite (Picture) - составной объект.
    Определяет поведение контейнерных объектов, у которых есть потомки;
    Хранит иерархию компонентов-потомков;
    Реализует относящиеся к управлению потомками (контейнерные) операции в интерфейсе класса Component; источник codelab.ru оригинал codelab.ru
  4. Client - клиент:
    Манипулирует объектами композиции, используя интерфейс Component. оригинал источник codelab.ru codelab.ru

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

Клиенты используют интерфейс класса Component для взаимодействия с объектами в составной структуре. Если взаимодействие происходит с листовым объектом Leaf, то он и обрабатывает запрос. Когда же получателем является составной объект Composite, то обычно он перенаправляет запрос своим потомкам, возможно, выполняя некоторые дополнительные операции до или после перенаправления. codelab.ru источник оригинал codelab.ru

Вопросы, касающиеся реализации паттерна Компоновщик (Composite)

При реализации паттерна компоновщик приходится рассматривать много вопросов: codelab.ru codelab.ru источник оригинал
  1. Явные ссылки на родителей
    Хранение в компоненте ссылки на своего родителя может упростить обход структуры и управление ею. Наличие такой ссылки облегчает передвижение вверх по структуре и удаление компонента. Кроме того, ссылки на родителей помогают поддержать паттерн цепочка обязанностей.
    Обычно ссылку на родителя определяют в классе Component. Классы Leaf и Composite могут унаследовать саму ссылку и операции с ней.
    При наличии ссылки на родителя важно поддерживать следующий инвариант: если некоторый объект в составной структуре ссылается на другой составной объект как на своего родителя, то для последнего первый является потомком. Простейший способ гарантировать соблюдение этого условия - изменять родителя компонента только тогда, когда он добавляется или удаляется из составного объекта. Если это удается один раз реализовать в операциях Add и Remove, то реализация будет унаследована всеми подклассами и, значит, инвариант будет поддерживаться автоматически; codelab.ru codelab.ru источник оригинал
  2. Разделение компонентов.
    Во избежание дублирования узлов, в случае, если они должны обладать одинаковыми данными, часто бывает полезно использовать одни и те же компоненты в составе различных контейнеров, например для уменьшения объема занимаемой памяти. Т.е. различные контейнеры должны ссылаться на один и тот же более примитивный узел. Но если у компонента может быть более одного родителя, то разделение становится проблемой.
    Возможное решение - позволить компонентам хранить ссылки на нескольких родителей. Однако в таком случае при распространении запроса по структуре могут возникнуть неоднозначности. Паттерн приспособленец показывает, как следует изменить дизайн, чтобы вовсе отказаться от хранения родителей. Работает он в тех случаях, когда потомки могут не посылать сообщений своим родителям, вынеся за свои границы часть внутреннего состояния; оригинал codelab.ru codelab.ru источник
  3. Максимизация интерфейса класса Component.
    Одна из целей паттерна компоновщик - избавить клиентов от необходимости знать, работают ли они с листовым или составным объектом. Для достижения этой цели класс Component должен сделать как можно больше операций общими для классов Composite и Leaf. Обычно класс Component предоставляет для этих операций реализации по умолчанию, а подклассы Composite и Leaf замещают их.
    Однако иногда эта цель вступает в конфликт с принципом проектирования иерархии классов, согласно которому класс должен определять только логичные для всех его подклассах операции. Класс Component поддерживает много операций, не имеющих смысла для класса Leaf. Как же тогда предоставить для них реализацию по умолчанию?
    Иногда, проявив изобретательность, удается перенести в класс Component операцию, которая, на первый взгляд, имеет смысл только для составных объектов. Например, интерфейс для доступа к потомкам является фундаментальной частью класса Composite, но вовсе не обязательно класса Leaf. Однако если рассматривать Leaf как Component, у которого никогда не бывает потомков, то мы можем определить в классе Component операцию доступа к потомкам как никогда не возвращающую потомков. Тогда подклассы Leaf могут использовать эту реализацию по умолчанию, а в подклассах Composite она будет переопределена, чтобы возвращать потомков. источник оригинал codelab.ru codelab.ru
  4. Операции для управления потомками.
    Хотя в классе Composite реализованы операции Add и Remove для добавления и удаления потомков, но для паттерна компоновщик важно, в каких классах эти операции объявлены. Надо ли объявлять их в классе Component и тем самым делать доступными в Leaf, или их следует объявить и определить только в классе Composite и его подклассах?
    Решая этот вопрос, мы должны выбирать между безопасностью и прозрачностью:
    - если определить интерфейс для управления потомками в корне иерархии классов, то мы добиваемся прозрачности, так как все компоненты удается трактовать единообразно. Однако расплачиваться приходится безопасностью, поскольку клиент может попытаться выполнить бессмысленное действие, например добавить или удалить объект из листового узла;
    - если управление потомками сделать частью класса Composite, то безопасность удастся обеспечить, ведь любая попытка добавить или удалить объекты из листьев в статически типизированном языке вроде C++ будет перехвачена на этапе компиляции. Но прозрачность мы утрачиваем, ибо у листовых и составных объектов оказываются разные интерфейсы.
    Но в паттерне компоновщик мы придаем особое значение прозрачности, а не безопасности. Если для вас важнее безопасность, будьте готовы к тому, что иногда вы можете потерять информацию о типе и придется преобразовывать компонент к типу составного объекта. Как это сделать, не прибегая к небезопасным приведениям типов?
    Можно, например, объявить в классе Component операцию Composite.GetComposite(). И класс Component реализует ее по умолчанию, возвращая нулевую ссылку, null т.е.. А в классе Composite эта операция переопределена и возвращает указатель this на сам объект:
     Composite/Component/Leaf, операция GetComposite [C++]  ссылка
    1. class Composite;
    2. class Component {
    3. public:
    4. //...
    5. virtual Composite* GetComposite() { return 0; }
    6. };
    7. class Composite : public Component {
    8. public:
    9. void Add(Component*);
    10. // ...
    11. virtual Composite* GetComposite(} { return this; }
    12. };
    13. class Leaf : public Component {
    14. // ...
    15. };

    Благодаря операции GetComposite можно спросить у компонента, является ли он составным. К возвращаемому этой операцией составному объекту допустимо безопасно применять операции Add и Remove:
     Composite/Component/Leaf, операции Add, Remove [C++]  ссылка
    1. Composite* aComposite = new Composite;
    2. Leaf* aLeaf = new Leaf;
    3.  
    4. Component * aComponent;
    5. Composite* test;
    6.  
    7. aComponent = aComposite;
    8. if (test = aComponent->GetComposite()) {
    9. test->Add(new Leaf);
    10. }
    11.  
    12. aComponent = aLeaf;
    13.  
    14. if (test = aComponent->GetComposite()) {
    15. test->Add(new Leaf); // не добавит лист
    16. }

    Разумеется, при таком подходе мы не обращаемся со всеми компонентами единообразно, что плохо. Снова приходится проверять тип, перед тем как предпринять то или иное действие.
    Единственный способ обеспечить прозрачность - это включить в класс Component реализации операций Add и Remove по умолчанию. Но появится новая проблема: нельзя реализовать Component.Add() так, чтобы она никогда не приводила к ошибке, в случае хаотичного обращения с ним как с контейнером, и как с примитивом. Можно, конечно, сделать данную операцию пустой, но тогда нарушается важное проектное ограничение, поскольку попытка добавить что-то в листовый объект, скорее всего, свидетельствует об ошибке. Допустимо было бы заставить ее удалять свой аргумент, но клиент может быть не рассчитанным на это.
    Обычно лучшим решением является такая реализация Add и Remove по-умолчанию, при которой они завершаются с ошибкой (возможно, возбуждая исключение), если компоненту не разрешено иметь потомков (для Add) или аргумент не является чьим-либо потомком (для Remove).
    Другая возможность - слегка изменить семантику операции «удаления». Если компонент хранит ссылку на родителя, то можно было бы считать, что Component.Remove() удаляет самого себя. Но для операции Add по-прежнему нет разумной интерпретации. оригинал codelab.ru codelab.ru источник
  5. Должен ли Component по-умолчанию включать список компонентов?
    Может возникнуть желание определить множество потомков в виде переменной экземпляра класса Component, в котором объявлены операции доступа и управления потомками. Но размещение указателя на потомков в базовом классе приводит к непроизводительному расходу памяти во всех листовых узлах, хотя у листа потомков быть не может. Такой прием можно применить, только если в структуре не слишком много потомков. codelab.ru codelab.ru источник оригинал
    codelab.ru оригинал codelab.ru источник
  6. Упорядочение потомков.
    Во многих случаях порядок следования потомков составного объекта важен. В рассмотренном выше примере класса Graphic под порядком может пониматься алфавитный порядок расположения потомков. В составных объектах, описывающих деревья синтаксического разбора, составные операторы могут быть экземплярами класса Composite, порядок следования потомков которых отражает семантику программы.
    Если порядок следования потомков важен, необходимо учитывать его при проектировании интерфейсов доступа и управления потомками(Add, Remove, GetChild). И в этом может помочь паттерн итератор. оригинал codelab.ru источник codelab.ru
    источник codelab.ru codelab.ru оригинал
  7. Кэширование для повышения производительности.
    Если приходится часто обходить композицию или производить в ней поиск, то класс Composite может кэшировать информацию об обходе и поиске. Кэшировать разрешается либо полученные результаты, либо только информацию, достаточную для ускорения обхода или поиска. Например, класс Picture из примера, приведенного в разделе Условия, мог бы кэшировать охватывающие прямоугольные рамки своих потомков. При рисовании или выборе эта информация позволила бы пропускать тех потомков, которые не видимы в текущем окне.
    Любое изменение компонента должно делать кэши всех его родителей недействительными. Наиболее эффективен такой подход в случае, когда компонентам известно об их родителях. Поэтому, если вы решите воспользоваться кэшированием, необходимо определить способ (при реализации – интерфейс), позволяющий уведомить составные объекты о недействительности их кэшей; оригинал codelab.ru источник codelab.ru
    codelab.ru оригинал источник codelab.ru
  8. Кто должен удалять компоненты.
    В языках, где нет сборщика мусора, лучше всего поручить классу Composite удалять своих потомков в момент уничтожения. Исключением из этого правила является случай, когда листовые объекты совпадают и один и тот же потомок может использоваться несколькими родителями. codelab.ru источник оригинал codelab.ru
    источник codelab.ru codelab.ru оригинал
  9. Структура для хранения.
    Составные объекты могут хранить своих потомков в самых разных структурах данных, включая связанные списки, деревья, массивы и хэш-таблицы.
    Выбор структуры данных определяется, как всегда, эффективностью. Собственно говоря, вовсе не обязательно пользоваться какой-либо из универсальных структур. Иногда в составных объектах каждый потомок представляется отдельной переменной. Правда, для этого каждый подкласс Composite должен реализовывать свой собственный интерфейс управления памятью (см. паттерн интерпретатор). codelab.ru codelab.ru источник оригинал
    codelab.ru источник оригинал codelab.ru

Результаты

Результаты применения паттерна компоновщик: оригинал codelab.ru источник codelab.ru
  1. Поддерживает иерархии классов, состоящие из примитивных и составных объектов.
    Из примитивных объектов можно составлять более сложные, которые, в свою очередь, участвуют в более сложных композициях и так далее. Любой клиент, ожидающий примитивного объекта, сможет работать и с составным. оригинал codelab.ru источник codelab.ru
  2. Облегчает, упрощает архитектуру клиента.
    Клиенты могут единообразно работать и с индивидуальными объектами и с составными структурами. Обычно клиенту неизвестно, взаимодействует ли он с листовым или составным объектом. Это упрощает код клиента, поскольку нет необходимости писать функции, ветвящиеся в зависимости от того, с объектом какого класса они работают. источник оригинал codelab.ru codelab.ru
  3. Облегчает добавление новых видов компонентов.
    Новые подклассы классов Composite или Leaf будут автоматически работать с уже существующими структурами и клиентским кодом. Изменять клиента при добавлении новых компонентов не нужно. источник оригинал codelab.ru codelab.ru
  4. Способствует созданию общего дизайна.
    Однако такая простота добавления новых компонентов имеет и свои отрицательные стороны: становится трудно наложить ограничения на то, какие объекты могут входить в состав композиции. Иногда желательно, чтобы составной объект мог включать только определенные виды компонентов. Паттерн компоновщик не позволяет воспользоваться для реализации таких ограничений статической системой типов. Вместо этого следует проводить проверки во время выполнения. оригинал codelab.ru источник codelab.ru
  5. Способствует использованию рекурсии.
    Главное достоинство которой, как известно – компактность, малый объем, читабельность кода. codelab.ru источник codelab.ru оригинал

Пример

Рассмотрим интернет-сайт. Его структура обычно представляет собой иерархию различных информационных страниц, сгруппированных по разделам этого интернет-сайта. Т.е. есть как просто страницы с информацией, и есть разделы, выводящие свое какое-нибудь описание, а также список вложенных разделов и страниц. codelab.ru оригинал codelab.ru источник
На такой специальной странице как «карта сайта» необходимо вывести всю иерархию этих страниц в виде единого дерева. Было бы очень полезно иметь единый алгоритм вывода, работающий унифицировано как с отдельными документами, так и разделами, а не определять по каким-то признакам чем является текущий элемент и применять специальную обработку для каждого случая. источник оригинал codelab.ru codelab.ru

Паттерн компоновщик – предлагает решение этой проблемы. Создаем общий интерфейс (Component) компонента интернет-сайта: SitePage. codelab.ru источник оригинал codelab.ru

В классе определены операции, возвращающие атрибуты страницы интернет-сайта: название, описание и ключевые слова. Подклассы будут уточнять эти операции для соответствующих видов интернет страниц. codelab.ru codelab.ru источник оригинал

Кроме того, приведены реализации по-умолчанию так называемых контейнерных операций: Add, Remove, getIterator. Простые информационные страницы будут наследовать эти операции в виде такой пустой реализации: Document. оригинал источник codelab.ru codelab.ru

Разделы же, т.е. составные части интернет сайта – будут переопределять их для работы с вложенными компонентами: Part. оригинал источник codelab.ru codelab.ru

Таким образом, информационные страницы будут наследовать класс Document, составные – Part. codelab.ru codelab.ru источник оригинал

Например, нам понадобятся еще такие информационные страницы как «Информация», «Контакты»,  «Ссылки» и т.д.: Contacts, Info, Links. оригинал codelab.ru codelab.ru источник

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

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

Известные применения паттерна Компоновщик (Composite)

Примеры паттерна компоновщик можно найти почти во всех объектно-ориентированных системах. Первоначально класс View в схеме модель/вид/контроллер в языке Smalltalk был компоновщиком, и почти все библиотеки для построения пользовательских интерфейсов и каркасы проектировались аналогично. Среди них ЕТ++ (со своей библиотекой VObjects) и Interviews (классы Styles, Graphics и Glyphs). Интересно отметить, что первоначально вид View имел несколько подвидов, то есть он был одновременно и классом Component, и классом Composite. В версии 4.0 языка Smalltalk-80 схема модель/вид/контроллер была пересмотрена, в нее ввели класс Visual-Component, подклассами которого являлись View и CompositeView. оригинал источник codelab.ru codelab.ru
В каркасе для построения компиляторов RTL, который написан на Smalltalk, паттерн компоновщик используется очень широко. RTLExpression - это разновидность класса Component для построения деревьев синтаксического разбора. У него есть подклассы, например Binary Expression, потомками которого являются объекты класса RTLExpression. В совокупности эти классы определяют составную структуру для деревьев разбора. RegisterTransfer – класс Component для промежуточной формы представления программы SSA (Single Static Assignment). Листовые подкласс RegisterTransfer определяют различные статические присваивания, например: codelab.ru оригинал источник codelab.ru
  • примитивные присваивания, которые выполняют операцию над двумя регистрами и сохраняют результат в третьем; codelab.ru оригинал codelab.ru источник
  • присваивание, у которого есть исходный, но нет целевого регистра. Следовательно, регистр используется после возврата из процедуры; оригинал источник codelab.ru codelab.ru
  • присваивание, у которого есть целевой, но нет исходного регистра. Это означает, что присваивание регистру происходит перед началом процедуры. codelab.ru codelab.ru источник оригинал

Подкласс RegisterTransferSet является примером класса Composite для представления присваиваний, изменяющих сразу несколько регистров. оригинал codelab.ru codelab.ru источник

Другой пример применения паттерна компоновщик - финансовые программы, когда инвестиционный портфель состоит их нескольких отдельных активов. оригинал codelab.ru источник codelab.ru
Можно поддержать сложные агрегаты активов, если реализовать портфель в виде оригинал codelab.ru источник codelab.ru
компоновщика, согласованного с интерфейсом каждого актива. codelab.ru оригинал источник codelab.ru
Паттерн команда описывает, как можно компоновать и упорядочивать объекты Command с помощью класса компоновщика MacroCommand. codelab.ru codelab.ru источник оригинал

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

Отношение компонент-родитель используется в паттерне цепочка обязанностей. codelab.ru оригинал источник codelab.ru
Паттерн декоратор часто применяется совместно с компоновщиком. Когда декораторы и компоновщики используются вместе, у них обычно бывает общий родительский класс. Поэтому декораторам придется поддержать интерфейс компонентов такими операциями, как Add, Remove и GetChild. оригинал codelab.ru источник codelab.ru
Паттерн приспособленец позволяет разделять компоненты, но ссылаться на своих родителей они уже не могут. codelab.ru источник codelab.ru оригинал
Итератор можно использовать для обхода составных объектов. оригинал источник codelab.ru codelab.ru
Посетитель локализует операции и поведение, которые в противном случае пришлось бы распределять между классами Composite и Leaf. codelab.ru codelab.ru источник оригинал




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

1) SitePage.java, code #452[автор:this]
2) Document.java, code #453[автор:this]
3) Part.java, code #454[автор:this]
4) Contacts.java, code #455[автор:this]
5) Info.java, code #456[автор:this]
6) Links.java, code #457[автор:this]
7) Catalog.java, code #458[автор:this]


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

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