Breviar 11

Introducere în Design Patterns

1. Ce reprezintă design patterns?

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.

2. Tipuri de design patterns

Există 3 tipuri de design patterns, în funcție de aspectul și de funcționalitatea obiectelor:

  • Behavioral Patterns: tratează modul de interacționare între obiecte (Observer, Strategy, Visitor, Command);
  • Creational Patterns: tratează modul de creare a obiectelor (Factory, Singleton, Builder);
  • Structural Patterns: tratează modul cum este reprezentat un obiect și cum sunt relațiile între entități (Decorator, Composite, Proxy, Facade, Adapter).

1. Creational Patterns

1.1 Singleton Pattern

Descriere

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:

  • constructorul clasei de tip Singleton este privat (astfel blocăm instanțierea multiplă din exterior);
  • avem un membru static și privat care reține instanța unică;
  • avem o metodă statică și publică prin care returnăm instanța (se creează dacă nu există deja).

Există două abordări frecvente:

  • lazy instantiation (instanța se creează la prima cerere);
  • eager instantiation (instanța se creează imediat, la încărcarea clasei).
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");
    }
}

Avantaje / Dezavantaje

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();

Singleton poate fi dezavantajos la testare, deoarece leagă dependențe între clase și îngreunează izolarea acestora. De asemenea, varianta lazy nu este thread-safe în multithreading; în astfel de situații se preferă eager instantiation (sau alte variante thread-safe).

public class SingletonClass {
    private static SingletonClass obj = new SingletonClass();
    private SingletonClass() {}
 
    // eager instantiation - merge la threaduri
    public static SingletonClass getInstance() {
        return obj;
    }
}

Utilizări

Utilizări frecvente:

  • înlocuirea variabilelor globale (instanța Singleton este „globală”);
  • obiecte care reprezintă resurse partajate (de exemplu logger);
  • implementarea de Factory (vezi mai jos).
1.2 Factory Pattern

Descriere

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.

Exemplu (pizzas)

Î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;
    }
}
1.3 Builder Pattern

Descriere

Acest pattern este folosit în restaurantele de tip fast food care furnizează meniul pentru copii. Un meniu pentru copii constă de obicei într-un fel principal, unul secundar, o băutură și o jucărie. Pot exista variații în ceea ce privește conținutul meniului, dar procesul de creare este același. Fie că la felul principal se alege un hamburger sau un cheeseburger, procesul va fi același. Vânzătorul le va indica celor din spate ce să pună pentru fiecare fel de mâncare, pentru băutură și jucărie. Toate acestea vor fi puse într-o pungă și servite clienților.

Acest șablon realizează separarea construcției de obiecte complexe de reprezentarea lor astfel încât același proces să poată crea diferite reprezentări. Builder-ul creează părți ale obiectului complex de fiecare dată când este apelat și reține toate stările intermediare. Când produsul este terminat, clientul primește rezultatul de la builder.

În acest mod, se obține un control mai mare asupra procesului de construcție de noi obiecte. Spre deosebire de alte pattern-uri din categoria creational, care creau produsele într-un singur pas, pattern-ul Builder construiește un produs pas cu pas la comanda coordonatorului.

Exemplu (User cu atribute required și optional)

public class User {
    private final String firstName; // required
    private final String lastName;  // required
    private final int age;          // optional
    private final String phone;     // optional
    private final String address;   // optional
 
    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public int getAge() {
        return age;
    }
 
    public String getPhone() {
        return phone;
    }
 
    public String getAddress() {
        return address;
    }
 
    public String toString() {
        return "User:" + this.firstName + " " + this.lastName + " " +
               this.age + " " + this.phone + " " + this.address;
    }
 
    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;
 
        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }
 
        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }
 
        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }
 
        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }
 
        public User build() {
            return new User(this);
        }
    }
 
    public static void main(String[] args) {
        User user1 = new User.UserBuilder("Lokesh", "Gupta")
                .age(30)
                .phone("1234567")
                .address("Fake address 1234")
                .build();
 
        User user2 = new User.UserBuilder("Jack", "Reacher")
                .age(40)
                .phone("5655")
                //no address
                .build();
    }
}
1.4 Singleton Factory

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.

2. Behavioral Patterns

2.1 Observer Pattern

Descriere

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.

2.2 Strategy Pattern

Descriere

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ă).

2.3 Visitor Pattern

Descriere

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:

  • avem o interfață Visitor, care reprezintă operația aplicată;
  • avem o interfață/clasă abstractă Visitable (numită în unele scheme Element), care reprezintă obiectele peste care se aplică operațiile;
  • Visitor are metode de forma `visit(…)`;
  • Visitable are metoda `accept(Visitor v)`.

Exemplu (Ls/Cat peste Repository: Director/Fisier)

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);
    }
}
poo/breviare/breviar-11.txt · Last modified: 2025/12/14 17:41 by george.tudor1906
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