User Tools

Site Tools


Problem constructing authldap
laboratoare:tutorial-doubledispatch
Differences

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

Link to this comparison view

Next revision
Previous revision
laboratoare:tutorial-doubledispatch [2014/10/26 15:48]
Daniel Ciocirlan created
laboratoare:tutorial-doubledispatch [2014/11/03 10:53] (current)
Daniel Ciocirlan
Line 3: Line 3:
 Double-dispatch este o tehnică folosită în modelarea orientată-obiect atunci când avem de a face cu două categorii de obiecte (fiecare categorie conținând obiecte de tipuri diferite), între care există o interacțiune. Double-dispatch este o tehnică folosită în modelarea orientată-obiect atunci când avem de a face cu două categorii de obiecte (fiecare categorie conținând obiecte de tipuri diferite), între care există o interacțiune.
  
-Fie aceste categorii de obiecte **A** și **B**, respectiv.+===Scenariu===
  
 +Ca exemplu, fie aceste categorii de obiecte ''​A''​ și ''​B'',​ respectiv. Din punct de vedere obiectual, ierarhia de clase ''​A''​ conține clasa ''​A''​ și clasele ''​A1''​ și ''​A2''​ ca subclase ale lui ''​A''​. Fie, de asemenea, o ierarhie identică pentru ''​B''​. Ne dorim ca orice obiect de tip ''​A''​ să poată interacționa cu orice obiect de tip ''​B'',​ dar în mod diferit. Cu alte cuvinte, interacțiunea dintre un ''​A1''​ și un ''​B2''​ să fie diferită de cea dintre un ''​A1''​ cu un ''​B1''​ sau de cea dintre un ''​A''​ cu un ''​B2''​. Presupunem, fără a restrânge generalitatea,​ că obiectele ''​A''​ sunt cele care "​acționează"​ efectiv asupra obiectelor ''​B''​.
  
 +===O posibilă soluție===
 +
 +O soluție care ar reieși imediat este în stilul următor. Toate obiectele de de tip ''​A''​ ar putea avea o metodă ''​public void interactiWith(B b)'',​ ca în snippet-ul de cod următor:
 +
 +<code java>
 +
 +public class A {
 + public void interactWith(B b) {
 + if (b instanceof B1){
 + B1 realb = (B1) b;
 + // ...
 + // action for B1
 + } else if (b instanceof B2){
 + B2 realb = (B2) b;
 + // ...
 + // action for B2
 + } else {
 + // ...
 + // action for B
 + }
 + }
 +}
 +</​code>​
 +
 +Dacă fiecare subclasă a lui ''​A''​ suprascrie metoda ''​interactWith''​ și tratează fiecare caz pentru tipul efectiv al parametrului,​ atunci soluția ar fi completă: fiecare obiect ''​A''​ ar avea propriile implementări pentru fiecare interacțiune,​ în funcție de tipul concret al obiectului ''​B''​ primit ca parametru.
 +
 +Soluția aceasta pare în regulă pentru scenariul nostru. Dar pentru că noi invățăm să gândim în perspectivă,​ ne punem întrebări:​ **ce se întâmplă dacă (e nevoie să) adăugăm clase noi în oricare dintre ierarhii? Cum arată codul pentru ierarhii mai mari?**
 +
 +Să răspundem pe rând: ​
 +
 +  * Dacă adăugăm o nouă clasă în ierarhia ''​A'',​ atunci ea trebuie să suprascrie metoda ''​interactWith'',​ în același stil care tratează tipul efectiv al parametrului. Nu ar fi prea urât. Poate că ar fi, dacă avem multe tipuri ''​B''​.
 +  * Dacă adăugăm o nouă clasă în ierarhia ''​B''​ (fie ea ''​B3''​),​ atunci trebuie ca fiecare clasă din ierarhia ''​A''​ să ia în calcul și noua variantă. Trebuie modificate //toate// metodele ''​interactWith'',​ din //toată// ierarhia ''​A'',​ cu un nou ''​instanceof''​. **Destul de urât.** Ce ne facem dacă avem 100 de clase ''​A''?​
 +  * Cum arată codul pentru ierarhii mai mari? Cam ca în snippet-ul de mai jos: pentru că mereu descoperim o nouă nevoie de extindere, adăugăm câte un caz nou. //​Înmulțit cu numărul de clase ''​A''//​.
 +
 +<code java>
 +boolean isPrime(int n) {
 +// probably the dumbest way of testing a prime number
 +// not even complete (not that it may ever be)
 + if (n == 2)
 + return true;
 + if (n == 3)
 + return true;
 + // ...
 + if (n == 160480967)
 + return true;
 + // ...
 + return false;
 +}
 +</​code>​
 +
 +Evident, ne trebuie ceva mai inteligent.
 +
 +===Double Dispatch===
 +
 +Să ne uităm încă o dată la soluția precedentă. Observăm că obiectele ''​A''​ "​acționează"​ asupra obiectelor ''​B'',​ fără ca obiectele ''​B''​ să aibă nicio "​implicare"​. Obiectele ''​B''​ sunt doar pasate ca parametri la metodele obiectelor ''​A''​ iar fiecare obiect ''​A''​ procesează,​ în mod [[http://​en.wikipedia.org/​wiki/​Polymorphism_(computer_science)#​Subtyping|polimorfic]],​ parametrul după implementarea proprie:
 +
 +<code java>
 +A a = new A1();
 +B b = new B2();
 +a.interactWith(b);​ // se execută blocul aferent action B2 din clasa A1
 +</​code>​
 +
 +Să încercăm să "​implicăm"​ și obiectele ''​B''​. Dacă ne gândim că obiectele ''​A''​ interacționează cu obiectele ''​B'',​ ne putem gândi și că obiectele ''​B''​ "​acceptă"​ interacțiunea. Fie astfel următoarea implementare:​
 +
 +<code java>
 +public class B {
 +
 + void accept(A a) {
 + a.interactWith(this);​
 + }
 +}
 +
 +class B1 extends B {
 + void accept(A a) {
 + a.interactWith(this);​
 + }
 +}
 +
 +class B2 extends B {
 + void accept(A a) {
 + a.interactWith(this);​
 + }
 +}
 +</​code>​
 +
 +Să ne gândim ce s-ar întâmpla dacă am avea următorul snippet (analog cu cel precedent, puțin schimbat):
 +
 +<code java>
 +A a = new A1();
 +B b = new B2();
 +b.accept(a);​
 +</​code>​
 +
 +**Întrebarea 1:** Ar avea, oare, același efect ca mai devreme? De ce? Încercați!
 +
 +Schimbarea mai mare intervine la clasele ''​A''​. Să spargem implementarea ''​interactWith(B b)'',​ mutând codul din fiecare bloc ''​instanceof''​ în metode separate, ca în exemplul următor:
 +
 +<code java>
 +void interactWith(B b) {
 +// action for B
 +}
 +
 +void interactWith(B1 b) {
 +// action for B1
 +}
 +
 +void interactWith(B2 b) {
 +// action for B2
 +}
 +</​code>​
 +
 +**Întrebarea 2**: să ne gândim la snippet-ul anterior și să observăm ce se întâmplă la apelul ''​b.accept(a)'':​ ''​b''​ fiind de fapt un ''​B2'',​ se va apela ''​accept''​ din clasa ''​B2'',​ care cheamă ''​a.interactWith(this)'',​ ''​this''​ fiind un ''​B2''​. Se va apela deci una dintre metodele ''​interactWith(B2 b)'',​ dar din care clasă ''​A''?​ De ce?
 +
 +Am ajuns deci să avem:
 +
 +  * câte o metodă ''​interactWith''​ în fiecare clasă ''​A'',​ pentru fiecare tip ''​B''​ în parte
 +  * câte o metodă ''​accept(A a)''​ în fiecare clasă B
 +
 +Ne folosim, astfel, de tipurile concrete ale obiectelor care interacționează,​ **fără să testăm efectiv tipurile adevărate** în cod. Astfel, gândind în perspectivă,​ să răspundem la aceleași întrebări:​
 +
 +  * Dacă adăugăm o nouă clasă în ierarhia ''​A'',​ atunci ea trebuie să suprascrie toate metodele ''​interactWith''​. Oricum va trebui să implementeze operațiile diferite pentru fiecare ''​B''​ în parte, iar **modularizarea este un plus**.
 +  * Dacă adăugăm o nouă clasă în ierarhia ''​B''​ (fie ea ''​B3''​),​ trebuie să suprascrie metoda ''​accept(A a)'',​ a cărei implementare va fi o singură linie: ''​a.interactWith(this)''​ => trebuie ca fiecare clasă ''​A''​ să aibă o nouă metodă ''​interactWith(B3 b)''​.
 +  * Cum arată codul pentru ierarhii mai mari? **Structurat și organizat**:​
 +    * fiecare clasă ''​A''​ are propriile implementări de interacțiuni,​ pe care, la nevoie, le putem modifica imediat, punctual, fără să căutăm în ''​instanceof''​-uri
 +    * nu mai facem **absolut niciun test de tip concret** pentru obiectele care interacționează;​ un mare beneficiu din punctul de vedere al extensibilității
 +
 +De ce se numește acest mecanism double dispatch? //​Dispatch//​ este termenul folosit atunci când gândim ca mașina virtuală Java și ne referim la metoda //​concretă//​ care se apelează la un moment dat. //Double// pentru că interacțiunea observăm că funcționează în ambele sensuri: ''​B''​-urile cheamă metoda proprie, concretă de ''​accept'',​ care apelează metoda concretă ''​interactWith''​ din clasa ''​A''​.
laboratoare/tutorial-doubledispatch.1414331301.txt.gz · Last modified: 2014/10/26 15:48 by Daniel Ciocirlan