Intent
- Sammansätt objekt i trädstrukturer för att representera hela hierarkier. Komposit låter klienterna behandla enskilda objekt och sammansättningar av objekt på ett enhetligt sätt.
- Rekursiv sammansättning
- ”Directories contain entries, each of which could be a directory.”
- 1-till-många ”has a” uppåt i ”is a” hierarkin
Problem
Programmet måste hantera en hierarkisk samling av ”primitiva” och ”sammansatta” objekt. Behandlingen av ett primitivt objekt hanteras på ett sätt och behandlingen av ett sammansatt objekt hanteras på ett annat sätt.Det är inte önskvärt att behöva fråga efter ”typen” för varje objekt innan man försöker behandla det.
Diskussion
Definiera en abstrakt basklass (Component) som specificerar det beteende som måste utövas enhetligt över alla primitiva och sammansatta objekt. Underklassa de primitiva och sammansatta klasserna från klassenComponent. Varje Composite-objekt ”kopplar” sig endast till den abstrakta typen Component när det hanterar sina ”barn”.
Använd det här mönstret när du har ”kompositer som innehåller komponenter, som var och en kan vara en komposit”.
Metoder för hantering av barn bör normalt definieras i Composite-klassen. Tyvärr kräver önskan att behandla Primitives och Composites på ett enhetligt sätt att dessa metoder flyttas till den abstrakta klassen Component. Se avsnittet ”Åsikter” nedan för en diskussion om ”säkerhet” kontra ”öppenhet”.
Struktur
Composites som innehåller Components, som var och en kan vara enComposite.
Menyer som innehåller menyalternativ, som var och en kan vara en meny.
Row-column GUI layout managers som innehåller widgetar, som var och en kan vara en row-column GUI layout manager.
Kataloger som innehåller filer, som var och en kan vara en katalog.
Containers som innehåller element, som var och en kan vara en container.
Exempel
Composite sätter ihop objekt till trädstrukturer och låter klienten behandla enskilda objekt och kompositioner på ett enhetligt sätt. Även om exemplet är abstrakt är aritmetiska uttryck kompositer. Ett aritmetiskt uttryck består av en operand, en operatör (+ – * /) och en annan operand. Operanden kan vara ett tal eller ett annat aritmetiskt uttryck. Således är 2 + 3 och (2 + 3) + (4 * 6) båda giltiga uttryck.
Checklista
- Säkerställ att ditt problem handlar om att representera hierarkiska relationer mellan ”helheter och delar”.
- Konsultera heuristiken ”Containers that contain containees,each of which could be a container”. Dela upp dina domänkoncept i behållarklasser och behållarklasser.
- Skapa ett gränssnitt med ”minsta gemensamma nämnare” som gör dina behållare och behållare utbytbara. Det bör specificera det beteende som måste utövas på ett enhetligt sätt över alla containee- och containerobjekt.
- Alla container- och containee-klasser deklarerar en ”is a”-relation till gränssnittet.
- Alla containerklasser deklarerar en ”has a”-relation från en till flera till gränssnittet.
- Containerklasser utnyttjar polymorfism för att delegera till sinacontainee-objekt.
- Metoder för hantering av barn bör normalt sett definieras i Composite-klassen. Tyvärr kan önskan att behandla Leaf- och Composite-objekt på ett enhetligt sätt kräva att metoderna flyttas till den abstrakta Component-klassen. Se Gang of Four för en diskussion om dessa avvägningar mellan ”säkerhet” och ”öppenhet”.
Tumsregler
- Composite och Decorator har liknande strukturdiagram, vilket återspeglar det faktum att båda är beroende av rekursiv komposition för att organisera ett obegränsat antal objekt.
- Composite kan genomgås med Iterator. Besökare kan tillämpa en operation över en komposit. Composite kan använda Chain ofResponsibility för att låta komponenter få tillgång till globala egenskaper genom sin förälder. Den kan också använda Decorator för att åsidosätta dessa egenskaper för delar av kompositionen. Den kan använda Observer för att binda en objektstruktur till en annan och State för att låta en komponent ändra sitt beteende när dess tillstånd ändras.
- Composite kan låta dig komponera en Mediator av mindre bitar genom rekursiv komposition.
- Decorator är utformad för att låta dig lägga till ansvarsområden till objekt utan att underklassa dem. Composite fokuserar inte på utsmyckning utan på representation. Dessa intentioner är skilda men kompletterande och därför används Composite och Decorator ofta tillsammans.
- Flyweight kombineras ofta med Composite för att implementera delade bladknutar.
Opinions
Den stora poängen med Composite-mönstret är att Composite kan bearbetas atomärt, precis som ett blad. Om du vill tillhandahålla ett Iterator-protokoll är det bra, men jag tror att det ligger utanför själva mönstret. Kärnan i detta mönster är möjligheten för en klient att utföra operationer på ett objekt utan att behöva veta att det finns många objekt inuti.
För att kunna behandla en heterogen samling objekt atomärt (eller transparent) krävs att gränssnittet för ”barnhantering” definieras vid roten av Composite-klasshierarkin (den abstraktaComponent-klassen). Detta val kostar dock säkerheten, eftersom klienter kan försöka göra meningslösa saker som att lägga till och ta bort objekt från bladobjekt. Om du däremot ”designar för säkerhet” deklareras gränssnittet för hantering av barn i Composite-klassen, och du förlorar transparens eftersom blad och Composites nu har olika gränssnitt.
Smalltalk-implementationer av Composite-mönstret har vanligtvis inte gränssnittet för hantering av komponenterna i Component-gränssnittet, utan i Composite-gränssnittet. C++-implementationer tenderar att placera det i Component-gränssnittet. Detta är ett mycket intressant faktum som jag ofta funderar över. Jag kan erbjuda teorier för att förklara det, men ingen vet säkert varför det är sant.
Mina komponentklasser vet inte att det finns kompositer. De erbjuder ingen hjälp för att navigera i kompositer och inte heller någon hjälp för att ändra innehållet i en komposit. Detta beror på att jag vill att basklassen (och alla dess derivat) skall kunna återanvändas i sammanhang som inte kräver Composites. Om jag absolut behöver veta om en basklass pekar på en Composite eller inte, kommer jag att använda dynamic_cast
för att ta reda på det. I de fall då dynamic_cast
är för dyrt kommer jag att använda en Visitor.
Gemensamt klagomål: ”Om jag trycker ner Composite-gränssnittet iComposite-klassen, hur ska jag då räkna upp (dvs. gå igenom) en komplex struktur?” Mitt svar är att när jag har beteenden som gäller hierarkier som den som presenteras i Composite-mönstret, använder jag vanligtvis Visitor, så uppräkning är inget problem – Visitor vet i varje enskilt fall exakt vilken typ av objekt den har att göra med. Visitor behöver inte varje objekt tillhandahålla ett uppräkningsgränssnitt.
Composite tvingar dig inte att behandla alla Components som Composites. Den säger bara att du ska placera alla operationer som du vill behandla ”enhetligt” i Component-klassen. Om add, remove och liknande operationer inte kan, eller inte får, behandlas på ett enhetligt sätt, så placera dem inte i basklassen Component. Kom förresten ihåg att varje mönsters strukturdiagram inte definierar mönstret; det beskriver bara vad som enligt vår erfarenhet är en vanlig realisering av mönstret. Bara för att Composites strukturdiagram visar barnhanteringsoperationer i basklassen Component betyder det inte att alla implementationer av mönstret måste göra detsamma.
Stötta vår kostnadsfria webbplats och ägna e-boken!
- 22 designmönster och 8 principer förklaras på djupet
- 228 tydliga och hjälpsamma illustrationer och diagram
- Ett arkiv med kodexempel på 4 språk
- Alla enheter stöds: EPUB/MOBI/PDF-format
406 välstrukturerade, lättlästa och jargongfria sidor
Läs mer…