This shows you the differences between two versions of the page.
|
poo:laboratoare:11 [2025/12/14 16:43] carmen.odubasteanu [Laboratorul – Design Patterns 1] |
poo:laboratoare:11 [2025/12/14 17:54] (current) george.tudor1906 |
||
|---|---|---|---|
| Line 1: | Line 1: | ||
| - | ===== Laboratorul – Design Patterns 1 ===== | + | ===== Laboratorul 11 – Design Patterns (1) ===== |
| - | <hidden>{{:poo:laboratoare:arhiva_dp1.zip|Arhiva laborator}}</hidden> | + | <hidden> |
| - | + | {{:poo:laboratoare:rezolvare_dp_catalog.zip|}} | |
| - | <note important> | + | </hidden> |
| - | + | ||
| - | În cadrul acestui laborator se va lucra pe baza scheletului pus la dispoziție. | + | |
| - | + | ||
| - | Fiecare TODO este numerotat conform problemei ce trebuie rezolvată. | + | |
| - | + | ||
| - | În urma realizării cerințelor, se va obține o platformă de streaming muzical. | + | |
| - | </note> | + | |
| === Problema 1 - Singleton === | === Problema 1 - Singleton === | ||
| - | Se cere implementarea Design Pattern-ului **Singleton** în cadrul clasei **//Spotify//**. | + | Implementați o clasă **Catalog** care conține o listă cu obiecte de tip **Course**. |
| - | Clasa **//Spotify//** reprezintă componenta principală a aplicației, gestionând datele | + | Va trebui să vă asigurați că pentru această clasă va putea exista o singură instanță |
| - | platformei (utilizatori și melodii). | + | care să poată fi accesată din orice clasă a proiectului. Implementați metoda toString |
| + | pentru clasa Catalog. | ||
| - | Pattern-ul **Singleton** asigură existența unei singure instanțe a clasei pe | + | <code java> |
| - | parcursul execuției programului. | + | public class Catalog { |
| + | // TODO -- Adaugati aici implementarea exercitiului | ||
| + | } | ||
| - | Cerințe de implementare: | + | public class Course { |
| - | + | } | |
| - | * Declarați constructorul clasei ca **PRIVATE**; | + | </code> |
| - | * Adăugați un atribut **STATIC PRIVATE** de tip **Spotify** pentru instanța unică; | + | |
| - | * Implementați o metodă **PUBLICĂ STATICĂ** //getInstance()// care: | + | |
| - | * creează instanța dacă nu există; | + | |
| - | * returnează instanța existentă; | + | |
| - | * Instanțiați listele (**users**, **songs**, **observers**) în constructorul privat. | + | |
| - | + | ||
| - | <note tip> | + | |
| - | Singleton-ul va fi punctul de acces global la datele platformei. | + | |
| - | </note> | + | |
| === Problema 2 - Factory === | === Problema 2 - Factory === | ||
| - | Pornind de la clasa abstractă **//User//**, se va implementa Design Pattern-ul **Factory** | + | Pornind de la clasa abstractă **User**, definiți clasele **Student**, **Parent**, |
| - | prin intermediul claselor **//Regular//**, **//Premium//** și **//UltraPremium//** care moștenesc **//User//**. | + | **Assistant** și **Teacher** care vor moșteni clasa **User**: |
| - | Factory pattern centralizează logica de creare a obiectelor, ascunzând detaliile | + | <code java> |
| - | de implementare și permițând extinderea ușoară cu noi tipuri. | + | public abstract class User { |
| + | protected String firstName, lastName; | ||
| - | Valorile pentru câmpul **subscription**: | + | public User(String firstName, String lastName) { |
| + | this.firstName = firstName; | ||
| + | this.lastName = lastName; | ||
| + | } | ||
| - | * **Regular**: 9.99 | + | public String toString() { |
| - | * **Premium**: 14.99 | + | return firstName + " " + lastName; |
| - | * **UltraPremium**: 19.99 | + | } |
| - | + | } | |
| - | Cerințe de implementare: | + | </code> |
| - | + | ||
| - | * Clasele **Regular**, **Premium**, **UltraPremium** extind **User**; | + | |
| - | * Fiecare clasă își setează **subscription** în constructorul propriu; | + | |
| - | * **UserFactory** conține metoda statică //createUser()// care primește un **UserType** și returnează instanța corespunzătoare; | + | |
| - | * În **JSONReader**, folosiți factory-ul pentru a crea utilizatorii. | + | |
| + | Pentru a putea realiza o instanțiere ușoară a obiectelor pentru aceste tipuri de clase, | ||
| + | veți implementa o clasă **UserFactory** care va avea o metodă statică **createUser** ce | ||
| + | va returna un obiect de tip User (se va folosi șablonul de proiectare Factory). | ||
| === Problema 3 - Builder === | === Problema 3 - Builder === | ||
| - | Se cere implementarea Design Pattern-ului **Builder** în cadrul clasei **//Song//**. | + | Pe baza claselor definite anterior, veți completa implementarea clasei **Course**. |
| + | În cadrul aplicației noastre, un obiect de tipul Course o să conțină: | ||
| + | * un nume (de tipul String) | ||
| + | * un profesor titular | ||
| + | * o listă de asistenți | ||
| + | * o colecție ordonată cu obiecte de tipul Grade | ||
| + | * o listă de studenți | ||
| - | Un cântec conține: **title**, **artists**, **duration**, **views**, **releaseDate**, **ratings**. | + | Pentru a putea seta câmpurile unui obiect de tip Course, veți folosi șablonul de |
| + | proiectare **Builder**. Definiți toString pentru clasa Course. | ||
| - | Builder pattern permite construirea obiectelor complexe pas cu pas, oferind | + | <code java> |
| - | o alternativă la constructorii cu mulți parametri. | + | public class Grade { |
| + | private Double partialScore, examScore; | ||
| + | private Student student; | ||
| + | private String course; | ||
| - | Cerințe de implementare: | + | public Grade(String course, Student student) { |
| + | partialScore = 0.0; | ||
| + | examScore = 0.0; | ||
| + | this.course = course; | ||
| + | this.student = student; | ||
| + | } | ||
| - | * Definiți o clasă internă statică **Builder** în **Song**; | + | public Grade(String course, Student student, Double partialScore, Double examScore) { |
| - | * Builder-ul conține câmpuri identice cu cele din **Song**; | + | this.partialScore = partialScore; |
| - | * Fiecare metodă setter din Builder returnează //this// (pentru *method chaining*); | + | this.examScore = examScore; |
| - | * Metoda //build()// creează și returnează obiectul **Song**; | + | this.course = course; |
| - | * Constructorul **Song** este **PRIVAT** și primește un **Builder** ca parametru; | + | this.student = student; |
| - | * În **JSONReader**, folosiți Builder-ul pentru a crea melodiile. | + | } |
| - | Exemplu de utilizare Builder (în **JSONReader**): | + | public void setPartialScore(Double score) { |
| + | partialScore = score; | ||
| + | } | ||
| - | <code java> | + | public void setExamScore(Double score) { |
| - | Song song = new Song.Builder() | + | examScore = score; |
| - | .title(title) | + | } |
| - | .artists(artists) | + | |
| - | .duration(duration) | + | |
| - | .views(views) | + | |
| - | .releaseDate(releaseDate) | + | |
| - | .ratings(ratings) | + | |
| - | .build(); | + | |
| - | </code> | + | |
| - | Exemplu de metodă setter în Builder: | + | public Double getTotal() { |
| - | + | return partialScore + examScore; | |
| - | <code java> | + | } |
| - | public Builder title(String title) { | + | |
| - | this.title = title; | + | |
| - | return this; | + | |
| } | } | ||
| </code> | </code> | ||
| - | === Problema 4 - Strategy === | + | Modificați clasa **Grade** astfel încât două obiecte de tip Grade vor putea să fie |
| + | comparate (în funcție de punctajul total). De asemenea, va trebui să adăugați în | ||
| + | clasa Catalog o listă cu obiecte de tip Course. | ||
| - | Fiecare utilizator poate alege modul în care sunt afișate cântecele, în funcție | + | === Problema 4 - Observer === |
| - | de un criteriu de sortare preferat. | + | |
| - | Strategy pattern permite schimbarea algoritmului de sortare la runtime, fără | + | Aplicația noastră le permite părinților unui student să se aboneze la Catalog pentru |
| - | a modifica codul clientului. | + | a putea primi notificări în momentul în care copilul este notat de către un profesor |
| + | sau de către un asistent. Pentru a putea realiza acest lucru, veți folosi șablonul | ||
| + | de proiectare **Observer** și veți implementa o clasă **Notification** (stabiliți voi | ||
| + | care sunt atributele și metodele din această clasă – este obligatoriu să fie | ||
| + | suprascrisă metoda toString). | ||
| - | Interfața **//SortingStrategy//** definește metoda //sortedSongs(List<Song>)//. | + | Ce clasă va implementa interfața Observer și ce clasă va implementa interfața Subject? |
| - | Strategii de implementat: | + | <code java> |
| + | public interface Observer { | ||
| + | void update(Notification notification); | ||
| + | } | ||
| - | * **AverageScoreStrategy** - sortare **DESCRESCĂTOARE** după scorul mediu; | + | public interface Subject { |
| - | * **TotalViewsStrategy** - sortare **DESCRESCĂTOARE** după numărul de views; | + | void addObserver(Observer observer); |
| - | * **ReleaseYearStrategy** - sortare **CRESCĂTOARE** după data lansării (*oldest first*). | + | void removeObserver(Observer observer); |
| + | void notifyObservers(Grade grade); | ||
| + | } | ||
| + | </code> | ||
| - | Cerințe de implementare: | + | Hint: |
| + | * Parent va ține minte și o listă a notificărilor, iar Catalog o listă a Observatorilor. | ||
| + | * Atenție! Primesc notificări doar părinții studentului respectiv! Modificați clasa Student astfel încât să avem memorați și părinții pentru fiecare Student și adăugați o metodă isParent(Observer parent) care verifică dacă un observator este părintele studentului curent. | ||
| - | * Implementați cele trei strategii; | + | === Problema 5 - Strategy === |
| - | * //User.getSortedSongs()// delegă sortarea către strategia setată; | + | |
| - | * În **JSONReader**, citiți câmpul //"sortingStrategy"// din JSON și setați strategia corespunzătoare (valori: //"averageScore"//, //"totalViews"//, //"releaseYear"//). | + | |
| - | Hint pentru sortare cu Comparator: | + | Fiecare profesor va aplica o politică prin care la sfârșitul semestrului selectează |
| + | cel mai bun student. Pentru a realiza acest lucru în cadrul implementării, va trebui | ||
| + | să folosiți șablonul de proiectare **Strategy**. Veți defini câte o clasă pentru | ||
| + | fiecare din următoarele strategii: | ||
| - | <code java> | + | * **BestPartialScore** – această strategie va selecta studentul care are cel mai mare punctaj în timpul semestrului; |
| - | // Sortare DESCRESCĂTOARE după un câmp | + | * **BestExamScore** – această strategie va selecta studentul care are cel mai mare punctaj în examen; |
| - | songs.sort((s1, s2) -> s2.getField().compareTo(s1.getField())); | + | * **BestTotalScore** – această strategie va selecta studentul care are punctajul total maxim. |
| - | // Sortare CRESCĂTOARE după un câmp | + | <code java> |
| - | songs.sort((s1, s2) -> s1.getField().compareTo(s2.getField())); | + | interface Strategy { |
| + | Student getBestStudent(Collection<Grade> grades); | ||
| + | } | ||
| </code> | </code> | ||
| + | Veți adăuga în clasa **Course** un atribut Strategy, și o metodă cu antetul: | ||
| + | //public Student getBestStudent();// | ||
| + | Metoda va returna cel mai bun student, ținând cont de strategia setată de profesor | ||
| + | pentru curs. | ||
| - | === Problema 5 - Observer === | + | === Problema 6 - Visitor === |
| - | Utilizatorii platformei pot primi notificări când un cântec nou este adăugat. | + | Folosind șablonul de proiectare **Visitor**, vom implementa funcționalitatea prin care |
| + | fiecare asistent o să poată completa notele de pe parcurs ale studenților, iar fiecare | ||
| + | profesor o să poată completa notele de la examen ale studenților săi. | ||
| - | Observer pattern permite notificarea automată a obiectelor interesate (**observers**) | + | <code java> |
| - | când starea unui obiect (**subject**) se schimbă. | + | public interface Element { |
| + | void accept(Visitor visitor); | ||
| + | } | ||
| - | Interfețele din schelet: | + | public interface Visitor { |
| + | void visit(Assistant assistant); | ||
| + | void visit(Teacher teacher); | ||
| + | } | ||
| + | </code> | ||
| - | * **Subject** - definește //addObserver//, //removeObserver//, //notifyObservers// | + | Clasele **Assistant** și **Teacher** vor implementa interfața Element, iar clasa |
| - | * **Observer** - definește //update(String message)// | + | **ScoreVisitor** va implementa interfața Visitor. În clasa ScoreVisitor vom avea |
| + | două dicționare în care sunt stocate notele studenților pentru examene și pentru parcurs. | ||
| + | * Dicționarul **examScores** va avea cheia de tip Teacher și valoare de tip listă de Pair (Student, Numele cursului – ca String, nota pe care a acordat-o studentului pentru cursul indicat – ca Double). | ||
| + | * Dicționarul **partialScores** cu semnificație similară, dar pentru notele de pe parcurs atribuite de asistenți. | ||
| - | Cerințe de implementare: | + | <code java> |
| + | class Pair<K, V1, V2> { | ||
| + | private K key; | ||
| + | private V1 value1; | ||
| + | private V2 value2; | ||
| - | * **Spotify** implementează **Subject** și menține lista de observatori; | + | public Pair(K key, V1 value1, V2 value2) { |
| - | * **User** implementează **Observer** și afișează mesajul primit în //update()//; | + | this.key = key; |
| - | * În //addSong()//, după adăugarea melodiei, notificați observatorii cu mesajul: //"New song added: <titlu>"//; | + | this.value1 = value1; |
| - | * În //readData()//, după încărcarea utilizatorilor, adăugați-i ca observatori. | + | this.value2 = value2; |
| + | } | ||
| - | === Problema 6 - Visitor === | + | public K getKey() { return key; } |
| + | public V1 getValue1() { return value1; } | ||
| + | public V2 getValue2() { return value2; } | ||
| + | } | ||
| - | Pentru calculul venitului total lunar, se va folosi Design Pattern-ul **Visitor**. | + | public class ScoreVisitor implements Visitor { |
| + | private HashMap<Teacher, ArrayList<Pair<Student, String, Double>>> examScores; | ||
| + | private HashMap<Assistant, ArrayList<Pair<Student, String, Double>>> partialScores; | ||
| - | Visitor pattern permite adăugarea de operații noi asupra obiectelor fără a | + | public ScoreVisitor( |
| - | modifica clasele acestora. „Vizitorul” parcurge elementele și aplică operații | + | HashMap<Teacher, ArrayList<Pair<Student, String, Double>>> examScores, |
| - | specifice fiecărui tip. | + | HashMap<Assistant, ArrayList<Pair<Student, String, Double>>> partialScores) { |
| + | this.examScores = examScores; | ||
| + | this.partialScores = partialScores; | ||
| + | } | ||
| - | Interfețele din schelet: | + | public void visit(Assistant assistant) { |
| + | // TODO1 | ||
| + | } | ||
| - | * **Element** - definește //accept(Visitor)// | + | public void visit(Teacher teacher) { |
| - | * **Visitor** - definește //visit()// pentru fiecare tip de utilizator | + | // TODO2 |
| - | + | } | |
| - | Formula de calcul a venitului per utilizator: | + | } |
| - | + | </code> | |
| - | * //revenue = subscription * (1 - discount/100.0) * VAT// | + | |
| - | * unde //VAT = 1.19// | + | |
| - | Cerințe de implementare: | + | * **TODO1** – veți determina toate notele pe care le are de trecut asistentul primit ca parametru de metoda respectivă. Veți verifica dacă pentru o intrare din lista de note există sau nu un obiect de tip Grade pentru cursul indicat corespunzător studentului. Dacă există, atunci se va seta nota de pe parcurs pentru acel obiect, dacă nu există, se va crea un nou obiect Grade și se va adăuga cursului. |
| + | * **TODO2** – veți determina toate notele pe care le are de trecut profesorul primit ca parametru de metoda respectivă. Veți verifica dacă pentru o intrare din lista de note există sau nu un obiect de tip Grade pentru cursul indicat corespunzător studentului. Dacă există, atunci se va seta nota de la examen pentru acel obiect, dacă nu există, se va crea un nou obiect Grade și se va adăuga cursului. | ||
| - | * **User** implementează **Element**; | + | Hint! Veți adăuga metodele de care aveți nevoie în clasele utilizate. |
| - | * **Regular**, **Premium**, **UltraPremium** implementează //accept()// apelând //visitor.visit(this)//; | + | Exemple: În Catalog veți adăuga o metodă //public Course getCourse(String name)//, care |
| - | * **RevenueVisitor** implementează **Visitor** cu metode //visit()// pentru fiecare tip; | + | întoarce obiectul Course corespunzător numelui trimis ca parametru. |
| - | * **RevenueVisitor** acumulează //totalRevenue// și oferă //getTotalRevenue()//; | + | În Course o metodă //addGrade(Grade g);//. Etc. |
| - | * //Spotify.calculateRevenue()// creează un visitor, parcurge userii și returnează venitul total. | + | |
| - | Exemplu de utilizare Visitor (în //Spotify.calculateRevenue//): | + | === Clasa Test (pentru testare) === |
| <code java> | <code java> | ||
| - | RevenueVisitor visitor = new RevenueVisitor(); | + | class Test { |
| - | for (User user : users) { | + | public static void main(String[] args) { |
| - | user.accept(visitor); | + | //testare exercitiu 2 |
| - | } | + | User studentA = UserFactory.createUser("Student", "A", "Popescu"); |
| - | return visitor.getTotalRevenue(); | + | User studentB = UserFactory.createUser("Student", "B", "Ionescu"); |
| - | </code> | + | User studentC = UserFactory.createUser("Student", "C", "Ionescu"); |
| + | User mother = UserFactory.createUser("Parent", "M_AC", "Ionescu"); | ||
| + | User father = UserFactory.createUser("Parent", "T_AC", "Ionescu"); | ||
| + | User teacher = UserFactory.createUser("Teacher", "Teacher", "Georgescu"); | ||
| + | User assistant = UserFactory.createUser("Assistant", "Assistant", "Popescu"); | ||
| + | //testare exercitiu 3+5 | ||
| + | Course course = new Course.CourseBuilder("POO").teacher((Teacher) teacher) | ||
| + | .assistant((Assistant) assistant).grade(new Grade("POO", (Student) studentA, 4d,5d)) | ||
| + | .grade(new Grade("POO", (Student) studentB,3d,3d)).strategy(new BestExamScore()).build(); | ||
| + | System.out.println("Curs: "+ course); | ||
| + | System.out.println("Best Student:" + course.getBestStudent()); | ||
| - | === BONUS === | + | //testare exercitiu 1+3+5 |
| + | Catalog catalog = Catalog.getInstance(); | ||
| + | catalog.addCourse(course); | ||
| - | Implementați pattern-ul **Observer** și pentru rating-uri. | + | //testare exercitiu 4 |
| + | ((Student) studentB).setMother((Parent) mother); | ||
| + | ((Student) studentB).setFather((Parent) father); | ||
| + | ((Student) studentC).setMother((Parent) mother); | ||
| + | catalog.addObserver((Parent) mother); | ||
| + | catalog.addObserver((Parent) father); | ||
| + | catalog.notifyObservers(new Grade("POO", (Student)studentB,2.5d,3d)); | ||
| - | Când un utilizator adaugă un rating unui cântec, toți utilizatorii care au dat | + | //testare exercitiu 6 |
| - | anterior un rating aceluiași cântec vor fi notificați. | + | HashMap<Teacher, ArrayList<Pair<Student, String, Double>>> examScores=new HashMap<>(); |
| + | ArrayList<Pair<Student, String, Double>> ar1=new ArrayList<>(); | ||
| + | ar1.add(new Pair(studentA,"POO",3.6d)); | ||
| + | examScores.put((Teacher)teacher,ar1); | ||
| - | Cerințe de implementare: | + | HashMap<Assistant, ArrayList<Pair<Student, String, Double>>> partialScores= new HashMap<>(); |
| + | ArrayList<Pair<Student, String, Double>> ar2=new ArrayList<>(); | ||
| + | ar2.add(new Pair(studentC,"POO",4.6d)); | ||
| + | partialScores.put((Assistant)assistant,ar2); | ||
| - | * **Song** menține o listă de observatori (utilizatorii care au dat rating); | + | Visitor v=new ScoreVisitor(examScores,partialScores); |
| - | * La //addRating()//, adăugați rating-ul, apoi notificați observatorii existenți cu mesajul: //"New rating for: <titlu>"//; | + | v.visit((Teacher)teacher); |
| - | * Utilizatorul care adaugă rating-ul devine și el observator pentru viitor. | + | System.out.println("Actualizare teacher:" + catalog); |
| + | v.visit((Assistant)assistant); | ||
| + | System.out.println("Actualizare assistant:" + catalog); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||