Intentent
- Komponowanie obiektów w struktury drzewiaste w celu reprezentowania całycharthierarchii. Composite pozwala klientom traktować pojedyncze obiekty i kompozycje obiektów jednolicie.
- Recursive composition
- „Directories contain entries, each of which could be a directory.”
- 1-to-many „has a” up the „is a” hierarchy
Problem
Aplikacja musi manipulować hierarchiczną kolekcją „prymitywnych” i „złożonych” obiektów. Przetwarzanie obiektu prymitywnego jest obsługiwane w jeden sposób, a przetwarzanie obiektu złożonego jest obsługiwane inaczej. Konieczność zapytania o „typ” każdego obiektu przed próbą jego przetworzenia nie jest pożądana.
Dyskusja
Zdefiniuj abstrakcyjną klasę bazową (Component), która określa zachowanie, które musi być wykonywane jednolicie dla wszystkich obiektów prymitywnych i złożonych. Podklasuj klasy Primitive i Composite na podstawie klasyComponent. Każdy obiekt Composite „sprzęga” się tylko z abstrakcyjnym typem Component, ponieważ zarządza swoimi „dziećmi”.
Używaj tego wzorca zawsze, gdy masz „kompozyty, które zawierają komponenty, z których każdy może być kompozytem”.
Metody zarządzania dziećmi powinny być normalnie zdefiniowane w klasie Composite. Niestety, chęć jednolitego traktowania Primitives i Composites wymaga, aby te metody zostały przeniesione do abstrakcyjnej klasy Component. Zobacz sekcję „Opinie” poniżej, aby omówić kwestie „bezpieczeństwa” i „przejrzystości”.
Struktura
Kompozyty, które zawierają Komponenty, z których każdy może być Kompozytem.
Menu, które zawierają elementy menu, z których każdy może być menu.
Row-column GUI layout manager, który zawiera widżety, z których każdy może być row-column GUI layout manager.
Katalogi, które zawierają pliki, z których każdy może być katalogiem.
Kontenery, które zawierają Elementy, z których każdy może być Kontenerem.
Przykład
Kompozyt komponuje obiekty w struktury drzewiaste i pozwala klientowi traktować poszczególne obiekty i kompozycje jednolicie. Chociaż przykład jest abstrakcyjny, wyrażenia arytmetyczne są Kompozytami. Wyrażenie arytmetyczne składa się z operandu, operatora (+ – * /) i innego operandu. Operandem może być liczba lub inne wyrażenie arytmetyczne. Tak więc, 2 + 3 i (2 + 3) + (4 * 6) są wyrażeniami prawidłowymi.
Lista kontrolna
- Upewnij się, że Twój problem dotyczy reprezentowania „całościowych” relacji hierarchicznych.
- Rozważ heurystykę, „Kontenery, które zawierają elementy, z których każdy może być kontenerem.” Na przykład, „Assembliest, które zawierają komponenty, z których każdy może być zespołem. „Podziel swoje koncepcje domeny na klasy kontenerów i klasy kontenerów.
- Stwórz interfejs „najniższego wspólnego mianownika”, który sprawia, że twoje kontenery i kontenery są wymienne. Powinien on określać zachowanie, które musi być wykonywane jednolicie we wszystkich obiektach kontenerów i kontenerów.
- Wszystkie klasy kontenerów i containee deklarują relację „is a” do interfejsu.
- Wszystkie klasy kontenerów deklarują relację jeden do wielu „has a” do interfejsu.
- Klasy kontenerów wykorzystują polimorfizm do delegowania do swoich obiektówcontainee.
- Metody zarządzania dziećmi powinny być normalnie zdefiniowane w klasie Composite. Niestety, chęć jednolitego traktowania obiektów Leaf i Composite może wymagać, aby metody te były promowane do abstrakcyjnej klasy Component. Zobacz Gang ofFour, aby omówić te kompromisy pomiędzy „bezpieczeństwem” a „przejrzystością”.
Rules of thumb
- Kompozyt i Dekorator mają podobne diagramy strukturalne, odzwierciedlające fakt, że oba polegają na rekurencyjnej kompozycji w celu zorganizowania nieograniczonej liczby obiektów.
- Kompozyt może być przemierzany za pomocą Iteratora. Visitor może zastosować operację na Composite. Composite może używać Chain ofResponsibility aby pozwolić komponentom na dostęp do globalnych właściwości poprzez ich rodzica. Może również użyć Dekoratora do nadpisania tych właściwości na elementach kompozycji. Mógłby użyć Observer do powiązania jednej struktury obiektów z inną i State do umożliwienia komponentowi zmiany zachowania, gdy zmienia się jego stan.
- Composite może pozwolić na skomponowanie Mediatora z mniejszych kawałków poprzez rekursywną kompozycję.
- Decorator jest zaprojektowany, aby pozwolić na dodawanie odpowiedzialności do obiektów bez podklasowania. Composite nie skupia się na upiększaniu, ale na reprezentacji. W konsekwencji Composite i Decorator są często używane razem.
- Flyweight jest często łączony z Composite w celu implementacji współdzielonych węzłów liściowych.
Opinie
Cały sens wzorca Composite polega na tym, że Composite może być tworzony atomowo, tak jak liść. Jeśli chcesz zapewnić protokółIterator, dobrze, ale myślę, że jest to poza samym wzorcem. Sercem tego wzorca jest zdolność klienta do wykonywania operacji na obiekcie bez konieczności posiadania wiedzy, że w środku znajduje się wiele obiektów.
Możliwość traktowania heterogenicznej kolekcji obiektów atomowo (lub transparentnie) wymaga, aby interfejs „zarządzania dziećmi” był zdefiniowany u korzenia hierarchii klas Composite (abstrakcyjna klasaComponent). Jednak ten wybór kosztuje Cię bezpieczeństwo, ponieważ klienci mogą próbować robić bezsensowne rzeczy, takie jak dodawanie i usuwanie obiektów z obiektów liści. Z drugiej strony, jeśli „projektujesz dla bezpieczeństwa”, interfejs zarządzania dziećmi jest zadeklarowany w klasie Composite, a ty tracisz przejrzystość, ponieważ liście i kompozyty mają teraz różne interfejsy.
Smalltaltaltalkowe implementacje wzorca Composite zazwyczaj nie umieszczają interfejsu zarządzania komponentami w interfejsie Component, ale w interfejsie Composite. Implementacje C++ mają tendencję do umieszczania go w interfejsie Component. Jest to niezwykle interesujący fakt, nad którym często się zastanawiam. Mogę zaproponować teorie, które to wyjaśnią, ale nikt nie wie na pewno, dlaczego jest to prawda.
Moje klasy Komponentów nie wiedzą, że Kompozyty istnieją. Nie zapewniają one żadnej pomocy w nawigacji po Kompozytach, ani żadnej pomocy w zmianie zawartości Kompozytu. Dzieje się tak, ponieważ chciałbym, aby klasa bazowa (i wszystkie jej pochodne) były wielokrotnego użytku w kontekstach, które nie wymagają Kompozytów. Gdy otrzymam wskaźnik do klasy bazowej, jeśli koniecznie muszę wiedzieć, czy jest ona złożona, czy nie, użyję dynamic_cast
, aby się tego dowiedzieć. W tych przypadkach, w których dynamic_cast
jest zbyt kosztowne, użyję Visitor.
Common complaint: „jeśli zepchnę interfejs Composite w dół do klasyComposite, jak mam zamiar wyliczyć (tj. Przemierzać) złożoną strukturę?”. Moja odpowiedź jest taka, że kiedy mam zachowania, które odnoszą się dohierarchii takich jak ta przedstawiona we wzorcu Composite, zazwyczaj używam Visitora, więc wyliczanie nie jest problemem – Visitork wie w każdym przypadku, dokładnie z jakim rodzajem obiektu ma do czynienia. TheVisitor nie potrzebuje, aby każdy obiekt zapewniał interfejs wyliczeniowy.
Composite nie zmusza cię do traktowania wszystkich komponentów jako Composites. Mówi ci jedynie, abyś umieścił wszystkie operacje, które chcesz traktować „jednolicie” w klasie Component. Jeśli operacje dodawania, usuwania i podobne nie mogą, lub nie powinny być traktowane jednolicie, to nie umieszczaj ich w klasie bazowej Component. Pamiętaj, przy okazji, że diagram struktury każdego wzorca nie definiuje wzorca; on jedynie przedstawia to, co z naszego doświadczenia jest jego powszechną realizacją. Tylko dlatego, że diagram struktury Composite pokazuje operacje zarządzania dziećmi w klasie bazowej Component, nie oznacza to, że wszystkie implementacje wzorca muszą robić to samo.
Wspieraj naszą darmową stronę i posiądź eBook!
- 22 wzorce projektowe i 8 zasad wyjaśnionych dogłębnie
- 406 dobrze zorganizowanych, łatwych do czytania, wolnych od żargonu stron
- 228 przejrzystych i pomocnych ilustracji i diagramów
- Archiwum z przykładami kodu w 4 językach
- Wszystkie obsługiwane urządzenia: Formaty EPUB/MOBI/PDF
Dowiedz się więcej…