Design patterns

Un design pattern, sau un model de proiectare, este o soluție generală, repetabilă pentru provocările din proiectarea software. Acesta este un șablon, un model pentru rezolvarea unei probleme, care poate fi adaptat în cazuri specifice.

Utilitate

Modelele de proiectare pot accelera procesul de dezvoltare prin furnizarea de paradigme de dezvoltare și testare. Pentru a crea un model de proiectare este necesar să luăm în calcul problemele ce pot să apară pe parcursul dezvoltării proiectului, în implementare. Reutilizarea unor modele de proiectare standradizate ajută la prevenirea problemelor, chiar și la depistarea lor și îmbunătățesc lizibilitatea codului, pentru a putea fi înțeles mai ușor de alți oameni.

Modelele de design pot fi dezvoltate sau ajustate de fiecare echipă în parte, pe parcurs, reprezentând un limbaj universal în interiorul proiectului.

Factory

Creează o instanță a mai multor familii de clase

Scop și utilitate

Furnizează o interfață pentru crearea familiilor de obiecte înrudite sau dependente, fără a specifica clasele lor concrete. Acest lucru este util atunci când trebuie să instanțiem mai multe obiecte, dar cu proprietăți diferite. Un exemplu ar fi modelele de mașini. Toate sunt mașini, dar au proprietăți diferite, iar fiecare producător poate decide care să fie.

Builder

Separă construcția obiectului în sine de reprezentarea acestuia

Scop și utilitate

Separă construcția unui obiect complex de reprezentarea acestuia, astfel încât același proces de construcție să poată crea reprezentări diferite. Acest lucru este util atunci când pornind de la același agregat și avem mai multe posibilități în care acesta se poate transforma. Un exemplu este un fișier pe care dorim să îl salvăm și avem posibilitatea să îl salvăm ca raw, pdf sau docx.

File newFile;

switch (newFile.type) {
    case LIST:
        SaveTool::saveAsXCEL(newFile);
        break;
    case PARAGRAPH:
        SaveTool::saveAsDOCX(newFile);
        break;
}



Object Pool

Evită achiziționarea și eliberarea costisitoare de resurse prin reciclarea obiectelor care nu mai sunt utilizate

Scop și utilitate

Gruparea de obiecte poate oferi o creștere semnificativă a performanței. Acest model reciclează obiecte pe care programul nu le mai folosește. Acest lucru este util atunci când gestionăm multe obiecte de același tip și nu dorim să pierdem din eficiența programului instanțiând obiecte noi și stergându-le pee cele vechi, ci mai degrabă le reutilizăm pe cele care nu mai sunt de trebuință.

Prototype

O instanță complet inițializată de copiat sau clonat

Scop și utilitate

Specifică tipurile de obiecte pentru creat folosind o instanță prototip și creează obiecte noi prin copierea acestui prototip.

Singleton

O clasă din care poate exista doar o singură instanță

Scop și utilitate

Se asigură că o clasă are o singură instanță și oferă un punct global de acces la ea. Acest lucru este util atunci când dorim să avem o singură instanță a unei clase, precum ar fi un manager.

Exemplu

Un exemplu de singleton poate fi o clasă care administrează timpul global dintr-un joc. Nu avem nevoie de mai multe obiecte, dar pentru a separa logica, este bine să folosim o clasă, un singleton în acest caz.

class Time
{
public:
	static Time& GetInstance() {
		static Time instance;
		return instance;
	}

private:
	Time() {};
	Time(Time const&) = delete;
	void operator= (Time const&) = delete;
};

Singletonul nu are un constructor utilizabil. Pentru a accesa interfața clasei, trebuie mai întâi să facem rost de instanța singletonului apelând GetInstance(), care returnează o instanță statică. Abia după aceea, prin intermediul instanței, putem avea acces la interfața clasei.

Decorator / Wrapper

Adăugă responsabilități obiectelor în mod dinamic

Scop și utilitate

Decoratorii oferă o alternativă flexibilă la subclasare pentru extinderea funcționalității. Aceștia funcționează precum adăugarea de decorațiuni unui brad: bradul rămâne același, dar funcționalitatea se schimbă ușor. Un alt exemplu pot fi atașamentele pe armele din jocurile video.

Exemplu

Un exemplu de singleton poate fi o clasă care administrează timpul global dintr-un joc. Nu avem nevoie de mai multe obiecte, dar pentru a separa logica, este bine să folosim o clasă, un singleton în acest caz.

#include <iostream>

class Component {
public:
    virtual ~Component() {}
    virtual std::string Operation() const = 0;
};


class ConcreteComponent : public Component {
public:
    std::string Operation() const override {
        return "ConcreteComponent";
    }
};


class Decorator : public Component {
public:
    Decorator(Component* component) : component_(component) { }

    std::string Operation() const override {
        return this->component_->Operation();
    }

protected:
    Component* component_;
};


class ConcreteDecoratorA : public Decorator {
public:
    ConcreteDecoratorA(Component* component) : Decorator(component) { }

    std::string Operation() const override {
        return "ConcreteDecoratorA(" + Decorator::Operation() + ")";
    }
};


class ConcreteDecoratorB : public Decorator {
public:
    ConcreteDecoratorB(Component* component) : Decorator(component) { }

    std::string Operation() const override {
        return "ConcreteDecoratorB(" + Decorator::Operation() + ")";
    }
};


void Print(Component* component) {
    std::cout << component->Operation() << '\n';
}

int main() {
    Component* starting = new ConcreteComponent;

    Print(starting);

    Component* decorator1 = new ConcreteDecoratorA(starting);
    Component* decorator2 = new ConcreteDecoratorB(decorator1);

    Print(decorator2);

    delete starting;
    delete decorator1;
    delete decorator2;

    return 0;
}

Programul va afișa prima dată doar ConcreteComponent, iar a 2-a oară va afișa ConcreteDecoratorB(ConcreteDecoratorA(ConcreteComponent)).

Iterator

Accesează secvențial elementele unei colecții

Scop și utilitate

Iteratorii permit accesarea elementelor unui agregat, fără a expune reprezentarea de bază. Acest lucru este util atunci când dorim să abstractizăm parcurgerea unui set de date și să nu mai interacționăm neapărat cu datele.

Exemplu

Iteratorii sunt folosiți pentru a arăta către locuri din memorie ale containerelor din STL și prezintă posibilitatea de a itera prin elemente. Un exemplu de iterator este un pointer, dar în STL, aceștia sunt mai complecși de atât. Aceștia reduc complexitatea și timpul de execuție al programelor.

Iteratorii lucrează cu pointeri pentru a putea funcționa. O clasă de iteratori ar trebui să aibă ca membri un pointer și un index. Este importnat ca operatorii iteratorilor sa fie rescriși pentru a asigura funcționalitatea dorită. Spre exemplu, operatorul * ar putea să returneze, prin referință, valoarea la care arată iteratorul, operatorul -> ar putea să returneze pointerul, operatorul ++ ar putea să avanseze iteratorul, iar -- să îl retrogradeze. Aceste efecte se pot realiza usor, manipulând pointerul și indexul iteratorului.

class iterator {
private:
    T *mPtr;
    int mIndex;

private:
    // default constructor
    iterator() {
        mPtr = nullptr;
    }

    // constructs based on a vector
    iterator(const vector<T> &vect) {
        *this = vect.begin();
    }

    // constructs based on a vector and a position
    iterator(const vector<T> &vect, T *ptr) {
        mPtr = ptr;
        mIndex = ptr - vect.vect;
    }

public:
    // returnează referință la valoarea de la pointerul respectiv
    int &operator*() {
        return *mPtr;
    }

    // returnează pointerul 
    int *operator->() {
        return mPtr;
    }



Task

  1. Implementați realocarea de memorie pentru un vector
  2. Implementați un iterator pentru vectorul vostru