Decorator Radek Zikmund NPRG024, LS 2016/17
Motivační příklad GUI Toolkit
Motivační příklad – GUI Toolkit Několik GUI prvků, několik možností dekorace posuvník – horní / spodní rámeček (různé šířky) Soustředíme se zatím jenom na TextView 3
Motivační příklad – GUI Toolkit GUI Toolkit obsahuje komponentu TextView prosté zobrazení textu Chceme instance této komponenty „dekorovat“ přidat posuvník přidat rámeček apod. Hodilo by se přitom: mít k dispozici také původní TextView bez dekorací kombinovat vlastnosti mezi sebou posuvník + rámeček přidávat některé vlastnosti vícenásobně dvojitý rámeček přidávat/odebírat konkrétní vlastnosti za běhu „vrstevnatá struktura“, Možná umět prohazovat vrstvy => posuvník uvnitř, nebo vně rámečku 4
Motivační příklad – GUI Toolkit 1. pokus o řešení: „supertřída“ obsahuje stavy (flagy) pro všechny možné dekorace vykreslovací metoda kontroluje přítomnost jednotlivých dekorací class TextView { private bool isBordered; // Is TextView bordered? private int borderWidth; // Used only by bordered TextViews. private bool isScrollable; // Is TextView scrollable? private int scrollBarPosition; // Used only by scrollable TextViews. // ... public void Draw() /* Main drawing logic here. */ if (isBordered) /* Border drawing logic here. */ } if (isScrollable) /* Scrollbar drawing logic here. */ 5
Motivační příklad – GUI Toolkit 1. pokus o řešení: „supertřída“ můžeme instancím přidávat/odebírat vlastnosti za běhu nemusíme dekorovat vůbec netřeba rozlišovat mezi obyčejnou a dekorovanou instancí můžeme kombinovat vlastnosti nemůžeme jednu vlastnost použít vícekrát instance ví, že je dekorována silně neflexibilní řešení nerozšiřitelné bez modifikace TextView Každou třídu musíme modifikovat zvlášť TextView Button Label Grid Panel … ----- Meeting Notes (3/22/14 23:13) ----- Je to ošklivé Problém více odekorovaných tříd lze vyřešit společným předkem přesto však problémové Problém – horizontální a vertikální posuvník duplikace kódu Nemožnost si zvolit pořadí dekorování 2. Návrh: využít nějakou formu dědičnosti 6
Motivační příklad – GUI Toolkit 2. pokus o řešení: dědičnost bázová třída – TextView odvozené – BorderedTextView, ScrollableTextView, BorderedScrollableTextView class TextView { // ... fields and other necessary textview logic public virtual void Draw() { /*...*/ } } class BorderedTextView : TextView { // ... border-specific data and logic public override void Draw() { /*...*/ } class ScrollableTextView : TextView { // ... scrolling-specific data and logic class BorderedScrollableTextView : ScrollableTextView { // ... border-specific data and logic, probably copied from BorderedTextView 7
Motivační příklad – GUI Toolkit 2. pokus o řešení: dědičnost statické Nemožno měnit vlastnosti instance za běhu pro každou kombinaci vlastností je třeba vytvořit novou třídu Stále si nemůžeme libovolně zvolit kombinaci/pořadí BorderedScrollableBorderedTextView ... vede k explozi tříd (n vlastností → 2n tříd) podstatně zvyšuje komplexitu systému Měnit vlastnosti za běhu znamená vytvořit nový objekt, překopírovat data a vyměnit je Co s referencemi na původní instanci? Dokážu je všechny opravit? Vícenásobná dědičnost v C++ SPOLEČNÝ PŘEDEK TextView!!! překrytí názvů? nedá se to udělat líp? Návrat k původní vrstevnaté myšlence 8
Motivační příklad – GUI Toolkit 3. Návrat k původní myšlence vrstev Delegace vykreslování rámečku/posuvníku na jiný objekt Myšlenka: Oddělit jednotlivé dekorace od původního objektu Orámečkovaný textview má dvě části: textview a rámeček Rámeček si bude držet referenci na textview, který dekoruje Nový objekt se bude starat jenom o vykreslení rámečku / posuvníku, vykreslení samotného textview nechá na textview samotném 9
Motivační příklad – GUI Toolkit 3. pokus o řešení: Decorator Společný (abstraktní) předek – def. interface pro kreslení Konkrétní vizuální komponenta Reference na dekorovaný objekt Delegace vykreslování na dekorovaný objekt Abstraktní dekorátor Konkrétní dekorátory vyjma delegace implementují navíc své vlastní vykreslování Konkrétní dekorátor 10
Motivační příklad – GUI Toolkit 3. pokus o řešení: Decorator public interface IVisualComponent { void Draw(); } class TextView : IVisualComponent public override void Draw() { // draw window // ... abstract class Decorator : IVisualComponent protected IVisualComponent vc; // decorated component public Decorator(IVisualComponent vc) { this.vc = vc; public virtual void Draw() { vc.draw(); //delegation class ScrollDecorator : Decorator { public ScrollDecorator(IVisualComponent vc) : base(vc) { } public override void Draw() { base.Draw(); // draw component DrawScrollBar(); } private void DrawScrollBar() { /*...*/ } // scrolling logic implementation // ... class BorderDecorator : Decorator { public BorderDecorator(IVisualComponent vc) drawScrollBar(); private void DrawBorder() { /*...*/ } 11
Motivační příklad – GUI Toolkit 3. pokus o řešení: Decorator dekorátory přidávají jednotlivé vlastnosti (dekorace) můžeme instancím přidávat/odebírat vlastnosti za běhu máme k dispozici i obyčejný TextView dekorace jsou navzájem nezávislé lze je libovolně kombinovat lze je používat i vícekrát je to transparentní z hlediska klienta není rozdíl mezi obyčejným a dekorovaným TextView TextView o dekoracích vůbec neví VisualComponent licenseTextView = new BorderDecorator( new ScrollDecorator( new TextView())); // somewhere else: licenseTextView.Draw(); Pro odebírání vlastností potřebujeme podporu v rozhranní! 12
std::reverse_iterator Motivační příklad std::reverse_iterator
Motivační příklad – std::reverse_iterator Příklad minimalistické implementace template <typename Iter> class reverse_iterator : public std::iterator< typename Iter::iterator_category, typename Iter::value_type, typename Iter::difference_type, typename Iter::pointer, typename Iter::reference> { private: Iter current; // decorated iterator public: explicit reverse_iterator(Iter&& p) : current{std::forward<Iter>(p)} {} void operator=(const reverse_iterator& other) { current = other.current; } typename Iter::reference operator*() const { auto tmp = current; return *--tmp; reverse_iterator& operator++() { --current; return *this; bool operator!=(const reverse_iterator& other) const { return current != other.current; }; template <typename Iter> typename reverse_iterator<Iter> make_reverse(Iter&& i) { return reverse_iterator<Iter>{std::forward<Iter>(i)}; } void print_elements(Iter begin, Iter end) { for (; begin != end; ++begin) std::cout << *begin << " "; int main(int argc, char* argv[]) { std::vector<int> v = { 1,2,3,4,5,6,7,8,9,10 }; print_elements( make_reverse(v.end()), make_reverse(v.begin())); std::cout << std::endl; 14
Decorator – shrnutí Strukturální vzor Rozšiřuje objekty o dodatečné chování rozšiřuje konkrétní objekty, ne třídy rozšiřuje objekt dynamicky, tj. za běhu Upřednostňuje kompozici objektů před dědičností Delegace volání na dekorovaný objekt + vlastní přidaná funkcionalita vlastní přidané chování může být před i za delegovaným voláním 15
Decorator – struktura a účastníci Component – def. rozhraní pro objekty, které je možné dynamicky rozšiřovat ConcreteComponent – def. objekt, který je možné dynamicky rozšířit Decorator – abstraktní předek všech dekorátorů obsahuje refenci na objekt, který dekoruje všechna volání deleguje na dekorovaný objekt ConcreteDecorator – přidává dodatečné chování komponentě 16
Decorator – implementace Použití typicky pomocí řetězení konstruktorů: Rozhraní dekorátoru musí být shodné s rozhraním dekorovaného objektu dědění od společného předka nebo implementace společného interface Abstraktní dekorátor lze vynechat za předpokladu, že potřebujeme přidat pouze jediné rozšíření často v případě, kdy potřebujeme rozšířit existující kód delegování na komponentu se pak děje přímo v tomto jediném dekorátoru Společný předek (Component) by měl zůstat odlehčený definice rozhraní, nikoli uložení dat jinak hrozí, že dekorátory budou příliš těžkotonážní Component c = new ConcreteDecoratorA( new ConcreteDecoratorB( new ConcreteComponent(...))); c.Operation(); 17
Decorator – výhody a nevýhody vyšší flexibilita pro přidávání funkcionality než při statickém dědění vlastnosti lze přidávat/odebírat za běhu aplikace několikanásobné použití stejné dekorace transparentnost není třeba předvídat všechny potřeby klienta jednoduché inkrementální přidávání funkcionality Nevýhody komponenta a její dekorovaná verze nejsou identické dekorátor se chová jako transparentní zapouzdření při použití dekorátorů nespoléhat na identitu objektů! mnoho podobných (malých) objektů potenciálně horší orientace v kódu dlouhé řetězce dekorátorů mohou mít dopad na výkon 18
Reálně využívané Decoratory Grafické toolkity Java Swing System.Windows.Controls Čtení vstupu, zapisování výstupu System.IO.Stream java.io Component ConcreteComponent Decorator DataInputStream dis = new DataInputStream( new GzipInputStream( new BufferedInputStream( new FileInputStream("file.gz")))); dis.Read(...); ConcreteDecorator 19
Související vzory Adapter Composite Strategy Decorator mění pouze chování objektu, ne jeho rozhraní Adapter dá objektu zcela nové rozhraní Composite Decorator lze technicky vnímat jak zdegenerovaný Composite s jedinou komponentou Decorator přidává dodatečné chování není určen pro agregaci objektů Strategy Decorator umožňuje měnit skin (povrch, kůži...) objektu v zásadě obaluje nějaký objekt a mění při tom jeho chování zatímco Strategy umožňuje měnit „vnitřnosti“ (guts) objektu komponenta ve Strategy ví o možných extenzích, u Decoratoru nikoli: 20
Dotazy?
Děkuji za pozornost