This is an old revision of the document!
Design patterns reprezintă soluții generale, reutilizabile, pentru probleme des întâlnite în cadrul software design. Un design pattern este o schemă a unei soluții pentru o problemă de design (nu reprezintă un algoritm sau o bucată de cod care poate fi aplicată direct), ce poate ușura munca programatorului și poate duce la simplificarea și eficientizarea arhitecturii unei aplicații.
Există 3 tipuri de design patterns, în funcție de aspectul și de funcționalitatea obiectelor:
Uneori ne dorim să avem un obiect care să apară doar o singură dată într-o aplicație (de exemplu conducătorul unei țări). De aceea folosim Singleton, un mod prin care restricționăm numărul de instanțieri ale unei clase: clasa are o singură instanță, care va fi folosită în întreg proiectul.
Pentru a asigura restricționarea:
Există două abordări frecvente:
public class SingletonClass { /* la inceput, inainte de prima si singura instantiere a clasei SingletonClass va avea valoarea null */ private static SingletonClass obj = null; public int value = 10; // lasam constructorul clasei privat pentru a nu fi accesat din exterior private SingletonClass() { // do stuff System.out.println("Instantiam!"); } // metoda prin care se creaza unica instanta a clasei // lazy instantiation public static SingletonClass getInstance() { // daca clasa nu a fost instantiata inainte, o facem acum if (obj == null) obj = new SingletonClass(); return obj; } public void show() { System.out.println("Singleton is magic"); } }
Un avantaj este accesul ușor la instanța globală, fără a avea nevoie să o transmitem ca parametru sau să o instanțiem manual.
public void modifyValue (int x) { SingletonClass.getInstance().value = x; // se modifica valoarea lui value din clasa } SingletonClass.getInstance().show();
public class SingletonClass { private static SingletonClass obj = new SingletonClass(); private SingletonClass() {} // eager instantiation - merge la threaduri public static SingletonClass getInstance() { return obj; } }
Utilizări frecvente:
Uneori suntem nevoiți să creăm obiecte în funcție de preferința utilizatorului sau de alte necesități. De aceea folosim Factory, prin care alcătuim o familie de clase înrudite (prin moștenirea aceleiași clase abstracte sau implementarea aceleiași interfețe), iar crearea obiectului concret este delegată către o metodă de tip factory.
În exemplul de mai jos, utilizatorul cere un tip de pizza prin nume; dacă tipul există, primește informații despre pizza.
interface IPizza { void showStuff(); } /* nu este neaparat sa avem o clasa abstracta ce implementeaza o interfata putem avea pur si simplu o clasa abstracta (fara sa implementeze o interfata) care e extinsa de clasele normale sau o interfata ce e implementata direct de clasele normale din Factory */ abstract class Pizza implements IPizza { public abstract void showStuff(); } class PizzaMargherita extends Pizza { public void showStuff() { System.out.println("Sos tomat si branza Mozzarella."); } } class PizzaQuattroStagioni extends Pizza { public void showStuff() { System.out.println("Sos tomat, branza Mozzarella, sunca, pepperoni, " + "ciuperci, ardei. "); } } class PizzaPepperoni extends Pizza { public void showStuff() { System.out.println("Sos tomat, branza Mozzarella, dublu pepperoni."); } } class PizzaHawaii extends Pizza { public void showStuff() { System.out.println("Sos tomat, branza Mozzarella, sunca, dublu ananas."); } } class PizzaFactory { public static Pizza factory (String pizzaName) { if (pizzaName.equals("Margherita")) return new PizzaMargherita(); if (pizzaName.equals("Hawaii")) return new PizzaHawaii(); if (pizzaName.equals("Quattro Stagioni")) return new PizzaQuattroStagioni(); if (pizzaName.equals("Pepperoni")) return new PizzaPepperoni(); return null; } }
O clasă de tip Factory poate fi utilizată în mai multe locuri în cadrul unui proiect. Pentru a economisi resurse, putem folosi pattern-ul Singleton pentru Factory, astfel încât să existe o singură instanță a clasei Factory.
Acest design pattern stabilește o relație one-to-many între obiecte. Avem un obiect numit subiect, căruia îi este asociată o colecție (listă) de observatori. Observatorii sunt obiecte dependente de subiect și sunt notificate automat de către subiect atunci când în subiect are loc o acțiune sau o modificare a stării.
Strategy este un design pattern behavioral ce oferă o familie de algoritmi (strategii), încapsulate în clase care oferă o interfață comună de folosire. Clientul (utilizatorul) poate alege dinamic strategia care va fi folosită.
Exemplu de motivare: la căutarea unui element într-o colecție, putem alege algoritmul în funcție de proprietăți ale colecției (de exemplu, dacă este sortată: căutare binară; altfel: iterare liniară).
Acest design pattern oferă posibilitatea de a separa un algoritm de structura de date pe care acesta operează, astfel încât să putem adăuga ușor funcții noi care operează peste o familie de clase fără să modificăm structura acestora.
Pe scurt, folosim Visitor dacă avem tipuri diferite și dorim să adăugăm/schimbăm operații fără să modificăm clasele.
În cadrul pattern-ului:
interface Visitor { void visit (Director f); void visit (Fisier f); } class Ls implements Visitor { public void visit (Director f) { System.out.println(f.getName()); for (Repository repo: f.getChildren()) { System.out.println("\t" + repo.getName()); // afisam numele unui repo (fisier / folder) } } public void visit (Fisier f) { System.out.println("Not a folder"); /* comanda Ls (in acest exemplu) este specifica doar folderelor, in acest caz este evidentiat un dezavantaj al Visitor-ului, faptul ca noi trebuie sa implementam metode de care nu avem nevoie in acest caz - se incalca Interface Segregation Principle */ } } class Cat implements Visitor { public void visit (Director f) { // avertisment ca avem folder, nu fisier } public void visit (Fisier f) { // citire fisier, folosind numele fisierului } } abstract class Repository { private String name; // numele unui fisier sau folder (de fapt, calea acestuia) public String getName() { return name; } public abstract void accept (Visitor f); } class Fisier extends Repository { public void accept (Visitor f) { f.visit(this); // Visitor-ul "viziteaza" fisierul, adica acesta // efectueaza o operatie asupra fisierului } } class Director extends Repository { private List<Repository> children = new ArrayList<>(); public List<Repository> getChildren() { return children; } public void accept (Visitor f) { f.visit(this); } }