В разработке программного обеспечения составной шаблон представляет собой шаблон проектирования секционирования . Составной шаблон описывает группу объектов, которые обрабатываются так же, как один экземпляр объекта того же типа. Целью композиции является «составление» объектов в древовидные структуры для представления иерархий части-целого. Реализация составного шаблона позволяет клиентам единообразно обрабатывать отдельные объекты и композиции. [1]
Шаблон проектирования Композитный [2] — один из двадцати трех известных шаблонов проектирования GoF , которые описывают, как решать повторяющиеся проблемы проектирования для разработки гибкого и многократно используемого объектно-ориентированного программного обеспечения, то есть объектов, которые легче реализовать, изменить, тестируйте и используйте повторно.
При определении (1) Part
объектов и (2) Whole
объектов, которые действуют как контейнеры для Part
объектов, клиенты должны обрабатывать их отдельно, что усложняет клиентский код. [3]
Component
интерфейс как для частичных ( Leaf
) объектов, так и для целых ( Composite
) объектов.Leaf
объекты реализуют Component
интерфейс напрямую, а Composite
объекты пересылают запросы своим дочерним компонентам.Это позволяет клиентам работать через Component
интерфейс для единообразной обработки Leaf
объектов Composite
: Leaf
объекты выполняют запрос напрямую, а Composite
объекты пересылают запрос своим дочерним компонентам рекурсивно вниз по древовидной структуре. Это упрощает реализацию, изменение, тестирование и повторное использование клиентских классов.
См. также диаграмму классов и объектов UML ниже.
Имея дело с данными с древовидной структурой, программистам часто приходится различать листовой узел и ветвь. Это делает код более сложным и, следовательно, более подверженным ошибкам. Решением является интерфейс, позволяющий единообразно обрабатывать сложные и примитивные объекты. В объектно-ориентированном программировании составной объект — это объект, спроектированный как композиция одного или нескольких аналогичных объектов, обладающих схожей функциональностью. Это известно как отношение « имеет » между объектами. [4] Ключевая концепция заключается в том, что вы можете манипулировать одним экземпляром объекта так же, как если бы вы манипулировали их группой. Операции, которые вы можете выполнять со всеми составными объектами, часто имеют отношение наименьшего общего знаменателя . Например, при определении системы для отображения сгруппированных фигур на экране было бы полезно определить изменение размера группы фигур, чтобы оно имело тот же эффект (в некотором смысле), что и изменение размера одной фигуры.
Композитный вариант следует использовать, когда клиенты игнорируют разницу между составами объектов и отдельными объектами. [1] Если программисты обнаруживают, что они используют несколько объектов одинаковым образом и часто имеют почти идентичный код для обработки каждого из них, тогда составной объект — хороший выбор; в этой ситуации проще рассматривать примитивы и составные элементы как однородные.
На приведенной выше диаграмме классов UML класс не ссылается на классы и напрямую (отдельно). Вместо этого термин относится к общему интерфейсу и может обрабатываться единообразно .
Класс не имеет дочерних элементов и напрямую реализует интерфейс.
Класс поддерживает контейнер дочерних объектов ( ) и перенаправляет к ним запросы ( ).Client
Leaf
Composite
Client
Component
Leaf
Composite
Leaf
Component
Composite
Component
children
children
for each child in children: child.operation()
Диаграмма сотрудничества объектов показывает взаимодействие во время выполнения: В этом примере объект Client
отправляет запрос объекту верхнего уровня Composite
(типа Component
) в древовидной структуре. Запрос пересылается (выполняется) всем дочерним Component
объектам ( Leaf
и Composite
объектам) вниз по древовидной структуре.
Существует два варианта дизайна для определения и реализации дочерних операций, таких как добавление/удаление дочернего компонента в/из контейнера ( add(child)/remove(child)
) и доступ к дочернему компоненту ( getChild()
):
Component
. Это позволяет клиентам единообразно относиться Leaf
к Composite
объектам. Но безопасность типов теряется, поскольку клиенты могут выполнять над объектами дочерние операции Leaf
.Composite
классе. Клиенты должны относиться Leaf
и Composite
возражать по-разному. Но безопасность типов обеспечивается, поскольку клиенты не могут выполнять над объектами операции, связанные с дочерними Leaf
элементами.В шаблоне проектирования Composite основное внимание уделяется единообразию , а не безопасности типов .
Как описано в разделе «Шаблоны проектирования» , этот шаблон также предполагает включение методов манипулирования дочерними элементами в основной интерфейс компонента, а не только в подкласс Composite. В более поздних описаниях эти методы иногда опускаются. [7]
Эта реализация C++14 основана на реализации до C++98, описанной в книге.
#include <iostream> #include < строка> #include <список> #include <память> #include <stdException> typedef двойная валюта ; // объявляет интерфейс для объектов в композиции. class Equipment { // Компонент public : // реализует поведение по умолчанию для интерфейса, общего для всех классов, в зависимости от ситуации. virtual const std :: string & getName () { возвращаемое имя ; } Virtual void setName ( const std :: string & name_ ) { name = name_ ; } виртуальная валюта getNetPrice () { return netPrice ; } Virtual void setNetPrice ( валюта netPrice_ ) { netPrice = netPrice_ ; } // объявляет интерфейс для доступа и управления дочерними компонентами. virtual void add ( std :: shared_ptr < Оборудование > ) = 0 ; виртуальная пустота удалить ( std :: shared_ptr < Оборудование > ) = 0 ; виртуальный ~ Оборудование () = по умолчанию ; protected : Equipment () : name ( "" ), netPrice ( 0 ) {} Equipment ( const std :: string & name_ ) : name ( name_ ), netPrice ( 0 ) {} Private : std :: string name ; Валюта чистая цена ; }; // определяет поведение компонентов, имеющих дочерние элементы. class CompositeEquipment : public Equipment { // Composite public : // реализует операции, связанные с дочерними элементами, в интерфейсе компонента. виртуальная валюта getNetPrice () переопределить { Общая сумма валюты = Оборудование :: getNetPrice (); for ( const auto & i : Equipment ) { total += i -> getNetPrice (); } Возвращаемая сумма ; } Virtual void add ( std :: shared_ptr < Оборудование > оборудование_ ) переопределить { оборудование . push_front ( оборудование_ . get ()); } Virtual void удалить ( std :: shared_ptr < Оборудование > оборудование_ ) переопределить { оборудование . удалить ( оборудование_ . получить ()); } protected : CompositeEquipment () : оборудование () {} CompositeEquipment ( const std :: string & name_ ) : оборудование () { setName ( name_ ); } Private : // сохраняет дочерние компоненты. std :: list < Оборудование *> оборудование ; }; // представляет листовые объекты в композиции. class FloppyDisk : public Equipment { // Leaf public : FloppyDisk ( const std :: string & name_ ) { setName ( name_ ); } // У листа нет детей. void add ( std :: shared_ptr < Оборудование > ) override { throw std :: runtime_error ( "FloppyDisk::add" ); } void Remove ( std :: shared_ptr < Оборудование > ) override { throw std :: runtime_error ( "FloppyDisk::remove" ); } }; класс Chassis : public CompositeEquipment { public : Chassis ( const std :: string & name_ ) { setName ( name_ ); } }; int main () { // Интеллектуальные указатели предотвращают утечки памяти. std :: shared_ptr <FloppyDisk> fd1 = std :: make_shared <FloppyDisk> ( « 3,5-дюймовая дискета » ) ; fd1 -> setNetPrice ( 19.99 ); std :: cout << fd1 -> getName () << ": netPrice=" << fd1 -> getNetPrice () << '\n' ; std :: shared_ptr <FloppyDisk> fd2 = std :: make_shared <FloppyDisk> ( « 5.25in Floppy » ) ; fd2 -> setNetPrice ( 29,99 ); std :: cout << fd2 -> getName () << ": netPrice=" << fd2 -> getNetPrice () << '\n' ; std :: unique_ptr < Шасси > ch = std :: make_unique < Шасси > ( "Шасси ПК" ); ч -> setNetPrice ( 39.99 ); ч -> добавить ( fd1 ); ч -> добавить ( fd2 ); std :: cout << ch -> getName () << ": netPrice=" << ch -> getNetPrice () << '\n' ; fd2 -> добавить ( fd1 ); }
Вывод программы
3,5 на дискете : netPrice = 19,99 5,25 на дискете : netPrice = 29,99 Корпус ПК : netPrice = 89,97 завершить вызов после создания экземпляра ' std :: runtime_error ' What ( ) : FloppyDisk :: add
{{cite book}}
: CS1 maint: несколько имен: список авторов ( ссылка )