Differences

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

Link to this comparison view

poo-ca-cd:laboratoare:design-patterns [2020/10/05 14:42]
rares_stefan.epure [Factory]
poo-ca-cd:laboratoare:design-patterns [2023/12/08 21:34] (current)
aconstantinescu0606 [Exerciții]
Line 1: Line 1:
-===== Design patterns - Factory, Strategy, Observer ===== +===== Laboratorul 9: Design patterns - Factory, Strategy, Observer, Command, Builder ​=====
  
 +**Video introductiv:​** [[https://​www.youtube.com/​watch?​v=khRqWZrz1YU | link]] + [[https://​www.youtube.com/​watch?​v=F2HMN4mvVYY | link]]
  
  
 ==== Obiective ==== ==== Obiective ====
  
-Scopul acestui laborator este familiarizarea cu folosirea unor pattern-uri des întâlnite în design-ul atât al aplicațiilor,​ cât și al API-urilor - //​Factory//,​ //​Strategy//​ și //Observer//.+Scopul acestui laborator este familiarizarea cu folosirea unor pattern-uri des întâlnite în design-ul atât al aplicațiilor,​ cât și al API-urilor - //​Factory//,​ //Strategy//, //​Observer//,​ //Command// și //Builder//.
  
  
Line 42: Line 42:
 Patternurile de tip Factory sunt folosite pentru obiecte care generează instanțe de clase înrudite (implementează aceeași interfață,​ moștenesc aceeași clasă abstractă). Acestea sunt utilizate atunci când dorim să izolăm obiectul care are nevoie ​ de o instanță de un anumit tip, de creearea efectivă acesteia. În plus clasa care va folosi instanța nici nu are nevoie să specifice exact subclasa obiectului ce urmează a fi creat, deci nu trebuie să cunoască toate implementările acelui tip, ci doar ce caracteristici trebuie să aibă obiectul creat. Din acest motiv, Factory face parte din categoria //​Creational Patterns//, deoarece oferă o soluție legată de creearea obiectelor. Patternurile de tip Factory sunt folosite pentru obiecte care generează instanțe de clase înrudite (implementează aceeași interfață,​ moștenesc aceeași clasă abstractă). Acestea sunt utilizate atunci când dorim să izolăm obiectul care are nevoie ​ de o instanță de un anumit tip, de creearea efectivă acesteia. În plus clasa care va folosi instanța nici nu are nevoie să specifice exact subclasa obiectului ce urmează a fi creat, deci nu trebuie să cunoască toate implementările acelui tip, ci doar ce caracteristici trebuie să aibă obiectul creat. Din acest motiv, Factory face parte din categoria //​Creational Patterns//, deoarece oferă o soluție legată de creearea obiectelor.
  
-Aplicabilitate:​ 
-* în biblioteci/​API-uri,​ utilizatorul este separat de implementarea efectivă a tipului și trebuie sa folosească metode factory pentru a obține anumite obiecte. Clase care oferă o astfel de funcționalitate puteți găsi și in core api-ul de Java, in api-ul java.nio (e.g. clasa [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​nio/​file/​FileSystems.html|FileSystems]]),​ în Android SDK (e.g. [[http://​developer.android.com/​reference/​javax/​net/​SocketFactory.html|clasa SocketFactory]]) etc. 
-* atunci când crearea obiectelor este mai complexă (trebuie realizate mai multe etape etc.), este mai util să separăm logica necesară instanțierii subtipului de clasa care are nevoie de acea instanță. ​ 
  
 +
 +<note tip>
 +**Aplicabilitate:​**
 +
 +    * În biblioteci/​API-uri,​ utilizatorul este separat de implementarea efectivă a tipului și trebuie sa folosească metode factory pentru a obține anumite obiecte. Clase care oferă o astfel de funcționalitate puteți găsi și in core api-ul de Java, in api-ul java.nio (e.g. clasa [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​nio/​file/​FileSystems.html|FileSystems]]),​ în Android SDK (e.g. [[http://​developer.android.com/​reference/​javax/​net/​SocketFactory.html|clasa SocketFactory]]) etc.
 +    * **Atunci când crearea obiectelor este mai complexă** (trebuie realizate mai multe etape etc.), este mai util să separăm logica necesară instanțierii subtipului de clasa care are nevoie de acea instanță. :!: Asta înseamnă că puteți folosi metode factory care să vă construiască obiectul și dacă aveți doar un tip, nu mai multe.
 +
 +</​note>​
  
 === Abstract Factory Pattern === === Abstract Factory Pattern ===
  
-/* <​imgcaption af_image|Diagrama de clase pentru Abstract Factory>​*/​ {{ .:​design-patterns:​abstractfactory.png | figure 1}} /​*</​imgcaption>​ */+/* <​imgcaption af_image|Diagrama de clase pentru Abstract Factory>​*/​ {{ .:​design-patterns:​abstractfactory.png | Diagrama de clase pentru Abstract Factory}} /​*</​imgcaption>​ */
  
 Codul următor corespunde diagramei din /* <imgref af_image>​ */{{ .:​design-patterns:​abstractfactory.png?​linkonly | figure 1}} . În acest caz folosim interfețe pentru factory și pentru tip, însă în alte situații putem să avem direct //​SpecializedFooFactory,//​ fără a implementa interfața //​FooFactory//​. ​ Codul următor corespunde diagramei din /* <imgref af_image>​ */{{ .:​design-patterns:​abstractfactory.png?​linkonly | figure 1}} . În acest caz folosim interfețe pentru factory și pentru tip, însă în alte situații putem să avem direct //​SpecializedFooFactory,//​ fără a implementa interfața //​FooFactory//​. ​
Line 78: Line 83:
 Spre deosebire de Abstract Factory, Factory Method ascunde construcția unui obiect, nu a unei familii de obiecte "​inrudite",​ care extind un anumit tip. Clasele care implementează Abstract Factory conțin de obicei mai multe metode factory. Spre deosebire de Abstract Factory, Factory Method ascunde construcția unui obiect, nu a unei familii de obiecte "​inrudite",​ care extind un anumit tip. Clasele care implementează Abstract Factory conțin de obicei mai multe metode factory.
  
-/* <​imgcaption fm_image|Diagrama de clase pentru Factory Method> */ {{ .:​design-patterns:​factorymethod.png?​510 |}}+/* <​imgcaption fm_image|Diagrama de clase pentru Factory Method> */ {{ .:​design-patterns:​factorymethod.png?​510 | Diagrama de clase pentru Factory Method}}
 /* </​imgcaption>​ */ /* </​imgcaption>​ */
  
Line 145: Line 150:
   ​   ​
 Acest pattern este de tip //​Behavioral//​ (comportamental),​ deorece facilitează o organizare mai bună a comunicației dintre clase în funcție de rolurile/​comportamentul acestora. ​ Acest pattern este de tip //​Behavioral//​ (comportamental),​ deorece facilitează o organizare mai bună a comunicației dintre clase în funcție de rolurile/​comportamentul acestora. ​
 +
 +<note tip>
 +**Aplicabilitate**
  
 Observer se folosește în cazul în care mai multe clase(//​observatori//​) depind de comportamentul unei alte clase(//​subiect//​),​ în situații de tipul: ​   Observer se folosește în cazul în care mai multe clase(//​observatori//​) depind de comportamentul unei alte clase(//​subiect//​),​ în situații de tipul: ​  
Line 150: Line 158:
   * o clasă efectuează acțiuni care apoi pot fi reprezentate în mai multe feluri de către alte clase (view-uri ca în figură de mai jos).    * o clasă efectuează acțiuni care apoi pot fi reprezentate în mai multe feluri de către alte clase (view-uri ca în figură de mai jos). 
   ​   ​
-Practic în toate aceste situații clasele Observer **observă** modificările/​acțiunile clasei Subject. Observarea se implementează prin **notificări inițiate din metodele clasei Subject**. ​+Practic în toate aceste situații clasele Observer **observă** modificările/​acțiunile clasei Subject. Observarea se implementează prin **notificări inițiate din metodele clasei Subject**.</​note> ​
  
 === Structură === === Structură ===
  
-Pentru aplicarea acestui pattern, clasele aplicației trebuie să fie structurate după anumite roluri, și în funcție de acestea se stabilește comunicarea dintre ele. În exemplul din <imgref obs_image>,​ avem două tipuri de componente, //Subiect// și //​Observator//,​ iar //​Observator//​ poate fi o interfață sau o clasă abstractă ce este extinsă cu diverse implementări,​ pentru fiecare tip de monitorizare asupra obiectelor //​Subiect//​.+Pentru aplicarea acestui pattern, clasele aplicației trebuie să fie structurate după anumite roluri, și în funcție de acestea se stabilește comunicarea dintre ele. În exemplul din /* <imgref obs_image> ​*/ {{ .:​design-patterns:​observer.png?​linkonly ​ | figure 3}} , avem două tipuri de componente, //Subiect// și //​Observator//,​ iar //​Observator//​ poate fi o interfață sau o clasă abstractă ce este extinsă cu diverse implementări,​ pentru fiecare tip de monitorizare asupra obiectelor //​Subiect//​.
     * observatorii folosesc datele subiectului     * observatorii folosesc datele subiectului
     * observatorii sunt notificați automat de schimbări ale subiectului     * observatorii sunt notificați automat de schimbări ale subiectului
Line 207: Line 215:
 Denumirile uzuale în exemplele acestui pattern sunt: //​Strategy//​ (pt interfață sau clasa abstractă),​ //​ConcreteStrategy//​ pentru implementare,​ //​Context//,​ clasa care folosește/​execută strategiile. Denumirile uzuale în exemplele acestui pattern sunt: //​Strategy//​ (pt interfață sau clasa abstractă),​ //​ConcreteStrategy//​ pentru implementare,​ //​Context//,​ clasa care folosește/​execută strategiile.
  
-//​__Recomandare__//: ​Urmariti ​link-ul de la referinte catre postul de pe Stack Overflow care descrie necesitatea pattern-ului Strategy. Pe langa motivul evident de incapsulare ​prelucrarilor/​algoritmilor (care reprezinta ​strategiile efective), se prefera ​anumita ​abordare: la runtime se verifica ​mai multe conditii si se decide asupra strategiei. Concret, folosind mecanismul de polimorfism dinamic, se foloseste ​anumita instanta ​a tipului de strategie (//ex. Strategy str = new CustomStrategy//), care se paseaza in toate locurile unde este nevoie de Strategy. Practic, ​in acest fel, utilizatorii unei anumite strategii vor deveni agnostici ​in raport cu strategia ​utilizata, ea fiind instantiata intr-un loc anterior ​si putand ​fi gata utilizataGanditi-va la browserele care trebuie ​sa detecteze ​daca device-ul este PC, smartphone, ​tableta ​sau altceva ​si in functie ​de acest lucru sa randeze ​in mod diferit. Fiecare randare poate fi implementata ​ca o strategie, iar instantierea ​strategiei se va face intr-un punct, fiind mai apoi pasata in toate locurile unde ar trebui ​sa se tina cont de aceasta ​strategie.+//​__Recomandare__//: ​ ​Urmăriți ​link-ul de la referințe către ​postul de pe Stack Overflow care descrie necesitatea pattern-ului Strategy. Pe lângă ​motivul evident de încapsulare ​prelucrărilor/​algoritmilor (care reprezintă ​strategiile efective), se preferă ​anumită ​abordare: la runtime se verifică ​mai multe condiții și se decide asupra strategiei. Concret, folosind mecanismul de polimorfism dinamic, se folosește ​anumită instanță ​a tipului de strategie (ex. Strategy str = new CustomStrategy),​ care se pasează în toate locurile unde este nevoie de Strategy. Practic, ​în acest fel, utilizatorii unei anumite strategii vor deveni agnostici ​în raport cu strategia ​utilizată, ea fiind instanțiată într-un loc anterior ​și putând ​fi gata utilizatăGândiți-vă la browserele care trebuie ​să detecteze ​dacă device-ul este PC, smartphone, ​tabletă ​sau altceva ​și în funcție ​de acest lucru să randeze ​în mod diferit. Fiecare randare poate fi implementată ​ca o strategie, iar instanțierea ​strategiei se va face într-un punct, fiind mai apoi pasată în toate locurile unde ar trebui ​să se țină ​cont de această ​strategie.  
 + 
 +==== Command ==== 
 + 
 +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 componente, mai exact felul în care se efectuează apelurile. ​  
 +   
 +Acest pattern este recomandat în următoarele cazuri: ​  
 +  * pentru a 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 cunoscute) presupune încapsularea unei informații referitoare la acțiuni/​comenzi folosind un wrapper pentru a "ține minte această informație"​ și pentru a o folosi ulterior. Astfel, 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ă).  
 +   
 +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).  
 +   
 +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ă.  
 + 
 +//​__Recomandare__//:​ La Referinte aveti un link catre un post pe StackOverflow,​ pentru a intelege mai bine de ce aveti nevoie de Pattern-ul Command si de ce nu lansati comenzi pur si simplu. 
 + 
 +=== Structura === 
 + 
 +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//​).  
 + 
 +{{ .:​design-patterns:​command.png |Diagrama de stări pentru Command Pattern}} 
 +   
 + ​Tipuri de componente (**roluri**):​  
 +  * **Invoker** - comandantul  
 +    * 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 (obiectele) comandate. 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). 
 + 
 +În prima diagramă de mai jos, comandantul este clasa //Invoker// care conține o referință la o instanță (command) a 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. 
 +   
 +=== Exemplu === 
 + 
 +Prima diagramă de secvență prezintă apelurile în cadrul unei aplicație de editare a 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.  
 + 
 +{{ .design-patterns:​imageeditor_example.png | Diagrama de secvență pentru comenzile de prelucrare a imaginilor}} 
 + 
 +Pornind de la această diagramă, putem realiza o implementare a pattern-ului Command.  
 +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> 
 +public class Image { 
 +   ​private int blurStrength;​ 
 +   ​private int length; 
 +   ​private int width; 
 + 
 +   ​public Image(int length, int width) { 
 +       ​this.length = length; 
 +       ​this.width = width; 
 +   } 
 + 
 +   ​public int getBlurStrength() { 
 +       ​return blurStrength;​ 
 +   } 
 + 
 +   ​public void setBlurStrength(int blurStrength) { 
 +       ​this.blurStrength = blurStrength;​ 
 +   } 
 + 
 +   ​public int getLength() { 
 +       ​return length; 
 +   } 
 + 
 +   ​public void setLength(int length) { 
 +       ​this.length = length; 
 +   } 
 + 
 +   ​public int getWidth() { 
 +       ​return width; 
 +   } 
 + 
 +   ​public void setWidth(int width) { 
 +       ​this.width = width; 
 +   } 
 +
 +</​code>​ 
 + 
 +//Command// va fi o interfață,​ căreia pe langă metoda de //​execute()//​ îi vom asocia și o metodă de //undo()//.  
 +<code java> 
 +interface Command { 
 +   void execute();​ 
 + 
 +   void undo(); 
 +
 +</​code>​ 
 + 
 +//​BlurCommand//​ și //​ResizeCommand//​ vor implementa interfața //​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ă. 
 + 
 +<code java> 
 +// Concrete command 
 + 
 +public class BlurCommand implements Command { 
 +   ​private final Image image; 
 +   ​private int previousBlurStrength;​ 
 +   ​private int nextBlurStrength;​ 
 + 
 +   ​public BlurCommand(Image image, int blurStrength) { 
 +       ​this.image = image; 
 +       ​this.nextBlurStrength = blurStrength;​ 
 +   } 
 + 
 +   ​@Override 
 +   ​public void execute() { 
 +       ​previousBlurStrength = image.getBlurStrength();​ 
 +       ​image.setBlurStrength(nextBlurStrength);​ 
 +   } 
 + 
 +   ​@Override 
 +   ​public void undo() { 
 +       ​nextBlurStrength = previousBlurStrength;​ 
 +       ​previousBlurStrength = image.getBlurStrength();​ 
 +       ​image.setBlurStrength(nextBlurStrength);​ 
 +   } 
 +
 + 
 +public class ResizeCommand implements Command { 
 +   ​private final Image image; 
 +   ​private int previousWidth;​ 
 +   ​private int previousLength;​ 
 +   ​private int nextWidth;​ 
 +   ​private int nextLength;​ 
 + 
 +   ​public ResizeCommand(Image image, int width, int length) { 
 +       ​this.image = image; 
 +       ​nextWidth = width; 
 +       ​nextLength = length; 
 +   } 
 + 
 +   ​@Override 
 +   ​public void execute() { 
 +       ​previousWidth = image.getWidth();​ 
 +       ​image.setWidth(nextWidth);​ 
 + 
 +       ​previousLength = image.getLength();​ 
 +       ​image.setLength(nextLength);​ 
 +   } 
 + 
 +   ​@Override 
 +   ​public void undo() { 
 +       ​nextWidth = previousWidth;​ 
 +       ​previousWidth = image.getWidth();​ 
 +       ​image.setWidth(nextWidth);​ 
 + 
 +       ​nextLength = previousLength;​ 
 +       ​previousLength = image.getLength();​ 
 +       ​image.setLength(nextLength);​ 
 +   } 
 +
 +</​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> 
 +// Invoker 
 + 
 +public class Editor { 
 +   // LinkedList este folosit ca stivă în Java 
 +   ​private LinkedList<​Command>​ history = new LinkedList<>​();​ // păstrează comenzile aplicate pe imagine 
 + 
 +   ​public void edit(Command command) { 
 +       ​history.push(command);​ 
 +       ​command.execute();​ 
 +   } 
 + 
 +   ​public void undo() { 
 +       if (history.isEmpty()) return; 
 + 
 +       ​Command command = history.pop();​ 
 +       if (command != null) { 
 +           ​command.undo();​ 
 +       } 
 +   } 
 +
 + 
 +</​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 ==== 
 + 
 +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. 
 + 
 +=== Problema === 
 + 
 +Î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 
 + 
 +<code java Pizza.java>​ 
 +public class Pizza { 
 +    private String pizzaSize;​ 
 +    private int cheeseCount;​ 
 +    private int pepperoniCount;​ 
 +    private int hamCount; 
 +     
 +    // constructor,​ getters, setters 
 +
 +</​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. 
 + 
 +== Supraîncărcarea constructorilor == 
 + 
 +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. 
 + 
 +<code java Pizza.java>​ 
 +public class Pizza { 
 +    private String pizzaSize; // mandatory 
 +    private int cheeseCount;​ // optional 
 +    private int pepperoniCount;​ // optional 
 +    private int hamCount; // optional 
 +     
 +    public Pizza(String pizzaSize) { 
 +        this(pizzaSize,​ 0, 0, 0); 
 +    } 
 +     
 +    public Pizza(String pizzaSize, int cheeseCount) { 
 +        this(pizzaSize,​ cheeseCount,​ 0, 0); 
 +    } 
 +     
 +    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>​ 
 + 
 +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",​ 1, 1)//, deoarece al treilea argument al constructorului este pepperoni. Și aici vine a doua soluție comună - și mai multă supraîncărcare de constructori.  
 + 
 +<code java Pizza.java>​ 
 +public class Pizza { 
 +    private String pizzaSize; // mandatory 
 +    private String crust; // mandatory 
 +    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>​ 
 + 
 +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. 
 +Modelul are însă dezavantaje grave. Construcția clasei este împărțită în apeluri multiple, prin urmare instanța poate fi într-o stare parțial construită / invalidă. 
 + 
 +== Folosirea builder pattern == 
 + 
 +<code java Pizza.java>​ 
 +public class Pizza { 
 +    private String pizzaSize;​ 
 +    private String crust; 
 +    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 pizzaSize, String 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>​ 
 + 
 +Am făcut constructorul privat, astfel încât clasa noastră să nu poată fi instanțiată direct. În același timp am adăugat o clasă static Builder cu un constructor care are parametrii noștri obligatori pizzaSize și crust, metode de setare a parametrilor opționali și, în final, o metodă //build()// metoda care va returna o nouă instanță a clasei Pizza. Metodele setter returnează instanța de builder în sine, oferindu-ne astfel o interfață fluentă cu metoda de înlănțuire. 
 + 
 +<code java Pizza.java>​ 
 +Pizza pizza = new Pizza.Builder("​large",​ "​thin"​) 
 +    .cheeseCount(1) 
 +    .pepperoniCount(1) 
 +    .build(); 
 +</​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 a crea o instanță a clasei. 
 + 
 +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 ansamblu, modelul 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ă.
  
 ==== Summary ==== ==== Summary ====
  
-Principii de design adresate de aceste patternuri:+Principii de design adresate de aceste patternuri:
   * [[https://​stackoverflow.com/​questions/​62539/​what-is-the-dependency-inversion-principle-and-why-is-it-important|Dependency Injection Principle]] - componentele trebuie să depindă de tipuri abstracte, nu de implementări   * [[https://​stackoverflow.com/​questions/​62539/​what-is-the-dependency-inversion-principle-and-why-is-it-important|Dependency Injection Principle]] - componentele trebuie să depindă de tipuri abstracte, nu de implementări
     * Factory respectă acest principiu, componentele depinzând de interfața pentru un tip, nu de un subtip anume     * Factory respectă acest principiu, componentele depinzând de interfața pentru un tip, nu de un subtip anume
Line 220: Line 617:
  
 ==== Exerciții ==== ==== Exerciții ====
 +Laboratorul trebuie rezolvat pe platforma LambdaChecker,​ fiind găsit [[https://​beta.lambdachecker.io/​contest/​20 | aici]].
 +
 +<note important>​
 +În cadrul acestui laborator, exercițiile valorează în total 18p. Pentru primirea punctajului maxim pe acest laborator, trebuie să acumulați 10p din rezolvarea exercițiilor,​ orice depășește 10p fiind contorizat ca și bonus.
 +</​note>​
 +
 +
 +** [[https://​beta.lambdachecker.io/​problem/​43/​20| Task 1]] - Observer, Strategy, Factory (8p) **
 +
 +**Part 1. (4p) - Strategy, Factory**
 +
 +În cadrul acestui exercițiu, dorim să implementăm un magazin, ce are disponibile mai multe modalități de plată pentru clienții săi (folosind un voucher, IBAN-ul sau un card pe care îl posedă).
 +
 +Cele 3 modalități de plată sunt reprezentate de clase ce implementează interfața PaymentStrategy. ​
 +
 +Trebuie să implementați:​
 +
 +      * Metoda pay din cadrul fiecărei metode de plată.
 +      * Metoda getPaymentMethod,​ ce primește un client ca parametru și instanțiază o tranzacție folosind o anumită metodă de plată, aleasă random. ​
 + <​note tip> **HINT:** getPaymentMethod va fi folosită în cadrul unei metode ce trebuie implementată în cadrul părții de Observer </​note>​
 +
 +  ​
 +**Part 2. (4p) - Observer**
 +
 +În acest context, subiectul (cel care este Observable) este clasa Person. Această clasă implementează interfața BalanceObserver (care este Observer-ul). Clasa Shop (ce joacă rol de Publisher, întrucât implementează interfața TransactionPublisher) va avea rolul de a notifica un client când o tranzacție este efectuată.
 +
 +Trebuie să implementați:​
 +      *Motoda update din cadrul clasei Person, ce notifică faptul că un client are în cont  mai puțini Lei decât o anumită limită, specifică fiecărui client în parte, printr-un mesaj adecvat ("<​nume>​ <​prenume>,​ ramai fara bani, saracule!"​).
 +      *Metoda payBy din cadrul clasei Person, ce întoarce valoarea de adevăr a reușitei efectuării unei plăți (dacă e posibil efectuează plata), iar în caz negativ afisează și un mesaj adecvat ("<​nume>​ <​prenume>,​ pleaca de aici, saracule!"​).
 +      *Metoda createTransaction din cadrul clasei Shop, ce verifică dacă trebuie notificată persoana cu ajutorul metodei update, în urma unei tranzacții.
 +<note tip> **HINT:** trebuie folosită și metoda payBy </​note>​
 +
 +
  
-În cadrul acestui laborator veți implementa o aplicație ​//mock// care primește date și le procesează.+** [[https://beta.lambdachecker.io/problem/44/20 | Task 2]] - Builder pattern (2p)**
  
-Arhitectura ei va include patternurile Observer, Strategy șFactory și este descrisă în README-ul din +a) Scrieț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.
-arhiva dată cu scheletul ​de cod.+
  
 +Completați constructorul privat, metodele de get și metoda toString.
  
-** Task 1 - Observer pattern (3p) ** 
  
-Scheletul de cod vă oferă clasele //MainApp// (entry-point pentru testare), //​Utils// ​și //​DataRepository//​.+bÎn clasa de buildercompletați câmpurile, constructorul ​și metodele de adăugare 
 +a facilităților opționale.
  
-''​DataRepository''​ este obiectul observabil, care va primi date noi de la MainApp. Când acesta primește date noi va notifica observatorii săi: ''​ConsoleLogger'',​ ''​ServerCommunicationController''​ și ''​DataAggregator''​. +c) Finalizați metoda build și testați funcționalitatea într-o clasă Main creată de voi, 
-  * Pentru a avea deja mecanismul de notificare vom folosi interfața [[https://​docs.oracle.com/​en/​java/​javase/​11/​docs/​api/​java.base/​java/​util/​Observer.html|Observer]] și clasa [[https://​docs.oracle.com/​en/​java/​javase/​11/​docs/​api/​java.base/​java/​util/​Observable.html|Observable]] din java.util. Dacă doriți și aveți timp puteți să vă implementați propriile interfețe Oberver-Observable. +acoperind cazuri ​în care se construiește o casa doar cu facilitați obligatorii ​și 
-   * Observer-Observable din java.util sunt deprecated din java 9 pentru că sunt prea simple, însă asta le face potrivite pentru acest laborator. Într-o aplicație reală puteți folosi alte api-uri care sunt mult mai complexe și oferă foarte multe tipuri ​de obiecte și mecanisme (termenul folosit este //reactive programming//​). +altele ​adăugând și pe cele opționale.
-  * Citiți ​în [[https://​github.com/​oop-pub/​laboratoare/​blob/​master/​design-patterns/​skel/​src/​README|README]] rolul fiecărui observator. +
-  <note tip>Vedeți metodele din [[https://​docs.oracle.com/​en/​java/​javase/​11/​docs/​api/​java.base/​java/​util/​Observable.html|Observable]] pentru notificarea observatorilor,​ schimbarea stării obiectului observat ​și adăugarea de observatori.</​note>​+
  
 +** [[https://​beta.lambdachecker.io/​problem/​41/​20 | Task 3]] - Command pattern (8p)**
  
-** Task 2 Strategy pattern (3p)**+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. ​
  
-Scheletul vă oferă interfața ''​StepCountStrategy''​ ce va fi implementată de către "​algoritmii"​ de prelucrare ​datelor: ''​BasicStepCountStrategy''​ și ''​FilteredStepCountStrategy''​. Prima adună toate valorile primite, iar a doua le adună doar pe cele ce îndeplinesc niște condiții (să fie număr pozitiv ​și să nu fie o valoare prea mare (mai mult de 1000 de pași) venită prea curând de la ultimul update primit (în mai puțin de 1 minut). +Componentele principale ale programului:​ 
-   ​strategiile vor folosi datele stocate ​în DataRepository +  * //​DiagramCanvas//​ - reprezintă o diagramă care conține obiecte de tip DiagramComponent 
-   * pentru strategia Filtered puteți folosi următoarele constante: ''​ private static final int MAX_DIFF_STEPS_CONSECUTIVE_RECORDS = 1000;''​ și ''​private static final long TIME_FOR_MAX_DIFF = TimeUnit.SECONDS.toMillis(1);''​+  * //​DrawCommand//​ - interfață ​pentru comenzile făcute asupra diagramei sau componentelor acesteia 
 +  * //Invoker// - primește comenzile ​și le execută 
 +  //Client// - entry-point-ul ​în program
  
 +(4p) Implementați 5 tipuri de comenzi, pentru următoarele acțiuni:
 +  * Draw rectangle - crează o DiagramComponent și o 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
  
-** Task 3 - Factory pattern ​(2p)**+Implementați pe Invoker metoda execute() care va executa comanda primită ca argument.
  
-Creați clasa ''​StepCountStrategyFactory''​ care construiește instanțe de subclase ale StepCountStrategy.+Comenzile primesc în __constructor__ referința către DiagramCanvas șalte argumente necesare lor. De exemplu, comanda pentru schimbarea culorii trebuie sa primească ​și culoarea nouă și indexul componentei.
  
-Clasa factory va conține o metodă care să întoarcă o strategie. De exemplu: +Pentru acest task nu este nevoie ​să implementați și metoda //undo()//doar //execute()//.
-''​public StepCountStrategy createStrategy(String strategyTypeDataRepository dataRepository)''​. StrategyType poate fi un string care să indice tipul strategiei, vedeți în ''​Utils''​ stringurile definite deja.+
  
-** Task 4 - Putting all together ​(2p)**+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.
  
-Realizați TODO-urile din codul de test din MainApp ​și rulațiPuteți să vă adăugați propriile exemple ​de date pentru a verfica corectitudinea programului.+(6p) Implementați în comenzi ​și în Invoker mecanismul de undo/redo al comenzilorRecomandăm în Invoker sa folosiți două structuri de date, una care să mențină comenzile efectuate, iar una pentru comenzile făcute undo. Metoda reset() ​de pe Invoker va avea ca scop resetarea tuturor membrilor acestuia.
  
  
Line 261: Line 697:
 ==== Resurse ==== ==== Resurse ====
  
-  * {{:​poo-ca-cd:​laboratoare:​design-patterns:​design-patterns-part1-skel.zip |Schelet}} /* 
-   ​{{:​poo-ca-cd:​laboratoare:​design-patterns:​lab-design-patterns1-sol.zip | Soluție}} */ 
   * [[:​poo-ca-cd:​laboratoare:​old-exercises|Exerciții din alți ani]]   * [[:​poo-ca-cd:​laboratoare:​old-exercises|Exerciții din alți ani]]
  
Line 272: Line 706:
   * [[http://​sourcemaking.com/​design_patterns/​observer | Explicații pattern Observer]].   * [[http://​sourcemaking.com/​design_patterns/​observer | Explicații pattern Observer]].
   * [[https://​stackoverflow.com/​questions/​1710809/​when-and-why-should-the-strategy-pattern-be-used | De ce avem nevoie de Strategy Pattern? ]]   * [[https://​stackoverflow.com/​questions/​1710809/​when-and-why-should-the-strategy-pattern-be-used | De ce avem nevoie de Strategy Pattern? ]]
 +  * [[https://​sourcemaking.com/​design_patterns/​command | Command design pattern]]
 +  * [[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? ]]
  
 /* exercitii 2012 /* exercitii 2012
poo-ca-cd/laboratoare/design-patterns.1601898162.txt.gz · Last modified: 2020/10/05 14:42 by rares_stefan.epure
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