Composite

Przeznaczenie

  • Składa obiekty w struktury drzewiaste reprezentujące hierarchie typu część-całość

  • Umożliwia klientom jednakowe traktowanie pojedynczych obiektów i złożeń obiektów

  • Kluczem do wzorca kompozyt jest klasa abstrakcyjna, która reprezentuje zarówno elementy pierwotne jak i ich pojemniki

Kontekst

  • Chcemy przedstawić hierarchie obiektów część-całość

  • Hierarchia obiektów ma wspólną klasę bazową (klasę abstrakcyjną)

Problem

  • Chcemy, aby klienci mogli ignorować różnicę między złożeniami obiektów a pojedynczymi obiektami – klienci będą wtedy jednakowo traktować wszystkie obiekty występujące w strukturze

Struktura

Możemy wyróżnić dwie strukturalne odmiany wzorca Composite.

  1. Operacje zarządzania komponentami składowymi są zdefiniowane w klasie Component (klasie bazowej hierarchii):

_images/Composite_A.png
  1. Operacje zarządzania komponentami składowymi definiuje klasa Composite:

_images/Composite_B.png

Uczestnicy

Component

  • Implementuje, tam gdzie to możliwe, domyślne zachowanie w wypadku interfejsu wspólnego dla wszystkich klas

  • Definiuje interfejs umożliwiający dostęp i zarządzanie komponentami-dziećmi

  • Definiuje interfejs umożliwiający dostęp do rodzica komponentu w strukturze rekurencyjnej i implementuje go, o ile jest to potrzebne

Leaf

  • Reprezentuje obiekty będące liśćmi w składanej strukturze; liść nie ma dzieci

  • Definiuje zachowanie obiektów pierwotnych w strukturze

Composite

  • Definiuje zachowanie komponentów mających dzieci

  • Przechowuje komponenty będące dziećmi

  • Implementuje operacje z interfejsu komponentu związane z dziećmi

Client – manipuluje obiektami występującymi w strukturze, wykorzystując interfejs komponentu

Współpraca

Klienci używają interfejsu z klasy Component w celu komunikowania się z obiektami występującymi w składanej strukturze. Jeśli odbiorca jest liściem, to żądania są realizowane bezpośrednio. Jeśli odbiorca jest kompozytem (Composite), to zwykle przekazuje swoje żądania komponentom-dzieciom, wykonując ewentualnie przed i/ lub po przekazaniu dodatkowe operacje.

Konsekwencje

  1. Wzorzec Composite definiuje hierarchie klas grupujących obiekty pierwotne i złożone.

  2. Uproszczenie budowy klienta. Klienci mogą jednakowo traktować struktury złożone i pojedyncze obiekty.

  3. Ułatwienie dodawania nowych rodzajów komponentów.

  4. Może sprawić, że projekt będzie zbyt ogólny. Umieszczenie operacji dodawania nowych komponentów w klasie bazowej Component komplikuje wprowadzanie ograniczeń dotyczących złożeń komponentów.

Implementacja

  1. Jawne odwołania do rodziców. Przechowywanie odwołań z komponentów-dzieci do ich rodziców może ułatwiać poruszanie się po strukturze kompozytu i zarządzanie nią. Typowym miejscem do definiowania odwołania do rodzica jest klasa Component. Klasy Leaf i Composite mogą dziedziczyć to odwołanie i zarządzające nim operacje.

  2. Współdzielenie komponentów. Bardzo często opłacalne jest współdzielenie komponentów, na przykład w celu ograniczenia wymagań pamięciowych.

  3. Maksymalne powiększenie interfejsu klasy Component. Wzorzec kompozyt ujednolica interfejsy klas Leaf i Composite – klienci nie wiedzą, jakich konkretnie klas używają. Klasa Component powinna definiować tak dużo wspólnych operacji dla klas Composite i Leaf, jak to tylko możliwe.

  4. Ustalenie, które klasy w hierarchii klas Composite deklarują operacje Add i Remove dotyczące zarządzania dziećmi.

  5. Zdefiniowanie interfejsu zarządzania dziećmi w korzeniu omawianej hierarchii klas. Zapewnia przezroczystość, ponieważ umożliwia jednakowe traktowanie wszystkich komponentów. Zmniejsza bezpieczeństwo implementacji, ponieważ klienci mogą próbować dodawać i usuwać obiekty z liści.

  6. Zdefiniowanie zarządzania dziećmi w klasie Composite. Zapewnia bezpieczeństwo implementacji, ponieważ każda próba dodawania lub usuwania obiektów z liści będzie w językach ze statyczną kontrolą typów, takich jak C++, wychwycona już w czasie kompilacji.

  7. Kto powinien usuwać komponenty? W językach nie umożliwiających automatycznego odśmiecania pamięci najlepiej jest uczynić obiekt Composite odpowiedzialnym za usuwanie swoich dzieci wtedy, kiedy on sam jest niszczony. Wyjątkiem od tej reguły jest sytuacja, kiedy obiekty Leaf są niezmienne, a zatem mogą być współdzielone.

  8. Jaka struktura danych jest najlepsza do przechowywania komponentów? Kompozyty mogą używać wielu różnych struktur danych do przechowywania swoich dzieci, list, drzew, tablic i tablic z haszowaniem.

Wzorce pokrewne

  1. Związku komponent-rodzic używa się przy stosowaniu wzorca Chain of Responsibility.

  2. Composite jest często używany ze wzorcem dekorator. Gdy dekorator i kompozyt są stosowane razem, mają na ogół wspólnego rodzica. Klasa Decorator musi zatem uwzględnić interfejs klasy Component z takimi operacjami, jak Add(), Remove() i GetChild().

  3. Flyweight umożliwia współdzielenie komponentów, ale nie mogą one już mieć referencji do swoich rodziców.

  4. Iterator może być używany do przechodzenia kompozytów.

  5. Visitor grupuje w jednym miejscu operacje i zachowanie, które w przeciwnym razie byłyby rozproszone w klasach Composite i Leaf.