Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:22 1 Общие сведения о паттерне
03:39 2 Пример
08:32 3 «Деревья» в виртуальной реальности (Примеры)
17:04 4 Структура паттерна на языке UML
21:52 5 Структура паттерна на языке C#
29:56 6 Реализация паттерна
38:07 7 Назначение паттерна
40:27 8 Использование паттерна

Виде урок расскажет о шаблоне проектирования Composite, который составляет из объектов древовидные структуры для представления иерархий «часть – целое». Позволяет клиентам единообразно трактовать индивидуальные объекты (листья) и составные объекты (ветки).

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн Composite

Название

Компоновщик

Также известен как

-

Классификация

По цели: структурный

По применимости: к объектам

Частота использования

Выше средней    -   1 2 3 4 5

Назначение

Паттерн Composite – составляет из объектов древовидные структуры для представления иерархий «часть – целое». Позволяет клиентам единообразно трактовать индивидуальные объекты (листья) и составные объекты (ветки).

Введение

Паттерн Composite используется в первую очередь для построения специальных структур данных, которые в информатике называются – деревьями. Эти структуры данных называются деревьями, потому что их формальные изображения, напоминают деревья из объективной реальности.

Чтобы построить простейшее дерево, можно воспользоваться двумя классами – ArrayList и String. Корень и ветки дерева можно представить экземплярами класса (коллекции) ArrayList, а листья экземплярами класса String. Из корня и веток могут расти как листья, так и другие ветки (аналогично живому дереву). Из листьев ничего произрастать не может.

Но, в виртуальной реальности деревья растут сверху вниз, такая особенность представления «роста» деревьев обусловлена адресацией памяти (ОЗУ). В те времена, когда в программировании не использовались объектно-ориентированные подходы, ветви деревьев представлялись массивами, а листья – элементами этих массивов. Как известно, традиционный массив располагается в памяти начиная с области младших адресов и заканчивается в области старших адресов.

Так, например, может формально выглядеть дерево представления четных и нечетных чисел. 

Более детально познакомиться с разновидностями деревьев, способами их построения и обхода, можно в книге Дональда Кнута – Искусство программирования (Глава 2.3. Деревья).

Структура паттерна на языке UML

См. пример к главе: \008_Composite\001_Composite

Структура паттерна на языке C#

См. пример к главе: \008_Composite\001_Composite

Участники

  • Component - Компонент:

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

  • Leaf - Лист:

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

  • Composite - Составной объект:

Задает поведение объектов, входящих в структуру дерева, у которых есть потомки, а также сам хранит в себе компоненты дерева (объекты потомки), как узловые, так и листовые. Реализует методы интерфейса Component, относящиеся к управлению потомками.

  • Client - Клиент:

Манипулирует объектами, входящими в структуру дерева, через интерфейс, предоставляемый классом Component.

Отношения между участниками

Отношения между классами

  • Класс Leaf связан связью отношения наследования с абстрактным классом Component.
  • Класс Composite связан связью отношения наследования и связью отношения агрегации с абстрактным классом Component.

Отношения между объектами

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

Мотивация

В качестве примера работы с деревом, предлагается рассмотреть построение простейшего редактора векторной графики. Интерфейс IGraphic будет являться общим типом как для примитивных объектов (Line, Rectangle, Text), так и для контейнеров (Picture). 

Классы Line, Rectangle, Text, будут представлять примитивные графические объекты, из которых формируется картинка типа Picture. В этих классах реализован метод Draw для рисования соответствующих геометрических фигур, но поскольку эти классы представляют листовой уровень дерева, то они не могут иметь потомков, т.е. вложенных в них объектов, относящихся к структуре дерева (из листа не может вырасти ветвь или другой лист). Соответственно классы листового уровня не могут полноценно реализовать методы Add, Remove и GetChild интерфейса IGraphic, поэтому в их телах будет возбуждаться исключение типа InvalidOperationException. Кому-то такая реализация методов покажется не совсем правильной, но эта особенность будет рассмотрена далее достаточно подробно и ее следует принять как должное, несмотря на несоответствие интерфейсов.

Объекты класса Picture, являются узлами (ветвями) в контексте дерева. Класс Picture, полноценно реализует все методы интерфейса IGraphic. Реализованный в классе Picture метод Draw, вызывает методы Draw, на каждом элементе который входит в состав объекта класса Picture.

Объекты класса Picture в программе, будут представлять собой объектно-ориентированное представление векторной картинки, которая может состоять как из примитивных объектов типа Line, Rectangle, Text, так и других картинок типа Picture. Ниже на рисунке можно увидеть, как большая картинка принимает в себя меньшую картинку.

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

См. пример к главе: \008_Composite\002_GraphicDesigner

Применимость паттерна

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

  • Требуется представить иерархию объектов в виде дерева и управлять группами объектов, входящих в эту иерархию.
  • Требуется предоставить клиентам возможность единообразно трактовать (использовать) составные (ветви) и индивидуальные (листья) объекты.

Результаты

Паттерн Composite обладает следующими преимуществами:

  • Позволяет строить иерархии, состоящие из примитивных и составных объектов.

Из примитивных объектов (Leaf) можно строить сложные объекты (Composite), из которых в свою очередь можно строить еще более сложные объекты (Composite).

  • Позволяет упростить работу клиента (клиентский код).

Клиенты могут работать единообразно как с индивидуальными объектами (Leaf), так и с составными структурами (Composite). В идеале, клиент не должен знать с каким уровнем объектов он взаимодействует с листовым (Leaf) или с составным (Composite). Такой подход упрощает код клиента.

  • Облегчает добавление подклассов существующих компонентов.

Новые подклассы (SubLeaf, SubComposite) производные от классов Leaf и Composite смогут без дополнительной адаптации начать работать с существующим деревом, в котором имеются элементы типа Leaf и Composite. Изменять код клиента при этом может не понадобиться (если клиент работает с уже существующим деревом, которое он сам не строил).

 

Паттерн Composite обладает следующим недостатком:

  • Отсутствие контроля классов объектов, которые могут входить в определенное дерево.

Простота добавления в дерево новых типов компонентов имеет и отрицательную сторону. Иногда требуется наложить ограничение на то, объекты каких классов производных от класса Component, могут входить в состав дерева. Паттерн Composite не предоставляет возможности автоматически производить такой контроль. Для этого может понадобиться самостоятельно производить проверку типов, например, с использованием оператора is языка C#.

Реализация

Полезные приемы реализации паттерна Composite:

  • Наличие ссылок на родителей.

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

Обычно поле-ссылка на родителя создается один раз в базовом абстрактном классе Component. Классы Leaf и Composite наследуют это поле-ссылку, а также возможные методы по работе с этой ссылкой.

  • Максимизация интерфейса класса Component.

Одна из целей паттерна Composite – скрыть от клиента информацию о том, с каким уровнем объектов они работают (листовыми или составными). Для этого класс Component должен содержать как можно больше методов, общих для классов Leaf и Composite. Класс Component может предоставлять методы по умолчанию, а классы Leaf и Composite могут замещать или переопределять эти методы. Однако такой подход иногда может противоречить принципу проектирования иерархии классов, согласно которого, класс должен содержать только логичные методы для него самого и всех его подклассов.

  • Создание и использование методов для работы с потомками.

Класс Component содержит набор методов, не имеющих смысла для класса Leaf. Рекомендуется рассматривать класс Leaf как полноценный компонент дерева, у которого не может быть потомков. Лист живого дерева, является полноценным компонентом, входящим в состав дерева, при этом из листа не может вырасти новый лист или ветвь.

Можно было бы в классе Component создать методы доступа к потомкам с реализацией по умолчанию – «никогда не работать с потомком» (например, оставить тело метода пустым). Тогда класс Leaf сможет использовать методы по умолчанию из базового класса Component, а в классе Composite можно переопределить эти методы. Но подход с пустыми телами методов, будет не правильным.

Возникает вопрос: Нужно ли создавать методы по работе с потомками в базовом классе Component, делая их доступными для класса Leaf, или такие методы следует создавать только в классе Composite? Прежде чем ответить на этот вопрос, нужно рассмотреть такие понятия как прозрачность и безопасность, и выбрать что-то одно из них.

Если задать интерфейс (набор методов) по работе с потомками в корне иерархии (в базовом классе Component), то можно добиться прозрачности, так как все компоненты дерева можно трактовать (использовать) единообразно. Однако при таком подходе придется пожертвовать безопасностью, поскольку клиент может попытаться вызвать метод по работе с потомками на элементе листового уровня (например, попытаться добавить или удалить элемент).

Если интерфейс по работе с потомками задать исключительно в классе Composite, то безопасность удастся обеспечить (любая попытка добавить или удалить элемент на объекте класса Leaf, приведет к ошибке уровня компиляции), но при таком подходе теряется прозрачность, так как у Leaf и Composite будут различные интерфейсы.

В паттерне Composite предпочтение отдается прозрачности, а не безопасности. Единственный способ обеспечить прозрачность – это включить в класс Component методы Add, Remove и GetChild. При реализации данных методов в классе Leaf, можно было бы сделать реализацию данных методов пустой, но такой подход будет вводить разработчиков в заблуждение (Будет получаться так, что в программном коде объект казалось бы добавлен в дерево, а при выполнении в дереве этого объекта нет, или в программном коде объект удален из дерева, а при выполнении объект остался в дереве.). Лучшим решением будет такая реализация методов Add, Remove и GetChild, при которой вызовы этих методов будут завершаться ошибкой, возбуждая исключение типа InvalidOperationException.

  • Организация порядка следования элементов (потомков) в дереве.

В большинстве случаев важен порядок следования элементов в дереве. Например, может понадобиться организовать расположение элементов в Z-порядке (Аналогично Z-порядку расположения блочных элементов, перекрывающих друг друга в HTML. Или порядку перекрытия фигур в PowerPoint или Visio.). Если порядок следования элементов в дереве важен, то этот порядок необходимо учитывать при проектировании интерфейсов (методов) доступа.

  • Кэширование для повышения производительности.

Если приходится часто обходить дерево или производить в нем поиск элементов, то есть смысл организовать в классе Composite механизм кэширования, который будет сохранять информацию об обходе или поиске. Для организации механизма кэширования можно использовать техники мемоизации, или воспользоваться готовым решением: библиотекой сквозной функциональности - Enterprise Library.

Кэшировать рекомендуется либо полученные результаты, либо только информацию, позволяющую ускорить обход или поиск.

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

  • Какая разновидность коллекций лучше всего подходит для хранения компонентов дерева?

Родительские узлы деревьев (объекты класса Composite) могут хранить ссылки на своих потомков в самых разных разновидностях массивов и коллекций. Для хранения ссылок на потомков могут использоваться: массивы, списки, связанные списки, хэш-таблицы, словари и прочие разновидности коллекций. Выбор нужной коллекции определяется эффективностью ее использования. В частном случае родительский узел может хранить ссылки на своих потомков в обычных полях (переменных).

  • Рекурсивная композиция.

Паттерн Composite описывает правильные способы построения именно объектно-ориентированных представлений древовидных структур и способов их обхода. Построить дерево можно как вручную (пошагово добавляя узлы), так и с использованием техник рекурсивной композиции. В некоторых языках имеется специальный оператор для поддержки техники рекурсивной композиции, но в языке C# такого оператора нет. В языке C#, рекурсивная композиция может быть реализована через использование рекурсивных вызовов функций. Эффект применения рекурсивной композиции заключается в том, что клиенту при работе с деревом не придется производить различий между простыми объектами (листьями) и составными объектами (узлами), это значит, что клиент сможет работать со всеми компонентами дерева единообразно. При ручном подходе построения дерева – единообразность практически не дает преимуществ, так как приходится вникать в структуру дерева и понимать тип того или иного компонента.

Предлагается рассмотреть пример построения дерева с использованием техники рекурсивной композиции. Дерево состоит из двух узлов (ветвей). Каждый из узлов содержит элементы (листья) представляющие собой четные и нечетные числа. Структура дерева представлена ниже на диаграмме.

См. пример к главе: \008_Composite\003_RecursionComposition

 

Известные применения паттерна в .Net

System.ServiceModel.Channels.CompositeDuplexBindingElement

http://msdn.microsoft.com/ru-ru/library/system.servicemodel.channels.compositeduplexbindingelement(v=vs.110).aspx

System.Web.UI.Design.WebControls.CompositeControlDesigner

http://msdn.microsoft.com/ru-ru/library/system.web.ui.design.webcontrols.compositecontroldesigner(v=vs.90).aspx

System.Web.UI.WebControls.CompositeControl

http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.compositecontrol(v=vs.110).aspx

System.Windows.Data.CompositeCollection

http://msdn.microsoft.com/ru-ru/library/system.windows.data.compositecollection(v=vs.110).aspx

System.Web.UI.Design.WebControls.TreeViewDesigner

http://msdn.microsoft.com/ru-ru/library/system.web.ui.design.webcontrols.treeviewdesigner(v=vs.110).aspx

System.Web.UI.WebControls.TreeNodeCollection

http://msdn.microsoft.com/ru-ru/library/system.web.ui.webcontrols.treenodecollection(v=vs.110).aspx

System.Web.UI.WebControls.TreeView

http://msdn.microsoft.com/en-us/library/system.web.ui.webcontrols.treeview(v=vs.110).aspx

System.Windows.Controls.TreeView

http://msdn.microsoft.com/en-us/library/system.windows.controls.treeview.aspx

System.Windows.Forms.TreeNodeCollection

http://msdn.microsoft.com/ru-ru/library/system.windows.forms.treenodecollection(v=vs.110).aspx

© 2017 ITVDN, все права защищены