Visitor
Visitor - motivace Existující struktura typů Unit Character Main Hero Monster Environment Wall Magic Wall Stone Tree Mnoho nezávislých operací Draw Collision Animation
Visitor - motivace Každá dvojice typ a operace má jiný kód Main Hero, Draw Wall, Collision Stone, Collision... Implementace operace v jednom modulu DrawHero DrawWall DrawStone... Předpokládáme stabilní struktura typů se společným předkem přibývající operace
Visitor – Double Dispatch Intermezzo Single Dispatch C++, C#, Java Jméno funkce + odkaz na objekt Podpora v jazyce pomocí virtuálních funkcí Double Dispatch CLOS, Haskell, Dylan Jméno funkce + odkaz na objekt 1 + odkaz na objekt 2 příklad: výpočet obsahu průniku dvou geometrických obrazců Možné řešení: pomocí vzoru Visitor hrubou silou šablony + typelisty type_info + std::map ...
Visitor - motivace Možné řešení bez visitoru - operace v třídách Výhody jednoduché intuitivní Nevýhody pracné přidávání metod funkčnost rozházená po třídách nezachovává se stav MagicWall draw() collision() animation()... Environment draw() collision() animation()... Wall draw() collision() animation()... Stone draw() collision() animation()... …… Client
Visitor - motivace Možné řešení bez visitoru - špagetový kód Výhody kód na jednom místě Nevýhody nepřehledný, opakující se kód mnoho switch / if-else-if bloků pomalé, snadné zapomenout na nějaký typ void printStatistics(List elements){ foreach (Unit e in elements) { if (e is Wall) { //... } else if (e is MagicWall) { //... } else if (e is Stone) { //... }
Visitor - motivace Řešení s použitím Visitoru Environment accept(Visitor v) Wall accept(Visitor v) MagicWall accept(Visitor v) Stone accept(Visitor v) v.visitMagicWall(this);v.visitStone(this); Object structure (Traverser) v.visitWall(this); Visitor visitWall(Wall e) visitMagicWall(MagicWall e) visitStone(Stone e) DrawVisitor visitWall(Wall e) visitMagicWall(MagicWall e) visitStone(Stone e) CollisionVisitor visitWall(Wall e) visitMagicWall(MagicWall e) visitStone(Stone e) Potomci Visitora jednoduše přidávají další operace bez nutnosti změny v typech struktury typů. Každý uzel v metodě accept zavolá Visitora „na návštěvu“
Visitor Visitor obecně Visitor visitA(ElementA) visitB(ElementB) Visitor1 visitA(ElementA) visitB(ElementB) Visitor2 visitA(ElementA) visitB(ElementB) AbstractElement accept(Visitor) ElementA accept(Visitor) ElementB accept(Visitor) v.visitA(this);v.visitB(this); Object structure (Traverser) Client
Visitor - účastníci Přehled účastníků Visitor interface (nebo abstraktní třída), který musí implementovat konkrétní Visitory definuje metody visit pro všechny typy elementů může využívat i overloading funkcí: Visit( ElementA& ); Visit( ElementB& ); Visitor1, Visitor2 konkrétní Visitory (implementují rozhraní Visitor) přidávají novou funkcionalitu do existující struktury AbstractElement interface (nebo abstraktní třída) pro typy, které mohou být navštíveny Visitorem definuje abstraktní metodu accept ElementA, ElementB konkrétní elementy odvozené od AbstractElement implementují metodu accept (uvnitř které pozve předaného Visitora na návštěvu) Object structure (traverser) umí procházet strukturu elementů na každém elementu zavolá metodu accept
Visitor - vlastnosti Výhody snadno přidám novou operaci na třídě stačí napsat nového Visitora není třeba měnit objekty hierarchie Visitory mohou být implementovány jako zásuvné moduly zapouzdření souvisejících operací Visitor skrývá specifika algoritmu udržování kontextu při průchodu strukturou objektů uvnitř Visitoru Visitor může mít vnitřní stav (data) nemusí se předávat parametrem, nebo v globálních hodnotách vnitřní stav může ovlivnit prováděné operace
Visitor - důsledky a souvislosti Nevýhody přidání nového typu objektu většinou znamená úpravu všech Visitorů nasazení při častěji měnící se struktuře je nevhodné porušení zapouzdření objektů, nad nimiž se operuje Visitor může potřebovat pracovat s interním stavem obtížné zavedení do hotového kódu složitějsí kód explicitní získávání návratové hodnoty
Visitor – sekvenční diagram Sekvenční diagram
Visitor - procházení struktury Jakým způsobem lze procházet strukturu objektů visitorem? Kde bude kód umístěn: klient více práce pro klienta klient má možnost plně řídit, koho Visitor navštíví struktura rekurzivní volání metody accept na potomky při použití vzoru Composite Iterator nelze použít, pokud objekty nemají společného předka kód na procházení je na jediném místě Visitor elementy struktury nemusí mít společného předka komplexnější algoritmy průchodu strukturou duplikace kódu na procházení v každém Visitoru
Visitor – implementace C# interface IElement { void Accept(IVisitor visitor); } interface IVisitor { void Visit(IElement element); void Visit(Wall binary); void Visit(Stone value); void Visit(Map value); } vše zachytávající funkce společný interface class Wall:IElement { public readonly int Number;... public void Accept(IVisitor visitor) { visitor.Visit(this); } class Wall:IElement { public readonly int Number;... public void Accept(IVisitor visitor) { visitor.Visit(this); } accept na jednoduchém objektu class Map: IElement { public readonly char Operator; public readonly List staticObjects; public readonly List characters;... public void Accept(IVisitor visitor) { foreach(var e in this.staticObjects) { e.Accept(visitor); } foreach(var e in this.staticObjects) { e.Accept(visitor); } visitor.Visit(this); } class Map: IElement { public readonly char Operator; public readonly List staticObjects; public readonly List characters;... public void Accept(IVisitor visitor) { foreach(var e in this.staticObjects) { e.Accept(visitor); } foreach(var e in this.staticObjects) { e.Accept(visitor); } visitor.Visit(this); } accept na složeném objektu
Visitor – implementace C# class CollisionVisitor : IVisitor { private Hero hero; // whether hero collides private bool status; public void Visit(IElement element) { if(!(hero.x + hero.width < element.x || hero.x > element.x + element.width) || !(hero.y + hero.height < element.y || hero.y > element.y + element.height)){ status = false; } }... } class DrawVisitor : IVisitor { private Dictionary bitmaps; public void Visit(IElement element) { throw new Exception("Unknown object"); } public void Visit(Wall element) { if(!bitmaps.ContainsKey(element)) bitmaps[element] = LoadWallBitmap(); Window.Draw(this.bitmaps[element]); } public void Visit(Stone value) { if(!bitmaps.ContainsKey(element)) bitmaps[element] = LoadStoneBitmap() Window.Draw(this.bitmaps[element]); }... }
interface IVisitor { void Visit(IElement element); void Visit(Wall binary); void Visit(Stone value); void Visit(Map value); void Visit(BabaYagaMonster value); } Visitor – problém s rozšiřitelností Chtěli bychom přidat BabaYagaMonster
class DrawVisitor : IVisitor { private Dictionary bitmaps; public void Visit(IElement element) { throw new Exception("Unknown object"); } public void Visit(Wall element) { if(!bitmaps.ContainsKey(element)) bitmaps[element] = LoadWallBitmap(); Window.Draw(this.bitmaps[element]); } public void Visit(Stone value) { if(!bitmaps.ContainsKey(element)) bitmaps[element] = LoadStoneBitmap() Window.Draw(this.bitmaps[element]); } public void Visit(BabaYagaMonster value) { if(!bitmaps.ContainsKey(element)) bitmaps[element] = LoadBabaYagaBitmap() Window.Draw(this.bitmaps[element]); }... } interface IVisitor { void Visit(IElement element); void Visit(Wall binary); void Visit(Stone value); void Visit(Map value); void Visit(BabaYagaMonster value); } Visitor – problém s rozšiřitelností Chtěli bychom přidat BabaYagaMonster class CollisionVisitor : IVisitor { private Hero hero; // whether hero collides private bool status; public void Visit(IElement element) { if(!(hero.x + hero.width < element.x || hero.x > element.x + element.width) || !(hero.y + hero.height < element.y || hero.y > element.y + element.height)){ status = false; } public void Visit(BabaYagaMonster value) { // No implementatation }... }
Visitor – Double dispatch C#... IVisitor visitor = new ConcreteVisitor(); foreach(IElement el in elements) { visitor.Visit(el); } class ConcreteVisitor:IVisitor { void Visit(IElement w){... } void Visit(UnaryOperator w){... } interface IVisitor { void Visit(IElement w); } Řešení ?
Visitor – Double dispatch C#... IVisitor visitor = new ConcreteVisitor(); foreach(IElement el in elements) { visitor.Visit(el); } class ConcreteVisitor:IVisitor { void Visit(IElement w){... } void Visit(UnaryOperator w){... } interface IVisitor { void Visit(IElement w); } Řešení ? K výběru funkce dochází již za překladu!
Visitor – Double dispatch C#... IVisitor visitor = new ConcreteVisitor(); dynamic dispatchedVisitor=visitor; foreach(IElement el in elements) { dispatchedVisitor.Visit((dynamic) el); } class ConcreteVisitor:IVisitor { void Visit(IElement w){... } void Visit(UnaryOperator w){... } interface IVisitor { void Visit(IElement w); } Řešení !
Visitor – Double dispatch C#... IVisitor visitor = new ConcreteVisitor(); dynamic dispatchedVisitor=visitor; foreach(IElement el in elements) { dispatchedVisitor.Visit((dynamic) el); } class ConcreteVisitor:IVisitor { void Visit(IElement w){... } void Visit(UnaryOperator w){... } interface IVisitor { void Visit(IElement w); } Ale... Dynamické typování je výrazně pomalejší
Visitor - Shrnutí Používáme když máme stabilní strukturu typů chceme přidávat nezávislé funkce nad typy struktury Není vhodné když struktura typů není stabilní přidání nového typu je pracné Výhody přehledný kód rozšiřitelnost funkcí nad typy zapouzdření kontextu dané funkce Visitor částečně nahrazuje chybějící podporu Double Dispatch v jazycích jako jsou C++, C#, Java Obecná implementeace techniky Multi Dispatch se nazývá Multimethods
Visitor - použití v praxi Příklady XML parsery (např. SAX) parser.parse(file, handler) startElement() endElement() character() Stromové struktury visitNode visitLeaf Překladače operace nad syntaktickým stromem