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
- Implementați realocarea de memorie pentru un vector
- Implementați un iterator pentru vectorul vostru