Composite [kompozit, ne kompozajt]
Composite Výslovnost kompozit, ne kompozajt Účel Popisuje, jak postavit hierarchii tříd složenou ze dvou druhů objektů Atomických (primitivních) Složených (rekurzivně složených z primitivních a dalších složených objektů) Jednotný přístup k atomickým i složeným objektům (kontejnery) Motivace Grafický editor Vytváření složitých schémat z primitivních komponent Použití těchto schémat na vytvoření ještě složitějších schémat Hloupá implementace – bez Composite Definuje třídy pro grafická primitiva (Line, Text, Rect) a třídy jako jejich kontejnery Kód používající tyto třídy musí rozlišovat primitiva a kontejnery Lepší implementace - Composite pattern Klient nemusí rozlišovat
Příklad: Model tříd Struktura: Abstraktní třída Graphic (Component) Reprezentuje primitivní třídy i kontejner Deklaruje metodu Draw() Deklaruje metody pro správu potomků (případně poskytuje defaultní definice) Primitivní podtřídy (Leaf) přímo vykonávají své metody Draw() Kontejnery (Composite) definují Draw() a metody pro správu potomků Delegují Draw() na všechny své potomky Component Composite Leaves
Použití GUI Komponenty-kontejnery na komponenty Obecné (Panel, Container) Speciální (menu, strom) AWT, Swing Reprezentace XML Reprezentace aritmetického výrazu Nebo programu (složený příkaz) Struktura souborů a adresářů java.awt
Composite – obecná struktura
Composite – účastníci Component (Graphic) Deklaruje interface pro objekty v kompozici Implementuje defaultní chování společného interface Deklaruje interface pro správu a přístup k potomkům Uchovává referenci na předka (volitelně) Leaf (Rectangle, Line, Text, atd.) Reprezentuje listové objekty v kompozici, nemá potomky Definuje chování primitivních objektů Composite (Picture) Definuje chování komponent, které potomky mají Uchovává potomky ve vhodné datové struktuře a umožňuje jejich správu Client Používá objekty v kompozici přes interface Component
Důsledky Definuje hierarchii tříd skládajících se z primitivních a složených objektů, rekurzivně Kdykoliv klientský kód předpokládá primitivní objekt, může také použít složený objekt Zjednodušuje klienta Může zacházet stejně se složenými i primitivními objekty Nemusí je rozlišovat – jednodušší kód Umožňuje jednoduché přidávání nových komponent Nově definované Composite nebo Leaf třídy automaticky fungují s existujícími strukturami i klientským kódem Na klientovi se nemusí nic měnit Může Váš design učinit až příliš obecným Jednoduché přidání nových komponent naopak omezuje možnosti, jak omezit typy komponent ve složeném objektu Je potřeba použít run-time kontroly
Implementace – „a co děti?“ Deklarace operací s potomky ( add, remove, getChild ) Operace pro správu dětí nemají smysl pro listy – kam s nimi? V root Component – přináší transparentnost, méně bezpečné Preferovaná varianta podle GoF „Divné“ operace v listech, řešit výjimkami V Composite – přináší bezpečnost, odnáší transparentnost Chyby (snaha přidat potomek k listu) zachyceny už při kompilaci Leaf a composite mají jiný interface Ve třídě Component definovat virtuální metodu GetComposite() vracející defaultně null Datová struktura pro uchování potomků Pole, spojové seznamy, stromy, hashovací tabulky, … Nedefinovat kolekci ve třídě Component ! Pořadí potomků Composite může definovat pořadí potomků, které se může v aplikaci využít Příklad v Graphics: front-to-back order Použít vhodnou datovou strukturu Přizpůsobit interface metod pro přístup a správu potomků, lze použít Iterator pattern
Implementace Explicitní reference na rodiče Jednodušší pohyb po stromové struktuře, použití ve vzoru Chain of Responsibility Referenci definovat v Component Leaf a Composite ji dědí spolu s funkcemi s ní případně spojenými Zajistit, aby reference byla aktuální Měnit ji pouze když je dítě přidáváno nebo odstraňováno z Composite (Add, Remove) Mazání komponent Mazaný Composite by měl být zodpovědný za smazání svých dětí Výjimka: sdílené komponenty Při mazání dítěte je potřeba jej odebrat z rodičovy kolekce Composite.Operation() Nemusí se delegovat pouze na potomky Př.: Composite.Price() vrací cenu objektu: rekurzivně zavolá na všechny své děti a jejich návratové hodnoty sečte
Implementace - vylepšení Cachování pro zlepšení výkonnosti Při častém procházení nebo prohledávání velkých kompozic je dobré evidovat informace z posledního průchodu / hledání Composite cachuje informace o dětech Př.: Picture cachuje ohraničenou viditelnou oblast svých dětí, během překreslování je nemusí procházet znovu Je třeba invalidovat cache při změně Jsou potřeba reference na předky a interface k invalidaci cache Sdílení komponent Sdílet komponenty např. kvůli úspoře paměti Složitější, když komponenta může mít nejvýše jednoho rodiče Řešení – děti mají více rodičů - DAG Problémy s víceznačností, když je požadavek propagován směrem nahoru ve struktuře
Příklad class Equipment { protected: string name; public: Equipment(string name):name(name) {} virtual ~Equipment() {} virtual void add(Equipment* equipment) =0; virtual void remove(Equipment* equipment) =0; virtual int getTotalPrice() =0; string getName() {return this->name;} }; Component (abstract child methods) class Keyboard : public Equipment { private: int price; public: Keyboard(string name, int price) : Equipment(name), price(price) {} int getTotalPrice() {return this->price;} void add(Equipment*) { throw RtExc(); } void remove(Equipment*) { throw RtExc(); } }; Leaf (not implemented child methods)
Příklad class PC : public Equipment { private: List children; public: PC(String name) : Equipment(name) {}; ~PC() { for (Equipment* equipment : children) delete equipment; } int getTotalPrice() { int total = 0; for (Equipment* equipment : children) total += equipment->getTotalPrice(); return total; } void add(Equipment* equipment) { children.add(equipment); } void remove(Equipment* equipment) {children.remove(equipment);} }; Composite Implementované child methods Využití potomků pro výpočet celkové částky
Související vzory Command Složený MacroCommand Decorator Často používán s Composite Interface třídy Component z Decoratoru navíc obsahuje metody pro práci s dětmi Flyweight Sdílení komponent Iterator Procházení potomků různými způsoby Visitor Lokalizuje operace a chování, které by jinak byly rozprostřeny v Composite i Leaf třídách
Shrnutí Zakrytí rozdílu mezi objektem a kolekcí těchto objektů Zjednodušení práce klienta Možnost cacheování výsledků získaných z potomků Urychlení běhu programu Struktura
Composite Zdroje Příklady použití in-rdfowl.html Vše ostatní GoF C# návrhové vzory, Judith Bishopová