Differences

This shows you the differences between two versions of the page.

Link to this comparison view

poo-ca-cd:arhiva:laboratoare:2024:visitor [2025/09/27 10:42] (current)
florian_luis.micu created
Line 1: Line 1:
 +===== Laboratorul 6: Visitor pattern =====
 +
 +**Video introductiv:​** [[https://​www.youtube.com/​watch?​v=_mfLYYInv6c| link ]]
 +
 +==== Obiective ====
 +
 +  * Prezentarea design pattern-ului Visitor și familiarizarea cu situațiile în care acesta este util de aplicat
 +
 +==== Visitor Design Pattern ====
 +
 +<​note>​Design pattern-urile reprezintă soluții generale și reutilizabile ale unei probleme comune în design-ul software. Un design pattern este o descriere a soluției sau un template ce poate fi aplicat pentru rezolvarea problemei, nu o bucată de cod ce poate fi aplicată direct. În general, pattern-urile orientate pe obiect arată relațiile și interacțiunile dintre clase sau obiecte, fără a specifica însă forma finală a claselor sau a obiectelor implicate.</​note> ​
 +
 +//Visitor// este un **behavioural design pattern** ce oferă posibilitatea de a adăuga în mod __extern__ funcționalități pe o întreagă ierarhie de clase, fără să fie nevoie să modificăm efectiv structura acestora. ​
 +
 +Acest pattern este behavioural (//​comportamental//​) pentru că definește modalități de comunicare între obiecte. ​
 +
 +=== Aplicabilitate ===
 +
 +Pattern-ul **Visitor** este util când:
 +  * se dorește prelucrarea unei //structuri complexe//, ce cuprinde mai multe obiecte de //tipuri diferite//
 +  * se dorește definirea de operații specifice pentru aceeași structură, fără a polua interfețele claselor implicate, cu multe detalii specifice algoritmilor. Vizitatorul centralizează logica comună, păstrând în același timp detaliile specifice în interiorul acestuia.
 +  * ** clasele ce se doresc prelucrate se modifică rar, în timp ce operațiile de prelucrare se definesc des**. Vizitatorul permite adăugarea de noi funcționalități fără modificarea claselor existente.
 +
 +=== Structură === 
 +
 +{{ .:​visitor:​visitor.png?​680 | Componente pattern Visitor }}
 +
 +Structura design pattern-ului "​Visitor"​ este formată din următoarele componente:
 +
 +**Client:**
 +   * Este clasa consumatoare a design pattern-ului "​Visitor"​.
 +   * Are acces la obiectele din structura de date și poate instrui aceste obiecte să accepte un "​Visitor"​ pentru a realiza prelucrările corespunzătoare.
 +   * Exemplu: O aplicație care procesează diferite tipuri de elemente într-o structură de date complexă.
 +
 +**Visitor:​**
 +   * Este o interfață sau o clasă abstractă folosită pentru a declara operațiile de vizitare pentru toate tipurile de clase vizitabile.
 +   * Conține metode de vizitare corespunzătoare fiecărui tip de clasă vizitabilă.
 +   * Exemplu: Interfața Visitor cu metodele visit(ElementA elementA), visit(ElementB elementB), etc.
 +
 +**ConcreteVisitor:​**
 +   * Pentru fiecare tip de "​Visitor",​ toate metodele de vizitare definite în "​Visitor"​ trebuie implementate.
 +   * Fiecare "​Visitor"​ este responsabil pentru diferite operații.
 +   * Exemplu: Clasa ConcreteVisitorA implementând interfața Visitor cu metodele sale specifice pentru tratarea diferitelor tipuri de elemente.
 +
 +**Visitable:​**
 +   * Este o interfață pentru obiecte pe care pot fi aplicate operațiile.
 +   * Această operație permite unui obiect să fie "​vizitat"​ de către un obiect "​Visitor"​.
 +   * Exemplu: Interfața Visitable cu metoda accept(Visitor visitor).
 +
 +**ConcreteVisitable:​**
 +   * Aceste clase implementează interfața sau clasa Visitable și definesc operația accept.
 +   * Prin intermediul acestei operații, obiectul "​Vizitabil"​ primește un obiect "​Visitor"​.
 +   * Exemplu: Clasele ConcreteElementA,​ ConcreteElementB,​ etc., care implementează interfața Visitable și definesc metoda accept.
 +
 +
 +<note tip> Flow-ul aplicării acestui pattern:
 +  -  Când un client dorește să efectueze operații pe obiectele vizitabile, el creează un obiect vizitator corespunzător,​ le "​vizitează"​ apelând metoda accept, iar fiecare obiect vizitabil interacționează cu vizitatorul prin intermediul metodelor visit.
 +  -  Acest pattern oferă o modalitate de a separa algoritmii de obiectele pe care operează, facilitând extinderea și adăugarea de noi operații fără a modifica clasele obiectelor vizitabile.
 +</​note>​
 +
 +<note important>​
 +
 +**Visitor și structurile de date**
 +
 +Aparent, folosirea lui //accept// este artificială. De ce nu declanșăm vizitarea unui obiect, apelând **direct** //​v.visit(e)//​ atunci când dorim vizitarea unui obiect oarecare? Răspunsul vine însă chiar din situațiile în care vrem să folosim pattern-ul; vrem să lăsăm structura internă a colecţiei să facă aplicarea vizitatorilor. Cu alte cuvinte, vizitatorul se ocupă de fiecare obiect în parte, iar colecţia îl "​plimbă"​ prin elementele sale. De exemplu, când dorim să vizităm un arbore:
 +  * declanşarea vizitării se va face printr-un apel ''​accept''​ pe un prim obiect (e.g. rădăcina arborelui)
 +  * elementul curent este vizitat prin apelul ''​v.visit(this)''​
 +  * pe lângă vizitarea elementului curent, este necesar sa declanşăm vizitarea //tuturor elementelor accesibile din elementul curent// (e.g. nodurile-copil din arbore etc). Realizăm acest lucru apelând ''​accept''​ pe //fiecare// dintre aceste elemente. Acest comportament depinde de logica structurii.
 +</​note>​
 +
 +=== Scenariu Visitor ===
 +
 +Pentru a înţelege mai bine motivaţia din spatele design-pattern-ului //​Visitor//,​ să considerăm următorul exemplu.
 +
 +== Before ==
 +
 +Fie ierarhia de mai jos, ce defineşte un angajat (//​Employee//​) şi un şef (//​Manager//​),​ văzut, de asemenea, ca un angajat:
 +
 +<code java Test.java>​
 +class Employee {
 +        String ​ name;
 +        float   ​salary; ​       ​
 +        public Employee(String name, float salary) {
 +                this.name ​      = name;
 +                this.salary ​    = salary;
 +        }
 +        public String getName() {
 +                return name;
 +        }
 +        public float getSalary() {
 +                return salary;
 +        }
 +}
 +class Manager extends Employee {        ​
 +        float bonus;
 +        public Manager(String name, float salary) {
 +                super(name, salary);
 +                bonus = 0;
 +        }        ​
 +        public float getBonus() {
 +                return bonus;
 +        }
 +        public void setBonus(float bonus) {
 +                this.bonus = bonus;
 +        }
 +}
 +public class Test {
 +        public static void main(String[] args) {
 +                Manager manager;
 +                List<​Employee>​ employees = new LinkedList<​Employee>​(); ​               ​
 +                employees.add(new Employee("​Alice",​ 20));
 +                employees.add(manager= new Manager("​Bob",​ 1000));
 +                manager.setBonus(100);​
 +        }
 +}
 +</​code>​
 +
 +Ne interesează să interogăm toţi angajaţii noştri asupra //venitului lor total//. Observăm că:
 +  * angajaţii obişnuiţi au salariul ca unic venit
 +  * şefii posedă, pe lângă salariu, un posibil bonus
 +
 +Varianta la îndemână ar fi să definim în fiecare din cele doua clase, câte o metodă, //​getTotalRevenue()//,​ care întoarce salariul pentru angajaţi, respectiv suma dintre salariu şi bonus pentru şefi:
 +
 +<code java>
 +class Employee {
 +        ...
 +        public float getTotalRevenue() {
 +                return salary;
 +        }
 +}
 +class Manager extends Employee {
 +        ...       
 +        public float getTotalRevenue() {
 +                return salary + bonus;
 +        }
 +}
 +</​code>​
 +
 +Acum ne interesează să calculăm //procentul mediu// pe care îl reprezintă bonusul din venitul şefilor, luându-se în considerare doar bonusurile pozitive. Avem două posibilităţi:​
 +  * Definim câte o metodă, //​getBonusPercentage()//,​ care în //​Employee//​ întoarce mereu 0, iar în //Manager// raportul real. **Dezavantajul** constă în adăugarea în interfeţe a unor funcţii prea specializate,​ de detalii ce ţin doar de unele implementări ale acestora.
 +  * Parcurgem lista de angajaţi, //​testăm//,​ la fiecare pas, tipul angajatului,​ folosind ''​instanceof'',​ şi calculăm, doar pentru şefi, raportul solicitat. **Dezavantajul** este tratarea într-o manieră //​neuniformă//​ a structurii noastre, cu evidenţierea particularităţilor fiecărei clase.
 +
 +Datorită acestor particularităţi (în cazul nostru, modalităţile de calcul al venitului, respectiv procentului mediu), constatăm că ar fi foarte utilă **izolarea implementărilor specifice** ale algoritmului (în cazul nostru, scrierea unei funcţii în fiecare clasă). Acest lucru conduce, însă, la introducerea unei metode noi în //fiecare// din clasele antrenate in prelucrări,​ de fiecare dată cand vrem să punem la dispoziţie o nouă operaţie. Obţinem următoarele dezavantaje:​
 +  * în cazul unui număr mare de operaţii, **interfeţele claselor se aglomerează excesiv** şi se ascunde funcţionalitatea //de bază// a acestora
 +  * codul din interiorul clasei (care servea functionalităţii primare a acesteia) va fi amestecat cu cel necesar algoritmilor de prelucrare, devenind mai greu de parcurs şi întreţinut
 +  * în cazul în care nu avem acces la codul claselor, singura modalitate de adăugare de funcţionalitate este extinderea
 +
 +În final, tragem concluzia că este de dorit să **izolăm algoritmii de clasele pe care le prelucrează**.
 +
 +O abordare bună ar fi:
 +  * conceperea claselor cu **posibilitatea de primire/​ataşare a unor obiecte-algoritm**,​ care definesc operaţiile dorite
 +  * definirea unor **clase algoritm** care vor __//​**vizita**//​__ structura noastră de date, vor //efectua// prelucrările specifice fiecărei clase, având, totodată, //​posibilitatea de încapsulare a unor informaţii de stare// (cum sunt suma şi numărul din exemplul anterior)
 +
 +== After ==
 +Conform observațiilor precedente, structura programului Employee-Manager devine:
 +
 +<code java Test.java>​
 +interface Visitor {
 +        public void visit(Employee employee);
 +        public void visit(Manager manager);
 +}
 +interface Visitable {
 +        public void accept(Visitor v);
 +}
 +class Employee implements Visitable {
 +        ...      ​
 +        public void accept(Visitor v) {
 +                v.visit(this); ​         ​
 +        }
 +}
 +class Manager extends Employee {
 +        ...
 +        public void accept(Visitor v) {
 +                v.visit(this); ​         ​
 +        }
 +}
 +public class Test {
 +        public static void main(String[] args) {
 +                ...
 +                Visitor v = new SomeVisitor(); ​       // creeaza un obiect-vizitator concret
 +                for (Employee e : employees)
 +                        e.accept(v); ​               ​
 +        }
 +}
 +</​code>​
 +
 +Iată cum poate arăta un vizitator ce determină venitul total al fiecărui angajat şi îl afişează:
 +<code java RevenueVisitor.java>​
 +public class RevenueVisitor implements Visitor {        ​
 +        public void visit(Employee employee) {
 +                System.out.println(employee.getName() + " " + employee.getSalary()); ​               ​
 +        }        ​
 +        public void visit(Manager manager) {
 +                System.out.println(manager.getName() + " " + (manager.getSalary() + manager.getBonus())); ​               ​
 +        }       
 +}
 +</​code>​
 +
 +Secvenţele de cod de mai sus definesc:
 +  * o interfaţă,​ **Visitor**,​ ce reprezintă un //​algoritm//​ oarecare, ce va putea vizita orice clasă. Observaţi definirea câte //unei metode visit(...)//​ pentru //fiecare clasă ce va putea fi vizitată//​.
 +  * o interfaţă,​ **Visitable**,​ a carei metodă ''​accept(Visitor)''​ permite rularea unui algoritm pe structura curentă. ​
 +  * implementări ale metodei ''​accept(Visitor)'',​ în cele două clase, care, pur şi simplu, solicită vizitarea instanţei curente de către vizitator. ​
 +  * o implementare a unei operații aplicabilă pe obiectele de tip Visitable.
 +
 +În exemplul de mai sus, putem identifica :
 +  * Element - Visitable
 +  * ConcreteElement - Employee, Manager
 +
 +=== Double-dispatch ===
 +
 +Mecanismul din spatele pattern-ului Visitor poartă numele de **double-dispatch**. Acesta este un concept răspândit,​ şi se referă la faptul că metoda apelată este determinată la //runtime// de doi factori. În exemplul Employee-Manager,​ efectul vizitării, solicitate prin apelul ''​e.accept(v)'',​ depinde de:
 +  * tipul elementului vizitat, ''​e''​ (//​Employee//​ sau //​Manager//​),​ pe care se invocă metoda
 +  * tipul vizitatorului,​ ''​v''​ (//​RevenueVisitor//​),​ care conţine implementările metodelor //visit//
 +
 +Acest lucru contrastează cu un simplu apel //​e.getTotalRevenue()//,​ pentru care efectul este hotărât doar de tipul anagajatului. Acesta este un exemplu de **single-dispatch**. ​
 +
 +<​note>​[[:​poo-ca-cd::​laboratoare:​tutorial-doubledispatch| Tutorialul de double-dispatch]] oferă mai multe detalii legate de acest mecanism.</​note>​
 +
 +=== Cum implementăm?​ ===
 +
 +  - Se declară interfața care să reprezinte elementul nostru, care va conține și metoda ''​public void accept(ElementVisitor elementVisitor);''​
 +  - Se creează clasele concrete care implementează interfața declarată anterior. Body-ul pentru metoda accept va conține obligatoriu ''​elementVisitor.visit(this);''​
 +  - Se definește o interfață care reprezintă Visitor-ul nostru și care va conține atâtea metode de visit câte clase concrete am creat la pasul anterior (câte o metodă de visit pentru fiecare tip de element).
 +  - Se creează o clasă concretă care implementează interfața de Visitor, unde vom adăuga implementările pentru fiecare tip de element în parte. (ex: ''​ElementDisplayVisitor()''​)
 +  - În main putem testa dacă funcționează așa cum ne dorim iterând print-un arraylist/​vector de obiecte de tip Element astfel: ''​elementIterator.accept(new ElementDisplayVisitor());''​
 +
 +=== Avantaje și dezavantaje ===
 +
 +**Avantaje:​**
 +  * Decuplarea datelor de operațiile aplicate pe acestea
 +  * Ușurează adăugarea unor noi operații/​algortimi. Se creează o implementare a unui obiect de tip Visitor și nu se schimbă nimic în obiecte vizitate.
 +  * Spre deosebire de Iterator poate gestiona elemente de tipuri diferite
 +  * Poate menține informații de stare pe măsură ce vizitează obiectele
 +
 +**Dezavantaje:​**
 +  * Depinde de stabilitatea ierarhiei de obiecte vizitate. Adăugarea de obiecte vizitabile rezultă în schimbarea implementării obiectelor Visitor. ​
 +  * :!: obiecte de noi tipuri adăugate des + multe operații aplicabile = NU folosiți Visitor
 +  * Expune metode publice care folosesc informații de stare ale obiectelor. Nu se pot accesa membrii privați ai claselor, necesitatea expunerii acestor informaţii (in forma publică) ar putea conduce la //ruperea încapsulării//​
 +
 +==== Exemple din API-uri ====
 +
 +Visitor este de obicei utilizat pentru structuri arborescente de obiecte:
 +  * Parcurgerea arborilor de parsare
 +     * [[https://​help.eclipse.org/​neon/​index.jsp?​topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FASTVisitor.html| ASTVisitor]] din Eclipse JDT. Folosind [[http://​help.eclipse.org/​neon/​index.jsp?​topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FASTParser.html|ASTParser]] se creează arborele de parsare al codului dat ca intrare, iar [[https://​help.eclipse.org/​neon/​index.jsp?​topic=%2Forg.eclipse.jdt.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fjdt%2Fcore%2Fdom%2FASTVisitor.html|ASTVisitor]] parcurge arborele, oferind metode (//​preVisit//,​ //​postVisit//,​ //visit//) pentru multe tipuri de noduri (MethodDeclaration,​ Assignment, IfStatement etc.)
 +  * Parcurgerea și vizitarea ierarhiei de directoare și fișiere
 +     * Java Nio - [[https://​docs.oracle.com/​en/​java/​javase/​12/​docs/​api/​java.base/​java/​nio/​file/​FileVisitor.html | FileVisitor]]
 +        * //​FileVisitor//​ - interfața cu metode de vizitare
 +        * trebuie apelat [[https://​docs.oracle.com/​en/​java/​javase/​12/​docs/​api/​java.base/​java/​nio/​file/​Files.html#​walkFileTree(java.nio.file.Path,​java.nio.file.FileVisitor) | Files.walkFileTree]] transmițându-i ca parametru un obiect care implementează //​FileVisitor//​
 +        * [[http://​docs.oracle.com/​javase/​tutorial/​essential/​io/​walk.html | un tutorial]] ​
 +
 +==== Summary ====
 +
 +**Visitor** = behavioral design pattern
 +  * Util în situații în care:
 +      * avem mai multe obiecte și operații pentru acestea
 +      * dorim schimbarea/​adăugarea operațiilor fără a modifica clasele
 +  * Indicat de utilizat pentru operații pe colecții și parcurgerea de structuri arborescente
 +  * Folosește conceptul de [[:​poo-ca-cd:​laboratoare:​tutorial-doubledispatch|double dispatch]]
 +  ​
 +==== Exerciţii ====
 +
 +Dorim să prelucrăm forme geometrice, pe care să le afișăm în diverse formate: text și JSON https://​datatracker.ietf.org/​doc/​html/​rfc8259. Pentru un design decuplat între formele prelucrate și tipurile de formate dorite, implementați conversia folosind patternul Visitor.
 +
 +Problema de pe DevMind va avea două task-uri, corespunzătoare celor două tipuri de Visitor. Pentru simplitatea implementării acestor Visitors, vă sugerăm să urmăriți TODO-urile din schelet.
 +
 +      * Vom avea trei tipuri de forme geometrice care implementează interfața comună "​**Shape**":​ **Dot**, **Circle**, **Rectangle**. Aceste tipuri de forme vor accepta obiecte Visitor pentru a putea permite afișarea lor în cele două formate.
 +      * Vom avea două tipuri de Visitor care implementează interfața comună "​**Visitor**":​ **TextVisitor** și **JsonVisitor**. Fiecare Visitor va implementa metoda visit(), care va aplica modalitatea de afișare specifică pe obiectul primit ca parametru.
 +      * Scheletul conține în fiecare clasă copil a tipului Shape, câmpuri specifice formei geometrice. Pentru acestea, va trebui să creați getters și setters.
 +      * //Tips for faster coding//:
 +        * atunci când creați o clasă care implementează o interfață sau o clasă cu metode abstracte, nu scrieți de mână antetul fiecărei metode, ci folosiți-vă de IDE.
 +        * în Intellij va apărea cu roșu imediat după ce scrieți extends.../​implements... Dați alt-enter sau option-enter (pe mac), și vi se vor genera metodele pe care trebuie să le implementați,​ voi completând apoi conținutul lor.
 +        * generați getters/​setters folosind IDE-ul
 +
 +Exemplu de format text
 +
 +   ​Circle - radius = 30
 +
 +
 +Exemplu de format JSON
 +
 +
 +   {
 +      "​Circle":​ {
 +         "​radius":​ 30
 +      }
 +   }
 +
 +
 +
 +==== Referințe ====
 +  - Vlissides, John, et al. //Design patterns: Elements of reusable object-oriented software//. Addison-Wesley (1995) ([[http://​index-of.co.uk/​Software-Engineering/​Design%20Patterns%20-%20Elements%20Of%20Reusable%20Object-Oriented%20Software%20-%20Addison%20Wesley.pdf|available online]])
 +  - [[http://​en.wikipedia.org/​wiki/​Software_design_pattern | Clasificarea design pattern-urilor]]
 +  - [[:​poo-ca-cd:​laboratoare:​tutorial-doubledispatch| Tutorial double-dispatch]]
 +
  
poo-ca-cd/arhiva/laboratoare/2024/visitor.txt · Last modified: 2025/09/27 10:42 by florian_luis.micu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0