This shows you the differences between two versions of the page.
poo-ca-cd:laboratoare:visitor [2022/11/20 23:51] diana.tatulescu [Visitor Design Pattern] |
poo-ca-cd:laboratoare:visitor [2024/11/13 09:56] (current) silvia_elena.nistor [Exerciţii] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 7: Overriding, overloading & Visitor pattern ===== | + | ===== Laboratorul 6: Visitor pattern ===== |
**Video introductiv:** [[https://www.youtube.com/watch?v=_mfLYYInv6c| link ]] | **Video introductiv:** [[https://www.youtube.com/watch?v=_mfLYYInv6c| link ]] | ||
Line 5: | Line 5: | ||
==== Obiective ==== | ==== Obiective ==== | ||
- | * Implementarea polimorfismului în Java | ||
- | * Diferența dintre Overriding & Overloading | ||
* Prezentarea design pattern-ului Visitor și familiarizarea cu situațiile în care acesta este util de aplicat | * 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> | ||
- | ==== Overriding ==== | + | //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. |
- | + | ||
- | **Suprascrierea** se referă la redefinirea metodelor existente în clasa părinte de către clasa copil în vederea specializării acestora. | + | |
- | + | ||
- | * Metodele din clasa parinte nu sunt modificate. | + | |
- | * Putem suprascrie doar metodele vizibile pe lanțul de moștenire (public, protected). | + | |
- | * O metodă din clasa copil suprascrie metoda din clasa părinte dacă are **același tip de return** și **aceeași semnatură**. | + | |
- | <note important> **Semnătura (signature)** unei metode constă în: | + | Acest pattern este behavioural (//comportamental//) pentru că definește modalități de comunicare între obiecte. |
- | * numele metodei | + | |
- | * numărul și tipul parametrilor | + | |
- | </note> | + | |
- | + | ||
- | În cazul suprascrierii se determină ce metodă va fi apelată, în mod dinamic, **la runtime**. Explicația este că decizia se face pe baza __tipului obiectului__ care apelează metoda, deci a instanței, care e cunoscută la runtime. | + | |
- | Din acest motiv, suprascrierea este cunoscută și ca **polimorfism dinamic** (__Runtime polymorphism__). | + | |
- | **Polimorfismul reprezintă abilitatea unei clase să se comporte ca o altă clasă de pe lanțul de moștenire, și de aceea conceptul de suprascriere a metodelor este foarte strâns legat.** | + | === Aplicabilitate === |
- | + | ||
- | <note important> La apelarea unei metode suprascrise, Java se uită la tipul intern al obiectului pentru care este apelată metoda, NU la referință. Astfel dacă referința are tipul clasei părinte, dar tipul este al clasei copil, JVM va apela metoda din clasa copil. </note> | + | |
- | + | ||
- | Câteva **reguli pentru suprascriere** sunt: | + | |
- | * metoda suprascrisă are același tip de return și semnatură ca metoda inițială | + | |
- | * putem avea un tip de return diferit de cel al metodei inițiale, atâta timp cat este un tip ce moștenește tipul de return al metodei inițiale | + | |
- | * specificatorul de access al metodei suprascrise nu poate fi mai restrictiv decât cel al metodei inițiale | + | |
- | * nu poate arunca mai multe excepții sau excepții mai generale, poate însă arunca mai puține sau mai particulare sau excepții unchecked (de runtime) | + | |
- | * metodele de tip ''static'' și ''final'' nu pot fi suprascrise | + | |
- | * constructorii nu pot fi suprascriși | + | |
- | + | ||
- | În exemplul de mai jos, metodele //purr// și //getFeatures// au fost suprascrise de tipul //GrumpyCat//. | + | |
- | <code java> | + | |
- | class CatFeatures { } | + | |
- | class GrumpyCatFeatures extends CatFeatures { } | + | |
- | class GrumpyFeatures { } | + | |
- | + | ||
- | class Cat { | + | |
- | + | ||
- | public void purr() { | + | |
- | System.out.println("purrrr"); | + | |
- | } | + | |
- | + | ||
- | public CatFeatures getFeatures() { | + | |
- | System.out.println("Cat getFeatures"); | + | |
- | return new CatFeatures(); | + | |
- | } | + | |
- | + | ||
- | public final void die() { | + | |
- | System.out.println("Dying! frown emoticon"); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | class GrumpyCat extends Cat { | + | |
- | @Override | + | |
- | public void purr() { | + | |
- | System.out.println("NO!"); | + | |
- | } | + | |
- | + | ||
- | @Override | + | |
- | public GrumpyCatFeatures getFeatures() { | + | |
- | System.out.println("Grumpy getFeatures"); | + | |
- | return new GrumpyCatFeatures(); | + | |
- | } | + | |
- | + | ||
- | // compiler would complain if you included @Override here | + | |
- | //@Override | + | |
- | //public void die() { } // Cannot override the final method from Cat | + | |
- | + | ||
- | public static void main(String [] args) { | + | |
- | ArrayList<Cat> cats = new ArrayList<Cat>(); | + | |
- | cats.add(new Cat()); | + | |
- | cats.add(new GrumpyCat()); | + | |
- | + | ||
- | for (Cat c : cats) { | + | |
- | c.purr(); | + | |
- | c.die(); | + | |
- | c.getFeatures(); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | </code> | + | |
- | + | ||
- | + | ||
- | <note important>**Adnotarea** (**Annotation**) ''@Override'' este complet opțională. Totuși este indicat să o includeți mereu când suprascrieți o metodă. | + | |
- | Motivele sunt simple: | + | |
- | * Compilatorul vă va anunța printr-o eroare dacă ați greșit numele metodei sau tipul parametrilor și această nouă metodă nu suprascrie de fapt o metodă a părintelui | + | |
- | * Face codul vostru mai ușor de citit, pentru că devine evident când o metodă suprascrie o altă metodă | + | |
- | </note> | + | |
- | + | ||
- | <note tip> | + | |
- | O metodă cu argumente de tip primitiv nu poate fi suprascrisă cu o metodă cu tip wrapper. | + | |
- | + | ||
- | ''public void doSmth(int x)'' nu poate fi suprascrisă cu ''public void doSmth(Integer x)'' | + | |
- | + | ||
- | Metoda cu argument de tip wrapper poate primi si null, insă cea cu tipul primitiv nu, de aceea, neputând să fie păstrată echivalența, nu este permisă aceasta suprascriere | + | |
- | + | ||
- | </note> | + | |
- | + | ||
- | === super === | + | |
- | + | ||
- | În laboratorul [[:poo-ca-cd:laboratoare:agregare-mostenire#cuvantul_cheie_super_intrebuintari| de agregare și de moștenire]] am folosit cuvântul cheie **super** pentru a invoca un anumit constructor din clasa părinte dar și pentru a apela în mod explicit metoda din clasa părinte în cazul metodelor suprascrise. | + | |
- | + | ||
- | Rescriem metoda ''purr()'' din clasa ''GrumpyCat'' astfel: | + | |
- | <code java> | + | |
- | @Override | + | |
- | public void purr() { | + | |
- | super.purr(); | + | |
- | System.out.println("NO!"); | + | |
- | } | + | |
- | </code> | + | |
- | La apelul metodei pe o instanță a clasei ''GrumpyCat'' output-ul va fi: | + | |
- | <code> | + | |
- | purrrr | + | |
- | NO! | + | |
- | </code> | + | |
+ | 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ă === | ||
- | ==== Overloading ==== | + | {{ .:visitor:visitor.png?680 | Componente pattern Visitor }} |
- | + | ||
- | **Supraîncarcarea** se referă la posibilitatea de a avea într-o clasă mai multe metode cu același nume, dar implementari diferite. În Java, compilatorul poate distinge între metode pe baza semnăturii lor, acesta fiind mecanismul din spatele supraîncărcarii. | + | |
- | + | ||
- | Opțional, pe lângă semnătura metodei poate fi menționat și tipul excepțiilor ce pot fi aruncate din codul acesteia. | + | |
- | <note important> | + | Structura design pattern-ului "Visitor" este formată din următoarele componente: |
- | Tipul de return al unei metode NU face parte din semnătura acesteia. Din acest motiv simpla modificare a tipului de return al unei metode nu este suficientă pentru supraîncărcare. Ceea ce vom primi este o eroare de compilare. | + | |
- | <code java> | + | |
- | public class TRex { | + | |
- | public void eat(Triceratops victim) { | + | |
- | System.out.println("Take 5 huge bites"); | + | |
- | } | + | |
- | + | ||
- | public boolean eat(Triceratops victim) { | + | |
- | boolean satisfaction = false; | + | |
- | if (victim.isJuicy()) { | + | |
- | System.out.println("Eat and be satisfied"); | + | |
- | satisfaction = true; | + | |
- | } | + | |
- | return satisfaction; | + | |
- | } | + | |
- | + | ||
- | // Error "Duplicate method eat(Triceratops)" in type TRex | + | |
- | </code> | + | |
- | Observăm de asemenea că la compilare nu se ține cont de numele dat parametrilor. Astfel modificarea acestuia din //victim// în //dino//, spre exemplu, nu constituie o supraîncărcare validă. | + | |
- | </note> | + | |
- | * O clasă poate supraîncărca metodele moștenite. | + | **Client:** |
- | * Constructorii pot fi supraîncărcați. | + | * 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ă. | ||
- | Spre deosebire de suprascriere, **supraîncărcarea are loc la compilare**, motiv pentru care mai este numită și **polimorfism static** (__compile time polymorphism__). În aceasta fază compilatorul decide ce metodă este apelată pe baza __tipului referinței__ și prin analiza numelui și a listei de parametri. La runtime, când este întalnit apelul unei metode supraîncărcate, deja se știe unde este codul care trebuie executat. | + | **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. | ||
- | Mai jos avem un exemplu valid de supraîncărcare pentru metoda // eat//: | + | **ConcreteVisitor:** |
- | <code java> | + | * Pentru fiecare tip de "Visitor", toate metodele de vizitare definite în "Visitor" trebuie implementate. |
- | public class TRex { | + | * 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. | |
- | public void eat(Triceratops victim) { | + | |
- | System.out.println("Take 5 huge bites"); | + | |
- | } | + | |
- | + | ||
- | public void eat(Dromaeosaurus victim) { // parametru cu tip diferit | + | |
- | System.out.println("Take 1 single bite"); | + | |
- | } | + | |
- | + | ||
- | public void eat(Human firstCourse, Human secondCourse) { // numar si tipuri diferite de parametrii | + | |
- | System.out.println("No humans to eat at the time"); | + | |
- | } | + | |
- | + | ||
- | public int eat(Grass desert) { // parametru cu tip diferit, return type este irelevant | + | |
- | System.out.println("Rather starve"); | + | |
- | return 0; | + | |
- | } | + | |
- | + | ||
- | public static void main(String [] args) { | + | |
- | TRex john = new TRex(); | + | |
- | + | ||
- | john.eat(new Triceratops()); // "Take 5 huge bites" | + | |
- | john.eat(new Dromaeosaurus()); // "Take 1 single bite" | + | |
- | john.eat(new Human("Ana"), new Human("Andrei")); // "No humans to eat at the time" | + | |
- | john.eat(new Grass()); // "Rather starve" | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | ==== Visitor Design Pattern ==== | + | **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). | ||
- | <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 bucata de cod ce poate fi aplicata 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> | + | **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. | ||
- | //Visitor// este un **behavioral 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 behavioral (//comportamental//) pentru că definește modalități de comunicare între obiecte. | + | <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. | |
- | === Aplicabilitate === | + | - 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. |
- | + | ||
- | 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 distincte pe aceeaşi structură//, pentru a preveni poluarea interfeţelor claselor implicate, cu multe detalii aparţinând unor algoritmi diferiţi. În acest fel, se centralizează aspectele legate de acelaşi algoritm //într-un singur loc//, dar, în acelaşi timp, //se separă detaliile ce ţin de algoritmi diferiţi//. Acest lucru conduce la simplificarea atât a claselor prelucrate, cât şi a vizitatorilor. Orice date specifice algoritmului rezidă în vizitator. | + | |
- | * ** clasele ce se doresc prelucrate se modifică rar, în timp ce operaţiile de prelucrare se definesc des**. Dacă însă sunt introduse multe clase visitabile, după crearea obiectelor Visitor, atunci este necesară modificarea acestora din urmă, pentru adăugarea de metode //visit// pentru noile clase. | + | |
- | === Structură === | + | |
- | + | ||
- | {{ .:visitor:visitor.png?680 | Componente pattern Visitor }} | + | |
- | + | ||
- | **Visitor** - o interfață pentru operația aplicată | + | |
- | **Visitable** - o interfață pentru obiecte pe care pot fi aplicate operațiile (în diagramă este numită ''Element'') | + | |
- | * metoda ''accept'' e independentă de tipul concret al Visitor-ului | + | |
- | * în ''accept'' se folosește obiectul de tip Visitor | + | |
- | Pentru fiecare algoritm/operație ce trebuie aplicată, se implementează clase de tip Visitor. În fiecare obiect de tip //Visitor// trebuie să implementăm metode care aplică operația pentru fiecare tip de element vizitabil. | + | |
- | + | ||
- | <note tip>În imaginea de mai jos este reprezentat **flow-ul aplicării acestui pattern**: | + | |
- | - Clientul este cel care folosește o colecție de obiecte de unul sau mai multe tipuri, și dorește să aplice pe acestea diferite operații (în exercițiile din laborator clientul este practic programul vostru de test - main-ul). Clientul folosește obiecte //Visitor// create pentru fiecare operație necesară. | + | |
- | - Clientul parcurge colecția și în loc să aplice operaţia direct pe fiecare obiect de tip //Element//, îi oferă acestuia un obiect de tip //Visitor//. | + | |
- | - Obiectul de tip //Element// apelează metoda de "vizitare" oferită de //Visitor//. | + | |
- | - Pe obiectul //Visitor// se apelează metoda //visit// corespunzătoare obiectului, iar în ea se efectuează operația. (:!: ** în Visitor folosim conceptul de //overloading// pentru fiecare metodă //visit//**) | + | |
</note> | </note> | ||
- | |||
- | {{ .:visitor:visitor-flow.png |Interacțiunile dintre componentele pattern-ului Visitor }} | ||
<note important> | <note important> | ||
Line 230: | Line 63: | ||
**Visitor și structurile de date** | **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: | + | 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ădacina arborelui) | + | * 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)'' | + | * 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. | * 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> | </note> | ||
Line 284: | Line 117: | ||
Ne interesează să interogăm toţi angajaţii noştri asupra //venitului lor total//. Observăm că: | Ne interesează să interogăm toţi angajaţii noştri asupra //venitului lor total//. Observăm că: | ||
- | * anagajaţii obişnuiţi au salariul ca unic venit | + | * angajaţii obişnuiţi au salariul ca unic venit |
* şefii posedă, pe lângă salariu, un posibil bonus | * ş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: | + | 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> | <code java> | ||
Line 313: | Line 146: | ||
* în cazul în care nu avem acces la codul claselor, singura modalitate de adăugare de funcţionalitate este extinderea | * î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 primă idee se referă la utilizarea //metodelor statice//. Dezavantajul acestora este că nu pot reţine, într-un mod elegant, informaţie de stare din timpul prelucrării. De exemplu, dacă structura noastră ar fi arborescentă (recursivă), în sensul că o instanţă //Manager// ar putea ţine referinţe la alte instanţe //Manager//, ce reprezintă şefii ierarhic inferiori, o funcţie de prelucrare ar trebui să menţină o informaţie parţială de stare (precum suma procentelor calculate până într-un anumit moment) sub forma unor parametri furnizaţi apelului recursiv: | + | În final, tragem concluzia că este de dorit să **izolăm algoritmii de clasele pe care le prelucrează**. |
- | <code java> | + | |
- | class Manager extends Employee { | + | |
- | ... | + | |
- | public float getPercentage(float sum, int n) { | + | |
- | float f = bonus / getTotalRevenue(); | + | |
- | if (f > 0) | + | |
- | return inferiorManager.getPercentage(sum + f, n + 1); // trimite mai departe cererea catre nivelul inferior | + | |
- | return inferiorManager.getPercentage(sum, n); | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | O abordare mai bună ar fi: | + | O abordare bună ar fi: |
* conceperea claselor cu **posibilitatea de primire/ataşare a unor obiecte-algoritm**, care definesc operaţiile dorite | * 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) | * 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) | ||
Line 376: | Line 198: | ||
Secvenţele de cod de mai sus definesc: | 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ţă, **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ă. | * 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. | * 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 | + | * o implementare a unei operații aplicabilă pe obiectele de tip Visitable. |
În exemplul de mai sus, putem identifica : | În exemplul de mai sus, putem identifica : | ||
Line 387: | Line 209: | ||
=== Double-dispatch === | === Double-dispatch === | ||
- | Mecanismul din spatele pattern-ului Visitor poartă numele de **double-dispatch**. Acesta este un concept raspândit, şi se referă la faptul că metoda apelată este determinată la //runtime// de doi factori. În exemplul Employee-Manager, efectul vizitarii, solicitate prin apelul ''e.accept(v)'', depinde de: | + | 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 elementului vizitat, ''e'' (//Employee// sau //Manager//), pe care se invocă metoda | ||
* tipul vizitatorului, ''v'' (//RevenueVisitor//), care conţine implementările metodelor //visit// | * tipul vizitatorului, ''v'' (//RevenueVisitor//), care conţine implementările metodelor //visit// | ||
Line 429: | Line 251: | ||
==== Summary ==== | ==== Summary ==== | ||
- | **Supraîncărcarea (overloading) ** - mai multe metode cu același nume dar cu listă diferită de argumente | + | **Visitor** = behavioral design pattern |
- | * metoda care va fi executată este stabilită la //compilare//, pe baza tipului referinței | + | * Util în situații în care: |
- | * metoda supraîncărcată are neapărat o listă diferită de argumente și poate, opțional, avea: | + | |
- | * alți modificatori de acces | + | |
- | * alt tip de return | + | |
- | * alte excepții | + | |
- | * constructorii pot fi supraîncărcati | + | |
- | * metodele moștenite pot fi supraîncărcate | + | |
- | + | ||
- | **Suprascrierea (overriding) ** - redefinirea metodelor moștenite | + | |
- | * metoda care va fi executată este stabilită la //runtime//, pe baza tipului obiectului | + | |
- | * metoda suprascrisă are același tip de return și semnătură ca metoda inițială | + | |
- | * putem avea un tip de return diferit de cel al metodei inițiale, atâta timp cât este un tip ce moștenește tipul de return al metodei inițiale | + | |
- | * specificatorul de access al metodei suprascrise nu poate fi mai restrictiv decât cel al metodei inițiale | + | |
- | * nu poate arunca mai multe excepții sau excepții mai generale, poate însă arunca mai puține sau mai particulare sau excepții unchecked (de runtime) | + | |
- | * metodele de tip ''static'' și ''final'' nu pot fi suprascrise | + | |
- | * constructorii nu pot fi suprascriși | + | |
- | + | ||
- | + | ||
- | **Visitor** - pattern pt modelarea comportamentului claselor | + | |
- | * util în situații în care: | + | |
* avem mai multe obiecte și operații pentru acestea | * avem mai multe obiecte și operații pentru acestea | ||
* dorim schimbarea/adăugarea operațiilor fără a modifica clasele | * 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 | + | * Indicat de utilizat pentru operații pe colecții și parcurgerea de structuri arborescente |
- | * conceptul de [[:poo-ca-cd:laboratoare:tutorial-doubledispatch|double dispatch]] | + | * Folosește conceptul de [[:poo-ca-cd:laboratoare:tutorial-doubledispatch|double dispatch]] |
| | ||
==== Exerciţii ==== | ==== Exerciţii ==== | ||
- | **Task 1 (8 puncte):** Dorim să prelucrăm bucăți de text pe care să le convertim în diferite formate, momentan dokuwiki și markdown. Pentru un design decuplat între obiectele prelucrate și tipurile de formate dorite, implementați conversia folosind patternul Visitor. | + | 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. |
- | * Fișierul **[[https://github.com/oop-pub/oop-labs/tree/master/src/lab7|README]]** din scheletul de cod cuprinde informațiile necesare designului dorit. | + | 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. |
- | * implementați structura de clase din diagrama din README | + | |
- | * implementați TODO-urile din scheletul de cod | + | * 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. |
- | * Pentru simplitatea testării scheletul oferă clasa ''Test'' care oferă bucățile de text pe care să le prelucrați. | + | * 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. |
- | * dacă folosiți IntelliJ creați proiect din scheletul de laborator: File -> New Project -> select Java -> select the skel folder | + | * 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. |
- | * În implementare va trebui sa folositi clasa [[https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/StringBuilder.html|StringBuilder]]. Aceasta este o clasă mutabilă (//mutable//), spre deosebire de String, care e imutabilă (//immutable//). Vă recomandăm [[https://www.geeksforgeeks.org/string-vs-stringbuilder-vs-stringbuffer-in-java/|acest link]] pentru un exemplu si explicații despre diferențele dintre ele. | + | |
* //Tips for faster coding//: | * //Tips for faster coding//: | ||
- | * atunci cand creati o clasa care implementeaza o interfata sau o clasa cu metode abstracte, nu scrieti de mana antetul fiecarei metode, ci folositi-va de IDE. | + | * 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. |
- | * In Intellij va aparea cu rosu imediat dupa ce scrieti extends.../implements... Dati alt-enter sau option-enter (pe mac), si vi se vor genera metodele pe care trebuie sa le implementati, voi completand apoi continutul lor. | + | * î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. |
- | * generati constructorii folosind IDE-ul | + | * generați getters/setters folosind IDE-ul |
+ | |||
+ | Exemplu de format text | ||
+ | |||
+ | Circle - radius = 30 | ||
+ | |||
+ | |||
+ | Exemplu de format JSON | ||
- | **Task 2 (2 puncte):** Afișați numărul de fișiere cu extensia ".class" sau ".java" și numele acestora din directorul specificat în schelet. | + | { |
- | + | "Circle": { | |
- | * Pentru a rezolva exercițiul, veți implementa un [[https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/nio/file/FileVisitor.html | FileVisitor]], care extinde [[https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/nio/file/SimpleFileVisitor.html | SimpleFileVisitor]] din java.nio. Mai specific, veți suprascrie metoda [[https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitor.html#visitFile-T-java.nio.file.attribute.BasicFileAttributes-|visitFile]], care este invocată atunci când un fișier este vizitat. | + | "radius": 30 |
- | * Pentru mai multe detalii și un exemplu de implementare, consultați [[http://docs.oracle.com/javase/tutorial/essential/io/walk.html | acest tutorial]] și scheletul de laborator. | + | } |
- | * **Hint:** Folosiți ''path.toString().endsWith'' pentru a verifica extensia fișierelor. Dacă omiteți toString-ul nu o să funcționeze. | + | } |
- | * **Hint:** Va trebui să verificați dacă instanța la care ați ajuns este un fișier sau nu, folosindu-vă de [[https://docs.oracle.com/javase/8/docs/api/java/nio/file/attribute/BasicFileAttributes.html|BasicFileAttributes]]. | + | |
- | **Task 3 (2 puncte):** Copiați codul de la task-ul 1 și puneți-l în [[https://lambdachecker.io/problem/79 | LambdaChecker]], unde veți face testarea și submisia codului. | ||
==== Referințe ==== | ==== Referințe ==== | ||
- | - Kathy Sierra, Bert Bates. //SCJP Sun Certified Programmer for Java™ 6 - Study Guide//. Chapter 2 - Object Orientation ([[http://firozstar.tripod.com/_darksiderg.pdf|available online]]) - moștenire, polimorfism, overriding, overloading + exemple de întrebări | ||
- 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]]) | - 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]] | - [[http://en.wikipedia.org/wiki/Software_design_pattern | Clasificarea design pattern-urilor]] |