Composite “ Spojuj a panuj ”
Zakladní vlastnosti Výslovnost kompozit, ne kompozajt Účel Popisuje, jak postavit strukturované hierarchie tříd, v níž s jednotlivými komponentami lze pracovat úplné stejně jako skupinami komponentů Hierarchie je složená ze dvou druhů objektů Atomických (primitivních) Kontejnerů (rekurzivně složených z primitivních a dalších složených objektů) Typické operace na komponentách zahranují: Přidavání Odstraňování Vyhledávání Zpracování
Motivace – grafický editor 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 Naivní implementace – bez Composite Definuje třídy pro grafická primitiva (Line, Text, Rect) a třídu jako kontejner Kód používající tyto třídy musí rozlišovat primitivy a kontejnery Lepší implementace - Composite pattern (Jak? – Hledáme další slajd) Klient nemusí rozlišovat
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 Dobrá implementace grafického editoru Component Composite Leaves
Composite – obecná struktura pozor! diskuse později
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
Composite – použití – výjimky Výkon je kritický parametr pro aplikace Jestli je možná realizace bez kompozitu Neplánujete rozšiřování hierarchie a funkčnosti modulu Programujete v jazyce s dynamickým typováním Existují elegantnější způsoby řešení Nekontrolujete životní cyklus komponent Zpracování tříd Composite je stochastický proces Můzou vzniknut cyklické závislosti mezí potomky Kdy používání Composite není dobrým nápadem?
Problémy s implementací Deklarace metod s potomky ( add, remove, getChild ) metody pro správu dětí nemají smysl pro listy – kam s nimi? metody ve společném rozhraní nepodporované operace v listech řešit výjimkami preferovaná varianta dle GoF z dnešního pohledu nevhodné řešení náročné výjimky, kontrola až za běhu metody pouze v Composite odstranění operace z rozhraní chyby (snaha přidat potomek k listu) zachyceny už při kompilaci nepraktické a neflexibilní - dva druhy rozhraní doplnění rozhraní o GetComposite vrací defaultně nullptr - listy znalost z kontextu nebo lze otestovat 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
Příklad realizace public abstract class Equipment :Idisposable { protected string _name; public Equipment(string name) { _name = name; } public string Name { get { return _name; } } public Equipment Root { set; get; } public abstract int GetTotalPrice(); public abstract void Dispose(); } Component definuje abstraktní metody pro děti public class EquipmentUnit: Equipment { private int _price; public EquipmentUnit(string name, int price) :base(name) { _price = price; } public override int GetTotalPrice() { return _price; } public override void Dispose() { } } Leaf implementuje abstraktní metody
Příklad realizace - prodloužení public class HWComponents: Equipment { private List _components; public HWComponents(string name) : base(name) { _components = new List (); } public override int GetTotalPrice() { return _components.Sum(item => item.GetTotalPrice()); } public HWComponents Add(Equipment item) { item.Root = this; _components.Add(item); return this; } public HWComponents Remove(Equipment item) { _components.RemoveAll(elem => { elem.Root = null; return elem == item; }); return this; } public override void Dispose() { _components.ForEach(item => item.Dispose()); _components.Clear(); } Composite Implementované metody potomků Využití potomků pro výpočet celkové částky
Příklad realizace - prodloužení var systemUnit = new HWComponents("System unit").Add(new EquipmentUnit("CPU", 4000)).Add(new EquipmentUnit("Motherboard", 3500)).Add(new EquipmentUnit("Hard drive", 2000)).Add(new EquipmentUnit("RAM memory", 1000)).Add(new EquipmentUnit("Trunk", 500)); Equipment PC = new HWComponents("Personal Computer").Add(systemUnit).Add(new EquipmentUnit("Display", 2500)).Add(new EquipmentUnit("Keyboard", 250)); Console.WriteLine(PC.GetTotalPrice()); // PC.Dispose(); Client
Implementace – specifika Explicitní reference na rodiče Jednodušší pohyb po stromové struktuře, použití ve vzoru Chain of Responsibility Pomáhá mazání komponentů 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) Composite.Operation() Nemusí se delegovat pouze na potomky Př.: Equipment.GetTotalPrice() vrací cenu objektu: rekurzivně zavolá na všechny své děti a jejich návratové hodnoty sečte
Implementace – specifika Datová struktura pro uchování potomků Pole, spojové seznamy, stromy, hashovací tabulky, … Definice kolekci ve třídě Component není potřebná 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 rozhraní metod pro přístup a správu potomků lze použít vzor Iterator
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 používání sbírky rodičů
Composite – použití GUI Komponenty a kontejnery Java: AWT, Swing, JSP… atd.NET: WinForms, WPF, Silverlight... atd C++: Qt, GTK+… atd XML a podobné formáty Parsing výrazů Reprezentace, interpretace Struktura souborů a adresářů...atd Téměř každá stromová struktura v OOP používá vzor Composite java.awt TODO: aktualizovat, doplnit reálné
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 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 MVC, MVVM, MVP Stavení View-komponentů
Shrnutí Skrývá rozdíl mezi objektem a kolekcí těchto objektů Definuje hierarchie tříd Zjednodušení práce klienta Možnost zlepšení pomocí cacheování výsledků získaných z potomků Urychlení běhu programu Composite není všelék ale velmi užitečný návrhový vzor Jednoduchá struktura