Differences

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

Link to this comparison view

poo-ca-cd:laboratoare:design-patterns2 [2022/12/11 18:31]
bogdan.vatamanu [Exerciții]
poo-ca-cd:laboratoare:design-patterns2 [2022/12/18 01:22] (current)
cvintilian.rosca [Exerciții]
Line 1: Line 1:
-===== Laboratorul 10: Design patterns - Command și Builder ​=====+===== Laboratorul 10: Genericitate ​=====
  
-**Video introductiv:​** [[https://​www.youtube.com/​watch?​v=F2HMN4mvVYY | link]] 
 ==== Obiective ==== ==== Obiective ====
  
-Scopul acestui laborator este familiarizarea cu folosirea design pattern-ului comportamental //​Command// ​și a design pattern-ului creațional //Builder//.+ 
 +Scopul acestui laborator este prezentarea conceptului de genericitate ​și modalitățile de creare și folosire ​claselor, metodelor și interfețelor generice în Java. 
 + 
 +Aspectele urmărite sunt: 
 +  * prezentarea structurilor generice simple 
 +  * conceptele de wildcard și bounded wildcards 
 +  * utilitatea genericității în design-ul unui sistem
  
  
 ==== Introducere ==== ==== Introducere ====
  
-În laboratoarele precedent am prezentat pattern-uri ce vă ajută în realizarea unei arhitecturi ​mai decuplate, modulare și extensibile a aplicațiilor: +Să urmărim exemplul de mai jos:
-  * pattern-uri creaționale ce decuplează logica creării obiectelor: [[:​poo-ca-cd:​laboratoare:​static-final#​singleton_pattern|Singleton]],​ [[:​poo-ca-cd:​laboratoare:​design-patterns#​factory|Factory]] +
-  * pattern-uri comportamentale ce decuplează comunicarea dintre componente: [[:​poo-ca-cd:​laboratoare:​visitor#​visitor|Visitor]],​ [[:​poo-ca-cd:​laboratoare:​design-patterns#​observer_pattern|Observer]],​ [[:​poo-ca-cd:​laboratoare:​design-patterns#​strategy_pattern|Strategy]]+
  
-În acest laborator vom prezenta ​//Command// și //​Builder//,​ un pattern comportamental care decuplează obiectele care execută anumite acțiuni de obiectele care le invocă și un pattern creațional care oferă o soluție mai flexibilă pentru a crea obiecte complexe și este adesea însoțită de un model de interfață fluentă.+<code java> 
 +List myIntList = new LinkedList();​  
 +myIntList.add(new Integer(0));​ 
 +Integer x = (Integer) myIntList.iterator().next();​ 
 +</code>
  
-==== Command ====+Se observă necesitatea operației de //cast// pentru a identifica corect variabila obținută din listă. Această situație are mai multe dezavantaje:​ 
 +  * Este îngreunată citirea codului 
 +  * Apare posibilitatea unor erori la execuție, în momentul în care în listă se introduce un obiect care nu este de tipul ''​Integer''​.
  
-Design pattern-ul //Command// incapsulează un apel cu tot cu parametri într-o clasă cu interfată genericăAcesta este //​Behavioral//​ pentru că modifică interacțiunea dintre componentemai exact felul în care se efectuează apelurile. ​  +Genericitatea intervine tocmai pentru a elimina aceste problemeConcretsă urmărim secvența de cod de mai jos:
-   +
-Acest pattern este recomandat în următoarele cazuri: ​  +
-  * pentru ​ușura crearea ​de structuri ​de delegare, de callback, de apelare intarziată  +
-  * pentru a reține lista de comenzi efectuate asupra obiectelor  +
-     * accounting  +
-     * liste de Undo, Rollback pentru tranzacții-suport pentru operații reversibile (//undoable operations//​)  +
-   +
-Exemple de utilizare +
-  * sisteme de logging, accounting pentru tranzacții  +
-  * sisteme de undo (ex. editare imagini)  +
-  * mecanism ordonat pentru delegare, apel întârziat,​ callback  +
-   +
-=== Funcționare și necesitate ===+
  
-În esentă, Command pattern ​(așa cum v-ați obișnuit și lucrând cu celelate Pattern-uri pe larg cunoscutepresupune încapsularea unei informații referitoare la acțiuni/​comenzi folosind un wrapper pentru a "ține minte această informație"​ și pentru a o folosi ulteriorAstfel, un astfel de wrapper va deține informații referitoare la tipul acțiunii respective ​(în general un asemenea wrapper va expunde o metodă execute(), care va descrie comportamentul pentru acțiunea respectivă) +<code java> 
-   +List<​Integer>​ myIntList = new LinkedList<​Integer>​();  
-Mai mult încă, când vorbim de Command Pattern, în terminologia OOP o să întâlniți deseori și noțiunea de //​Invoker//​. Invoker-ul este un middleware ca funcționalitate care realizează managementul comenzilor. Practic, un //Client//, care vrea să facă anumite acțiune, va instanția clase care implementează o interfață //Command//Ar fi incomod ca, în cazul în care aceste instanțieri de comenzi provin din mai multe locuri, acest management de comenzi să se facă local, în fiecare parte (din rațiuni de economie, nu vrem să duplicăm cod). Invoker-ul apare ca o necesitate de a centraliza acest proces și de a realiza intern management-ul comenzilor ​(le ține într-o listă, ține cont de eventuale dependențe între ele, totul în funcție de context) +myIntList.add(new Integer(0)); 
-   +Integer x = myIntList.iterator().next(); 
-Un client (generic spus, un loc de unde se lansează comenzi) instanțiază comenzile și le pasează Invoker-ului. Din acest motiv Invoker-ul este un middleware între client și receiver, fiindcă acesta va apela execute pe fiecare Command, în funcție de logica să internă. ​+</​code>​
  
-//__Recomandare__//: La Referinte aveti un link catre un post pe StackOverflowpentru a intelege mai bine de ce aveti nevoie ​de Pattern-ul Command si de ce nu lansati comenzi pur si simplu.+În această situație, lista nu mai conține obiecte oarecare, ci poate conține doar obiecte de tipul ''​Integer''​. În plus, observăm că a dispărut și //cast//-ul. De această dată**verificarea tipurilor este efectuată ​de compilator**,​ ceea ce elimină potențialele erori de execuție cauzate ​de //​cast//​-uri incorecteLa modul general, beneficiile dobândite prin utilizarea genericității constau în: 
 +  * îmbunătățirea lizibilității codului 
 +  * creșterea gradului de robusteţe
  
-=== Structura ​===+==== Definirea unor structuri generice simple ====
  
-Ideea principală este de a crea un obiect de tip //Command// care va reține parametrii pentru comandă. Comandantul reține o referință la comandă ​și nu la componenta comandată. Comanda propriu-zisă este anunțată obiectului //Command// (de către comandant) prin execuția unei metode specificate asupra lui. Obiectul //Command// este apoi responsabil de trimiterea (//​dispatch//​) comenzii către obiectele care o îndeplinesc (//​comandați//​)+Să urmărim câteva elemente din definiția oferită de Java pentru tipurile ''​List'' ​și ''​Iterator''​.
  
-{{ .:​design-patterns:​command.png |Diagrama de stări pentru Command Pattern}} +<code java> 
-   +public interface List<​E> ​
- ​Tipuri de componente (**roluri**):​  +    ​void add(E x); 
-  * **Invoker** - comandantul ​ +    ​Iterator<​E>​ iterator(); 
-    ​* apelează acțiuni pe comenzi ​(invocă metode oferite de obiectele de tip //Command//)  +}
-    ​* poate menține, dacă e cazul, o //listă a tutoror comenzilor aplicate// pe obiectul ​(obiectelecomandate. Este necesară reținerea acestei liste de comenzi atunci când implementăm un comportament de undo/redo al comenzilor. ​ +
-    * primește clase //Command// pe care să le invoce +
-  * **Receiver** - comandatul +
-    * este clasa asupra căreia se face apelul  +
-    * conține implementarea efectivă a ceea ce se dorește executat +
-  * **Command** - obiectele pentru reprezentarea comenzilor implementează această interfață/​o extind dacă este clasă abstractă  +
-    * //concrete command// - ne referim la implementări/​subclasele acesteia ​  +
-    * de obicei conțin metode cu nume sugestiv pentru executarea acțiunii comenzii (e.g. ''​execute()''​). Implementările acestora conțin apelul către clasa //​Receiver//​. +
-    * în cazul implementării unor acțiuni //​undoable//​ adăugăm metode pentru ''​undo''​ și/sau ''​redo''​.  +
-    * țin referințe către comandați (receivers) pentru a aplica/​invoca acțiunea ce reprezintă acea comandă ​+
  
-În Java, se pot folosi atât interfețe cât și clase abstracte, pentru Command, depinzând de situație ​(e.g. clasă abstractă dacă știm sigur ca obiectele de tip Command nu mai au nevoie să extindă și alte clase).+public interface Iterator<​E>​ { 
 +    E next()
 +    boolean hasNext();​ 
 +    void remove(); 
 +
 +</​code>​
  
-În prima diagramă de mai jos, comandantul este clasa //Invoker// care conține o referință la o instanță (commanda clasei (Command). //Invoker// va apela metoda abstractă ''​execute()'' ​pentru a cere îndeplinirea comenzii. //​ConcreteCommand//​ reprezintă o implementare a interfeței //​Command//,​ iar în metoda ''​execute()''​ va apela metoda din //​Receiver//​ corespunzătoare acelei acțiuni/​comenzi+Sintaxa ''<​E>''​ (poate fi folosită orice literă) este folosită pentru a defini tipuri formale în cadrul ​interfețelor. Aceste tipuri pot fi folosite ​în mod asemănător cu tipurile uzuale, cu anumite restricții totușiÎn momentul în care invocăm o structură generică ele vor fi înlocuite cu tipurile efective utilizate în invocare. Concret, fie un apel de forma: 
-   + 
-=== Exemplu ===+<code java> 
 +ArrayList<​Integer>​ myList ​new ArrayList<​Integer>​();​ 
 +Iterator<​Integer>​ it myList.iterator();​ 
 +</​code>​
  
-Prima diagramă de secvență prezintă apelurile în cadrul unei aplicație de editare ​imaginilor, ce este structurată folosind pattern-ul Command. În cadrul acesteia, Receiver-ul este //Image//, iar comenzile //​BlurCommand//​ și //​ResizeCommand//​ modifică starea acesteia. Structurând aplicația în felul acesta, este foarte ușor de implementat un mecanism de undo/redo, fiind suficient să menținem în Invoker o listă ​cu obiectele de tip //Command// aplicate imaginii+În această situație, tipul formal ''​E'' ​fost înlocuit (la compilare) ​cu tipul efectiv ''​Integer''​.
  
-{{ .design-patterns:​imageeditor_example.png | Diagrama de secvență pentru comenzile de prelucrare a imaginilor}}+==== Genericitatea în subtipuri ====
  
-Pornind de la această diagramă, putem realiza o implementare a pattern-ului Command.  +Să considerăm următoarea situaţie:
-Vom construi clasa //Image//, care va juca rolul Receiver-ului. Acesteia îi vom asocia un câmp //​blurStrength//,​ care ne va oferi informații despre intensitatea filtrului de blur, și încă două câmpuri //length// și //width// care ne vor spune ce dimensiune are imaginea. Valorile acestor câmpuri vor fi alterate în urma aplicării comezilor de //blur// și //resize//.+
  
 <code java> <code java>
-public class Image { +List<​String>​ stringList = new ArrayList<​String>​()// 1 
-   ​private int blurStrength+List<​Object>​ objectList = stringList             // 2 
-   private int length+</​code>​
-   private int width;+
  
-   ​public Image(int lengthint width) { +Operația 1 este evident corectăînsă este corectă și operația 2? Presupunând că ar fi, am putea introduce în ''​objectList''​ orice fel de obiect, nu doar obiecte de tip ''​String'',​ fapt ce ar conduce la potențiale erori de execuție, astfel:
-       ​this.length = length; +
-       ​this.width = width; +
-   }+
  
-   ​public int getBlurStrength() { +<code java> 
-       return blurStrength+objectList.add(new Object());  
-   }+String s = stringList.get(0)// Aceasta operaţie ar fi ilegală 
 +</​code>​
  
-   ​public void setBlurStrength(int blurStrength+Din acest motiv, operația 2 **nu va fi permisă de către compilator**!  
-       this.blurStrength = blurStrength;​ +<note important>​ 
-   }+Dacă //​ChildType//​ este un subtip ​(clasă descendentă sau subinterfațăal lui //​ParentType//,​ atunci o structură generică //​GenericStructure<​ChildType>//​ **nu** este un subtip al lui //​GenericStructure<​ParentType>//​**Atenție** la acest concept, întrucât el nu este intuitiv! 
 +</​note>​ 
 +==== Wildcards ====
  
-   ​public int getLength() { +//​Wildcard//​-urile sunt utilizate atunci când dorim să întrebuințăm o structură generică drept //​parametru//​ într-o funcție și nu dorim să limităm tipul de date din colecția respectivă.
-       ​return length; +
-   }+
  
-   ​public ​void setLength(int length) { +<code java> 
-       this.length = length; +void printCollection(Collection<​Object>​ c) { 
-   } +    ​for ​(Object e : c
- +        ​System.out.println(e);
-   ​public int getWidth() { +
-       return width; +
-   } +
- +
-   ​public void setWidth(int width+
-       ​this.width = width; +
-   }+
 } }
 </​code>​ </​code>​
  
-//Command// va fi interfață, căreia pe langă metoda ​de //execute()// îi vom asocia și o metodă de //undo()// +De exemplu, ​situație precum cea de mai sus ne-ar restricționa să folosim la apelul funcției doar o colecţie cu elemente ​de tip ''​Object'',​ care **nu** ​//poate fi convertită la o colecție ​de un alt tip//, după cum am văzut mai susAceastă restricție este eliminată de folosirea **wildcard**-urilor,​ după cum se poate vedea:
-<code java> +
-interface Command { +
-   void execute();+
  
-   void undo();+<code java> 
 +void printCollection(Collection<?>​ c) { 
 +    for (Object e : c) 
 +        System.out.println(e);
 } }
 </​code>​ </​code>​
  
-//​BlurCommand//​ și //​ResizeCommand//​ vor implementa interfaț//Command//. La apelul //​execute()//,​ //​BlurCommand//​ va modifica câmpul //​blurStrength//​ din clasa //Image//, iar //​ResizeCommand//​ va modifica dimensiunea,​ lungimea și înălțimea imaginii. Întrucât ne dorim să implementăm un mecanism de undo, este nevoie să reținem valoare anterioară.+O limitare care intervine însă este că **nu putem adăuga elemente arbitrare** într-o colecție cu //wildcard//-uri:
  
 <code java> <code java>
-// Concrete command+Collection<?>​ c = new ArrayList<​String>​();  ​// Operaţie permisă 
 +c.add(new Object()); ​                       // Eroare la compilare 
 +</​code>​
  
-public class BlurCommand implements Command { +Eroarea apare deoarece nu putem adăuga într-o colecţie generică decât elemente **de un anumit tip**, iar //​wildcard//​-ul **nu indică un tip anume**.
-   ​private final Image image; +
-   ​private int previousBlurStrength;​ +
-   ​private int nextBlurStrength;​+
  
-   ​public BlurCommand(Image image, int blurStrength) { +<​note>​ 
-       this.image = image; +Aceasta înseamnă că nu putem adăuga nici măcar elemente de tip ''​String''​Singurul element care poate fi adăugat este însă ''​null'',​ întrucât acesta este membru al oricărui tip referințăPe de altă parte, operațiile de tip //getter// sunt posibile, întrucât rezultatul acestora poate fi mereu interpretat drept ''​Object'':​ 
-       this.nextBlurStrength = blurStrength;​ +</​note>​
-   }+
  
-   ​@Override +<code java> 
-   public void execute() { +List<?>​ someList = new ArrayList<​String>​(); 
-       previousBlurStrength = image.getBlurStrength(); +((ArrayList<​String>​)someList).add("Some String"​); 
-       image.setBlurStrength(nextBlurStrength); +Object item = someList.get(0); 
-   }+</​code>​
  
-   ​@Override +==== Bounded Wildcards ====
-   ​public void undo() { +
-       ​nextBlurStrength ​previousBlurStrength;​ +
-       ​previousBlurStrength ​image.getBlurStrength();​ +
-       ​image.setBlurStrength(nextBlurStrength);​ +
-   } +
-}+
  
-public class ResizeCommand implements Command { +În anumite situații, faptul că un //​wildcard//​ poate fi înlocuit cu orice tip se poate dovedi un inconvenient. Mecanismul bazat pe **Bounded Wildcards** permite introducerea unor restricţii asupra tipurilor ce pot înlocui un //​wildcard//,​ obligându-le să se afle într-o relație ierarhică (de descendență) față de un tip fix specificat.
-   ​private final Image image; +
-   ​private int previousWidth;​ +
-   ​private int previousLength;​ +
-   ​private int nextWidth;​ +
-   ​private int nextLength;+
  
-   ​public ResizeCommand(Image image, int width, int length) { +Exemplificăm acest mecanism:
-       ​this.image = image; +
-       ​nextWidth = width; +
-       ​nextLength = length; +
-   }+
  
-   ​@Override +<code java> 
-   public void execute() ​+class Pizza 
-       previousWidth ​image.getWidth();​ +    ​protected String name "​Pizza"​;
-       ​image.setWidth(nextWidth);+
  
-       ​previousLength = image.getLength(); +    public String getName() 
-       image.setLength(nextLength);​ +        return name
-   ​}+    } 
 +}
  
-   @Override +class HamPizza extends Pizza { 
-   public ​void undo() { +    ​public ​HamPizza() { 
-       nextWidth ​previousWidth+        ​name ​"​HamPizza"​
-       previousWidth = image.getWidth();​ +    } 
-       image.setWidth(nextWidth);​+}
  
-       ​nextLength = previousLength;​ +class CheesePizza extends Pizza { 
-       previousLength = image.getLength(); +    ​public CheesePizza() { 
-       image.setLength(nextLength)+        name = "​CheesePizza"​
-   ​}+    }
 } }
-</​code>​ 
-Invoker-ul este clasa //Editor//. Aceasta va avea două metode, edit si undo, care vor fi apelate de către user. În plus, vom păstra în această clasă și o listă a comenzilor aplicate pe imagine. Cu acestă listă vom putea implementăm un comportament de undo.\\ 
-Metoda edit va primi ca parametru o referiță la o instanță command, apoi va fi inițiată o cerere de către //Editor// prin apelarea metodei //​execute()//,​ cerând astfel execuția comenzii. 
  
-<code java> +class MyApplication { 
-// Invoker+    // Aici folosim "​bounded wildcards"​ 
 +    public static void listPizza(List<?​ extends Pizza> pizzaList) { 
 +        for(Pizza item : pizzaList) 
 +            System.out.println(item.getName());​ 
 +    }
  
-public ​class Editor ​+    ​public ​static void main(String[] args) 
-   // LinkedList este folosit ca stivă în Java +        List<PizzapList = new ArrayList<Pizza>();
-   ​private LinkedList<Commandhistory ​= new LinkedList<>​(); ​// păstrează comenzile aplicate pe imagine+
  
-   ​public void edit(Command command) { +        pList.add(new HamPizza()); 
-       ​history.push(command); +        pList.add(new CheesePizza()); 
-       command.execute(); +        pList.add(new Pizza()); 
-   } +                 
- +        ​MyApplication.listPizza(pList); 
-   ​public void undo() +        // Se va afişa: "​HamPizza",​ "​CheesePizza",​ "​Pizza"​ 
-       if (history.isEmpty()) return+    }
- +
-       ​Command command = history.pop(); +
-       if (command != null) { +
-           command.undo(); +
-       } +
-   ​}+
 } }
- 
 </​code>​ </​code>​
-  ​ 
-Pe [[http://​en.wikipedia.org/​wiki/​Command_pattern | Wikipedia]] puteți analiza exemplul PressSwitch. Flow-ul pentru acesta este ilustrat în diagrama de mai jos 
-{{ .:​design-patterns:​command_switch_sequence.png | Diagrama de secvență pentru comenzile de aprindere/​stingere a switch-ului }} 
  
-==== Builder ====+Sintaxa ''​List<?​ extends Pizza>''​ ([[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​upperBounded.html|Upper Bounded Wildcards]]) impune ca tipul elementelor listei să fie ''​Pizza''​ sau o subclasă a acesteia. Astfel, ''​pList''​ ar fi putut avea, la fel de bine, tipul ''​List<​HamPizza>''​ sau ''​List<​CheesePizza>''​. În mod similar, putem imprima constrângerea ca tipul elementelor listei să fie ''​Pizza''​ sau o superclasă a acesteia, utilizând sintaxa ''​List<?​ super Pizza>''​ ([[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​lowerBounded.html|Lower Bounded Wildcards]]).
  
-Design pattern-ul Builder este un design pattern creațional,​ cu alte cuvinte, este utilizat pentru a crea și configura obiecte. Un builder este utilizat ​în mod obișnuit pentru eliminarea supraincarcarilor de constructori multipli și oferă o soluție mai flexibilă la crearea obiectelor complexe.+<​note>​ 
 +Utilizarea bounded wildcards se manifestă ​în următoarele 2 situații : 
 +  * lower bounded wildcards se folosesc atunci când vrem să **modificăm** o colecție generică 
 +  * upper bounded wildcards se folosesc atunci când vrem să **parcurgem fără să modificăm** colecție generică 
 +</​note>​
  
-=== Problema ​===+==== Type Erasure ====
  
-În Programarea Orientată pe Obiecte, cel mai adesea avem clase care dețin unele date pe care le setăm și le accesăm ulterior. Crearea instanțelor unor astfel ​de clase ar putea fi uneori cam laborioasă. Să luăm în considerare ​următoarea clasă de Pizza+[[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​erasure.html|Type Erasure]] este un mecanism prin care compilatorul Java înlocuieşte la **compile time** parametrii ​de genericitate ai unei clase generice cu prima lor apariţie (ţinând cont de restricţii în cazul Bounded Wildcards) sau cu ''​Object''​ dacă parametrii nu apar (Raw Type)De exemplu, ​următorul cod:
  
-<​code ​java Pizza.java> +<code java> 
-public class Pizza { +List<String> list = new ArrayList<​String>​()
-    private ​String ​pizzaSize+list.add("​foo"​)
-    ​private int cheeseCount+String x = list.get(0);
-    ​private int pepperoniCount; +
-    private int hamCount; +
-     +
-    // constructor,​ getters, setters +
-}+
 </​code>​ </​code>​
  
-O clasă foarte simplă la prima vedere însă lucrurile ​se complică pe masură ce vom crea acest obiect. Oricare pizza va avea o dimensiune, cu toate acestea, atunci când vine vorba de topping-uri,​ unele sau toate pot fi prezente sau deloc, prin urmare, unele dintre proprietățile clasei noastre sunt opționale, iar altele sunt obligatorii.+se va transforma după acest pas al compilării în:
  
-== Supraîncărcarea constructorilor ==+<code java> 
 +List list new ArrayList();​ 
 +list.add("​foo"​);​ 
 +String x (String) list.get(0);​ 
 +</​code>​
  
-Crearea unei instanțe noi //new Pizza("​small",​ 1, 0, 0)// de fiecare dată când vreau să obțin pur și simplu un obiect pizza cu brânză și nimic altceva nu mi se pare o idee bună. Și aici vine prima soluție comună - supraîncărcarea constructorului.+Să urmărim următorul fragment de cod:
  
-<​code ​java Pizza.java> +<code java> 
-public ​class Pizza +class  ​GenericClass <​T> ​
-    ​private String pizzaSize; // mandatory +    ​void genericFunction(List<String> stringList) { 
-    private int cheeseCount;​ // optional +        ​stringList.add("​foo"​);
-    private int pepperoniCount;​ // optional +
-    private int hamCount; // optional +
-     +
-    public Pizza(String ​pizzaSize) { +
-        ​this(pizzaSize, 0, 0, 0);+
     }     }
-     +    ​// {...} 
-    public ​Pizza(String ​pizzaSize, int cheeseCount) { +    public ​static void main(String[] args) { 
-        ​this(pizzaSize, cheeseCount,​ 0, 0);+        ​GenericClass genericClass = new GenericClass();​ 
 +        List<​Integer>​ integerList = new ArrayList<​Integer>​();​ 
 + 
 +        integerList.add(100);​ 
 +        genericClass.genericFunction(integerList);​ 
 + 
 +        System.out.println(integerList.get(0)); // 100 
 +        System.out.println(integerList.get(1)); // foo
     }     }
-    ​ 
-    public Pizza(String pizzaSize, int cheeseCount,​ int pepperoniCount) { 
-        this(pizzaSize,​ cheeseCount,​ pepperoniCount,​ 0); 
-    } 
-    ​ 
-    public Pizza(String pizzaSize, int cheeseCount,​ int pepperoniCount,​ int hamCount) { 
-        this.pizzaSize = pizzaSize; 
-        this.cheeseCount = cheeseCount;​ 
-        this.pepperoniCount = pepperoniCount;​ 
-        this.hamCount = hamCount; 
-    } 
-    ​ 
-    // getters 
 } }
 </​code>​ </​code>​
  
-Cu toate acestea, am rezolvat problema doar parțial. Nu putem, de exemplu, să creăm o pizza cu brânză și șuncă, dar fără pepperoni ​ca aceasta ​//new Pizza("​small",​ 11)//, deoarece al treilea argument al constructorului este pepperoniȘi aici vine a doua soluție comună - și mai multă supraîncărcare de constructori+Observăm că în ''​main''​ se instanţiază clasa ''​GenericClass''​ cu Raw Typeapoi se trimite ​ca argument metodei ''​genericFunction''​ un ''​ArrayList<​Integer>''​. Codul nu va genera erori şi va afişa //100//, apoi //foo//. Acest lucru se întâmplă tot din cauza mecanismului de **Type Erasure**. Să urmărim ce se întâmplă: la instanţierea clasei ''​GenericClass''​ nu se specifică tipul generic al acesteia iar compilatorul va înlocui în corpul clasei peste tot ''​T''​ cu ''​Object''​ şi va dezactiva verificarea ​de tipAșadar, obiectul ''​genericClass''​ va aparţine unei clase de forma:
  
-<​code ​java Pizza.java> +<code java> 
-public ​class Pizza +class  ​GenericClass ​
-    ​private String pizzaSize; // mandatory +    ​void genericFunction(List stringList) { 
-    private String crust; // mandatory +        ​stringList.add("​foo"​);
-    private int cheeseCount;​ // optional +
-    private int pepperoniCount;​ // optional +
-    private int hamCount; // optional +
-    private int mushroomsCount;​ // optional +
-     +
-    public Pizza(String pizzaSize, String crust) { +
-        ​this(pizzaSize, crust, 0, 0, 0, 0);+
     }     }
-    ​ +    ​// {...}
-    public Pizza(String pizzaSize, String crust, int cheeseCount) ​{ +
-        this(pizzaSize,​ crust, cheeseCount,​ 0, 0, 0); +
-    } +
-     +
-    public Pizza(String pizzaSize, String crust, int cheeseCount,​ int pepperoniCount) { +
-        this(pizzaSize,​ crust, cheeseCount,​ pepperoniCount,​ 0, 0); +
-    } +
-     +
-    public Pizza(String pizzaSize, String crust, int cheeseCount,​ int pepperoniCount,​ int hamCount) { +
-        this(pizzaSize,​ crust, cheeseCount,​ pepperoniCount,​ hamCount, 0); +
-    } +
-     +
-    public Pizza(String pizzaSize, String crust, int cheeseCount,​ int pepperoniCount,​ int hamCount, int mushroomsCount) { +
-        this.pizzaSize = pizzaSize;​ +
-        this.crust = crust; +
-        this.cheeseCount = cheeseCount;​ +
-        this.pepperoniCount = pepperoniCount;​ +
-        this.hamCount = hamCount; +
-        this.mushroomsCount = mushroomsCount;​ +
-    ​} +
-     +
-    // getters+
 } }
-</​code>​ 
- 
-<note tip>​Gândiți-vă ce se va întampla dacă se schimbă ordinea parametrilor. Acest lucru minor va strica funcționalitatea completă a creative unei instanțe de Pizza.</​note>​ 
- 
-În concluzie, modelul de constructori supraincarcati funcționează,​ dar este greu de menținut dacă se schimbă funcționalitatea și introducem noi parametri, numărul constructorilor va crește, de asemenea. 
- 
-== Folosirea de getters și setters == 
- 
-<code java Pizza.java>​ 
-Pizza pizza = new Pizza(); 
- 
-pizza.setPizzaSize("​small"​);​ 
-pizza.setCrust("​thin"​);​ 
-pizza.setMushroomsCount(1);​ 
-pizza.setCheeseCount(1);​ 
  
-// do something with pizza 
 </​code>​ </​code>​
 +<note important>​
 +Modelul de mai sus este **bad practice** tocmai pentru că are un comportament nedeterminat și poate conduce la erori. De aceea nu e recomandat să folosiți Raw Types, ci să specificați **întotdeauna** tipul obiectelor în cazul instanțierii claselor generice!
 +</​note>​
 +==== Metode generice ====
  
-Această soluție nu prezintă niciunul dintre dezavantajele modelului anterior. Este ușor să scalați clasa, mai ușor de instanțiat,​ mai ușor de citit și mai flexibil+Java ne oferă posibilitatea scrierii ​de metode generice (deci având un tip-parametru) pentru a facilita prelucrarea unor structuri generice
-Modelul are însă dezavantaje graveConstrucția clasei este împărțită în apeluri multiple, prin urmare instanța poate fi într-o ​stare parțial construită / invalidă.+Să exemplificăm acest faptObservăm în continuare 2 căi de implementare ale unei metode ce copiază elementele unui vector intrinsec ​într-o ​colecție:
  
-== Folosirea builder pattern ==+<code java> 
 +// Metoda corectă 
 +static <T> void correctCopy(T[] a, Collection<​T>​ c) { 
 +    for (T o : a) 
 +        c.add(o); // Operaţia va fi permisă 
 +}
  
-<code java Pizza.java>​ +// Metoda incorectă 
-public class Pizza { +static void incorrectCopy(Object[] aCollection<?>​ c) { 
-    private String pizzaSize;​ +    ​for ​(Object o : a
-    private String crust; +        ​c.add(o); // Operatie incorectă, semnalată ca eroare de către compilator
-    private int cheeseCount;​ +
-    private int pepperoniCount;​ +
-    private int hamCount; +
-    private int mushroomsCount;​ +
-     +
-    public static class Builder { +
-        private String pizzaSize; ​// mandatory +
-        ​private String crust; // mandatory +
-        private int cheeseCount = 0; // optional +
-        private int pepperoniCount = 0; // optional +
-        private int hamCount = 0; // optional +
-        private int mushroomsCount = 0; // optional +
-         +
-        public Builder(String pizzaSizeString crust) { +
-            ​this.pizzaSize = pizzaSize;​ +
-            this.crust = crust; +
-        } +
-         +
-        public Builder cheeseCount(int cheeseCount+
-            this.cheeseCount = cheeseCount;​ +
-            return this; +
-        ​+
-         +
-        public Builder pepperoniCount(int pepperoniCount) { +
-            this.pepperoniCount = pepperoniCount;​ +
-            return this; +
-        } +
-         +
-        public Builder hamCount(int hamCount+
-            this.hamCount = hamCount; +
-            return this; +
-        } +
-         +
-        public Builder mushroomsCount(int mushroomsCount) { +
-            this.mushroomsCount = mushroomsCount;​ +
-            return this; +
-        } +
-         +
-        public Pizza build() { +
-            return new Pizza(this);​ +
-        } +
-    } +
-     +
-    private Pizza(Builder builder) { +
-        this.pizzaSize = builder.pizzaSize;​ +
-        this.crust = builder.crust;​ +
-        this.cheeseCount = builder.cheeseCount;​ +
-        this.pepperoniCount = builder.pepperoniCount;​ +
-        this.hamCount = builder.hamCount;​ +
-        this.mushroomsCount = builder.mushroomsCount;​ +
-    } +
-     +
-    ​// getters+
 } }
 </​code>​ </​code>​
  
-Am făcut constructorul privatastfel încât clasa noastră să nu poată fi instanțiată direct. În același timp am adăugat clasă static Builder ​cu un constructor care are parametrii noștri obligatori pizzaSize și crustmetode ​de setare a parametrilor opționali ​șiîn finalo metodă ​//build()// metoda care va returna o nouă instanță a clasei PizzaMetodele setter returnează instanța de builder în sineoferindu-ne astfel o interfață fluentă cu metoda ​de înlănțuire.+Trebuie remarcat faptul că ''​correctCopy()''​ este o metodă validă, care se execută corect, însă ''​incorrectCopy()'' ​nu este, din cauza limitării pe care o cunoaştem deja, referitoare la adăugarea elementelor într-colecție generică cu tip specificat. Putem remarca, de asemenea, că, și în acest cazputem folosi ​//wildcards// sau //bounded wildcards//. Astfelurmătoarele declaraţii ​de metode sunt corecte:
  
-<​code ​java Pizza.java> +<code java> 
-Pizza pizza = new Pizza.Builder("​large"​"​thin"​) +// Copiază elementele dintr-o listă în altă listă 
-    ​.cheeseCount(1) +public static <T> void copy(List<​T>​ destList<? extends T> src... 
-    ​.pepperoniCount(1) + 
-    ​.build();+// Adaugă elemente dintr-o colecţie în alta, cu restricţionarea tipului generic 
 +public <T extends E> boolean addAll(Collection<​T>​ c);
 </​code>​ </​code>​
  
-Este mult mai ușor să scrieți și, mai important, să citiți acest cod. La fel ca în cazul constructorului,​ putem verifica parametrii trecuți pentru orice încălcare, cel mai adesea în cadrul metodei //build()// sau a metodei setterși putem arunca IllegalStateException dacă există încălcări înainte de crea o instanță a clasei. +==== Exemple genericitate ====  
- +Probabil nu sunteți familiari ​încă ​cu termenul de “GPU Computing” ​(utilizarea unui procesor grafic pentru accelerarea calculelor), dar probabil ​ați exploatat una dintre întrebuințările ei, mai exact jocurile video.
-Modelul Builder are unele dezavantaje minore. În primul rând, trebuie să creați un obiect Builder înainte de a crea obiectul clasei în sine. Aceasta ar putea fi o problemă în unele cazuri critice de performanță iar clasa devine puțin mai mare când vine vorba de liniile de cod. +
- +
-În ansamblumodelul Builder este o tehnică foarte frecvent utilizată pentru crearea obiectelor și este o alegere bună de utilizat atunci când clasele au constructori cu parametri multipli (în special opționali) și este posibil să se schimbe în viitor. Codul devine mult mai ușor de scris și de citit în comparație cu constructorii supraincarcati,​ iar clasa este la fel de bună ca folosirea de getters și setters, dar este mult mai sigură.+
  
 +Jocurile video sunt create cu ajutorul unor engine-uri grafice, care în esență nu reprezintă altceva decât aplicații care realizează o multitudine de operații matematice: plotări de grafice, interpolări,​ operații matriceale/​vectoriale,​ derivări etc.
  
 +Aceste operații matematice pot fi făcute pe diferite tipuri de date. O matrice poate acceptă int-uri (exemplu: camera jucătorului),​ float-uri / double-uri (exemplu: setarea opacității unei texturi), char-uri (exemplu: reprezentarea text box-urilor pentru dialog) etc. În loc să creem câte o clasă care să adere fiecărui tip, putem scrie o singură dată o clasă care să reprezinte o matrice și care să accepte mai multe tipuri de date prin genericitate. Acest lucru devine foarte util dacă dorim să creem o bibliotecă întreagă pentru operații matematice avansate (exemplu: Jscience), fără să ne repetăm codul doar pentru a crea clase și metode specifice unor tipuri de date.
 ==== Exerciții ==== ==== Exerciții ====
 +Scheletul laboratorului poate fi descărcat de aici: {{:​poo-ca-cd:​laboratoare:​oop_lab10.zip|}}
  
 +Laboratorul trebuie rezolvat pe platforma LambdaChecker,​ fiind găsit [[https://​beta.lambdachecker.io/​contest/​21 | aici]].
  
-=== Command === +  - **(6 puncte)** ​Implementați ​o structură ​de date de tipul ''​MultiMapValue<​K,​ V>'',​ pe baza scheletului,​ care reprezintă ​un ''​HashMap<​K,​ ArrayList<​V>>'',​ unde cheie este asociată cu mai multe valori. Modalitatea ​de stocare ​datelor este la alegere (moștenire sau agregare) ​și să folosiți funcționalitățile din HashMap. În schelet aveți următoarele metode de implementat:​ 
- +     ​- **(1 punct)** ''​add(K keyV value)'' ​adaugă o valoare la cheie dată (valoarea este adăugate în lista de valori asociate cheii, dacă cheia și lista nu existăatunci lista va fi creată și asociată cheii
-Implementați ​folosind patternul Command un editor ​de diagrame foarte simplificat. [[https://​github.com/​oop-pub/​oop-labs/​tree/​master/​src/​lab10 | Scheletul ​de cod]] conține o parte din clase și câteva teste.  +     - **(1 puncte)** ''​void addAll(K key, List<​V>​ values)''​ - adaugă valorile din lista de valori dată ca parametru la lista asociată cheii. 
- +     - **(1 puncte)** ''​void addAll(MultiMapValue<​K,​ V> map)''​ - adaugă intrările din obiectul MultiMapValue dat ca parametru în obiectul curent (this)
-Componentele principale ale programului:​ +     - **(0.5 puncte)** ''​V getFirst(K key)''​ - întoarce prima valoare asociată cheii (dacă nu există, se întoarce null)
-  * //​DiagramCanvas//​ - reprezintă o diagramă care conține obiecte ​de tip DiagramComponent +     - **(0.5 puncte)** ''​List<​V>​ getValues(K key)''​ - se întoarce lista de valori asociată cheii
-  * //​DrawCommand//​ - interfață pentru comenzile făcute asupra diagramei sau componentelor acesteia +     - **(0.5 puncte)** ''​boolean containsKey(K key)''​ - se verifică faptul dacă este prezentă cheia în MultiMapValue
-  * //Invoker// - primește comenzile ​și le execută +     - **(0.5 puncte)** ''​boolean isEmpty()'' ​se verifică dacă MultiMapValue este gol. 
-  * //​Client// ​entry-point-ul în program +     - **(0.5 puncte)** ''​List<​V>​ remove(K key)''​ - se șterge cheia, împreună ​cu valorile asociate ei, din MultiMapValue
- +     - **(0.5 puncte)** ''​int size()'' ​se întoarce mărimea MultiMapValue.
-** Task 1 - Implementarea comenzilor ​(2p) ** +
- +
-Implementați 5 tipuri de comenzipentru următoarele acțiuni: +
-  * Draw rectangle ​crează o DiagramComponent și adaugă în DiagramCanvas +
-  * Resize - modifică width și height al unei DiagramComponent pe baza unui procent ​dat +
-  * Change color - modifică culoarea unei DiagramComponent +
-  * Change text - modifică textul unei DiagramComponent +
-  * Connect components - conectează o DiagramComponent la alta +
- +
-Comenzile primesc ​în __constructor__ referința către DiagramCanvas ​și alte argumente necesare lor. De exemplucomanda pentru schimbarea culorii trebuie sa primească și culoarea nouă și indexul componentei+
- +
-Pentru acest task nu este nevoie să implementați și metoda //undo()//, doar //execute()//. +
- +
-Comenzile implementează în afară de metodele interfeței și metoda [[https://​docs.oracle.com/​en/​java/​javase/​13/​docs/​api/​java.base/​java/​lang/​Object.html#​toString()|toString()]] pentru a afișa comanda. Recomandăm folosirea IDE-ului pentru a o genera+
- +
-** Task 2 - Undo/​redo ​(3p) ** +
- +
-Implementați în comenzi și în Invoker mecanismul de undo/redo al comenzilor. Recomandăm în Invoker sa folosiți două structuri de date, una care să mențină comenzile efectuateiar una pentru comenzile făcute undo+
- +
-=== Builder === +
- +
-** Task 3 - (0.75p) ** +
- +
-Scrieți câmpuri în skeletul clasei House pentru câteva facilități obligatorii în construcția unei case, spre exemplu locația construcției,​ numărul ​de etaje, încălzire, camere dar și unele opționale pe care le poate selecta sau nu clientul, cum ar fi electrocasnice,​ piscină, panouri solare, securitate etc+
- +
-Completați constructorul privat, metodele de get și metoda toString. +
- +
-** Task 4 - (0.75p) ** +
- +
-În clasa de builder, completați câmpurile, constructorul și metodele de adăugare +
-a facilităților opționale+
- +
-** Task 5 - (0.5p) ** +
- +
-Finalizați metoda build și testați funcționalitatea într-o clasă Main creată de voi, +
-acoperind cazuri în care se construiește o casa doar cu facilitați obligatorii și +
-altele adăugând și pe cele opționale+
- +
-/* +
- +
-TODO pt 2020 (acum nu am avut timp)+
-- builder pattern in textul labului +
-- un exercitiu care sa ia exemplul de cod pus la partea de teorie si sa il ruleze si sa ii aplice doar o mica modificare +
- +
-*+
- +
-==== Resurse ==== +
-  ​[[:poo-ca-cd:​laboratoare:​old-exercises|Exerciții din alți ani]] +
- +
-==== Referințe ====+
  
-  * [[https://​sourcemaking.com/​design_patterns/​command | Command design pattern]] +  ​**(4 puncte)** Implementați o structură de date de tipul Tree<​T>​ (Arbore binar de căutare) pe baza scheletuluiAnalizațmodalitatea de utilizare a bounded wildcards, explicați necesitatea lor laborantului (fie în cadrul orei de laborator, fie la nivel de comentariu în cod). În schelet aveți următoarele metode de implementat:​ 
-  ​[[https://​stackoverflow.com/​questions/​32597736/​why-should-i-use-the-command-design-pattern-while-i-can-easily-call-required-met | De ce avem nevoie de Command Pattern? ]]+     **(1 puncte)** ''​void addValue(T value)'' ​adaugă o valoare în arborele binar de căutare. 
 +     **(0.5 puncte)** ''​void addAll(List<​T>​ values)'' ​adaugă valorile dintr-o listă în arborele binar de căutare. 
 +     **(1.5 puncte)** ''​HashSet<​T>​ getValues(T inf, T sup)'' ​colectează valorile din arbore între o limită inferioară șsuperioară într-o colecție de tipul HashSet. 
 +     **(0.5 puncte)** ''​int size()'' ​se întoarce numărul de elemente inserate în arbore. 
 +     **(0.5 puncte)** ''​boolean isEmpty()'' ​se întoarce dacă există vreun element inserat în arborele binar sau nu.
  
 +====Referinţe====
  
 +  * [[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​types.html | Generic Types]]
 +  * [[https://​docs.oracle.com/​javase/​tutorial/​extra/​generics/​wildcards.html | Wildcards]]
 +  * [[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​upperBounded.html | Upper Bounded Wildcards]]
 +  * [[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​lowerBounded.html | Lower Bounded Wildcards]]
 +  * [[https://​docs.oracle.com/​javase/​tutorial/​java/​generics/​erasure.html | Type Erasure]]
poo-ca-cd/laboratoare/design-patterns2.1670776278.txt.gz · Last modified: 2022/12/11 18:31 by bogdan.vatamanu
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