Sunteți pe pagina 1din 171

BOGDAN GEORGE TUDORICĂ

BOGDAN GEORGE TUDORICĂ

O CĂLĂTORIE PRIN
LUMEA PROGRAMĂRII
ORIENTATE PE OBIECTE

O CĂLĂTORIE PRIN LUMEA PROGRAMĂRII ORIENTATE PE OBIECTE

ISBN 978-606-23-1033-2

9 786062 310332
Bogdan George Tudorica

0 calatorie prin

lumea programarii orientate pe obiecte

Editura PRINTECH
BUCURE~TI 2019
Editura PRINTECH
f

Tipar executat la:


S.C. ANDOR TIPO S.R.L. - Editura PRINTECH
Site: www.andortipo.ro; www.printech.ro
Adresa: Str. Tunari nr.11, Sector 2, Bucure~ti
Tel./Fax: 021.211.37.12; 021.212.49.51
E-mail: comenzi@andortipo.ro

Descrierea CIP a Bibliotecii Nationale a Romaniei


TUDORICA, BOGDAN GEORGE
O calatorie prin lumea programarii orientate pe obiecte I
Bogdan George Tudorica. -
Bucure~ti : Printech, 2019
Contine bibliografie
ISBN 978-606-23-1033-2

004

Referenti ~tiintifici:
prof. univ. dr. Dusmanescu Dorel, Universitatea Petrol-Gaze din Ploie~ti,
Facultatea de $tiinte Economice, Departamentul Cibernetica, Informatica
Economica, Finante ~i Contabilitate
lect. univ. dr. Bucur Cristian, Universitatea Petrol-Gaze din Ploie~ti,
Facultatea de $tiinte Economice, Departamentul Cibernetica, Informatica
Economica, Finante ~i Contabilitate

© Copyright 2019
Toate drepturile prezentei editii sunt rezervate autorului. Nicio parte din
aceasta lucrare nu poate fi reprodusa, stocata sau transmisa indiferent prin ce
forma, tara acordul prealabil scris al autorului.
Autorul poarta intreaga raspundere morala, legala ~i materiala fata de editura
~i teqe persoane pentru continutul lucrarii.
Cuprins
Despre această carte ................................................................................................................... 3
Elemente de bază ale programării orientate pe obiecte ............................................................. 5
Secțiunea 01 - Scurtă introducere în programarea orientată pe obiecte ................................. 5
Constructori........................................................................................................................ 7
Destructori.......................................................................................................................... 7
Nivele de acces .................................................................................................................. 8
Secțiunea 02 ........................................................................................................................... 9
Tutorialul 01 - Constructori impliciți și expliciți, destructori. ........................................... 9
Tutorialul 02 - Exemplul anterior completat cu setter-i și getter-i .................................. 13
Tutorialul 03 - Exemplul anterior completat cu o metoda to_string ................................ 16
Tutorialul 04 - Metode de inițializare a obiectelor .......................................................... 19
Secțiunea 03 ......................................................................................................................... 22
Tutorialul 05 - Modul de utilizare a operatorului de rezoluție......................................... 22
Tutorialul 06 - Modalități de inițializare a atributelor în constructori ............................. 24
Tutorialul 07 - Clase cu atribute alocate dinamic ............................................................ 26
Tutorialul 08 - Obiecte alocate dinamic........................................................................... 28
Tutorialul 09 - Modalități alternative de declarare a claselor în C++ .............................. 30
Secțiunea 04 ......................................................................................................................... 35
Tutorialul 10 - Supraîncărcarea operatorilor.................................................................... 35
Tutorialul 11 - Atribute statice ......................................................................................... 42
Tutorialul 12 - Metode statice .......................................................................................... 44
Tutorialul 13 - Obiecte constante și metode constante .................................................... 50
Secțiunea 05 ......................................................................................................................... 55
Tutorialul 14 - Template-uri (șabloane) de clase ............................................................. 55
Tutorialul 15 - Specializarea template-urilor (șabloanelor) de clase ............................... 56
Tutorialul 16 - Constructori de copiere ............................................................................ 59
Tutorialul 17 - Asignare (atribuire) prin copiere ............................................................. 67
Secțiunea 06 ......................................................................................................................... 72
Tutorialul 18 - Constructori de mutare ............................................................................ 72
Tutorialul 19 - Asignare (atribuire) prin mutare .............................................................. 76
Rezumat funcții membre speciale .................................................................................... 80

1
Tutorialul 20 - Funcții friend ........................................................................................... 81
Tutorialul 21 - Clase friend .............................................................................................. 83
Secțiunea 07 ......................................................................................................................... 88
Tutorialul 22 - Derivare și moștenire ............................................................................... 88
Tutorialul 23 - Moștenire multiplă................................................................................... 97
Secțiunea 08 ....................................................................................................................... 103
Tutorialul 24 - Polimofism............................................................................................. 103
Tutorialul 25 - Membri virtuali ...................................................................................... 105
Tutorialul 26 - Clase de bază abstracte .......................................................................... 107
Secțiunea 09 - Aplicații rezolvate și aplicații propuse ....................................................... 113
Aplicații rezolvate .......................................................................................................... 113
Aplicații propuse ............................................................................................................ 115
Secțiunea 10 - Aplicație rezolvată ..................................................................................... 119
Secțiunea 11 - Aplicație rezolvată ..................................................................................... 121
Elemente avansate de programare orientată pe obiecte - tipare de proiectare ....................... 127
Secțiunea 12 – Principii avansate ale POO - S.O.L.I.D. (Single responsibility / Open-closed
/ Liskov / Interface segregation / Dependecy inversion principles) .................................. 127
Tutorialul 27 - Principiul responsabilității unice (Single responsibility principle) ....... 130
Tutorialul 28 - Principiul deschis-închis (Open-closed principle) ................................. 131
Tutorialul 29 - Principiul Liskov al substituției (Liskov substitution principle) ........... 133
Tutorialul 30 - Principiul separării interfețelor (Interface segregation principle) ......... 135
Tutorialul 31 - Principiul inversării dependețelor (Dependecy inversion principle) ..... 137
Secțiunea 13 – Introducere în tipare de proiectare............................................................. 139
Tipare de proiectare software......................................................................................... 140
Tipare de arhitectură software ....................................................................................... 147
Tipare de interacțiune .................................................................................................... 149
Anti-tipare ...................................................................................................................... 152
Code smell & design smell ............................................................................................ 158
Secțiunea 14 – Seturi de tipare de proiectare - G.R.A.S.P. (General Responsibility
Assignment Software Patterns) .......................................................................................... 162
Încheiere ................................................................................................................................. 165
Referințe bibliografice ........................................................................................................... 166

2
Despre această carte

Lucrarea de față a pornit de la ideea simplă a unei colecții de aplicații practice pentru disciplina
Programare Orientată pe Obiecte, pe scurt, un caiet de laborator. Mai târziu am realizat că ar fi
avut puțin sens, pentru acest gen de aplicații, să furnizez doar aplicațiile, fără explicații. După
explicații au venit alte explicații, după alte explicații au venit comentarii, după comentarii au
venit alte idei și tot așa, până când m-am pomenit că am scris o carte întreagă.
Răzbate chiar din primul paragraf ideea că rostul acestei cărți este unul pur didactic. Nu conține
nicio idee pe care să nu o fi enunțat altcineva înainte, uneori de atât de multe ori încât nu se
mai știe cine va fi fost autorul original. Singurul element de originalitate (termen la modă prin
literatura universitară) este modul în care am pus laolaltă și am explicat conceptele urmărite.
Practic, sensul cărții este speranța că, expunând informațiile într-un anumit mod, voi face
învățarea mai ușoară pentru vreunii dintre studenții mei, sau poate chiar pentru vreun alt doritor
de a învăța programare orientată pe obiecte.
De ce Programare Orientată pe Obiecte? Pentru că aceasta este paradigma de programare cea
mai utilizată în acest moment (2019). Practic, din primele zece limbaje de programare aflate în
indexul TIOBE1 în acest moment, opt sunt orientate pe obiecte. Pur și simplu este aproape
inevitabil ca un viitor programator să poată urma această carieră fără a învăța să lucreze în acest
mod. Și pentru că este unul dintre elementele definitorii ale educației unui viitor programator
– nu există un singur program de studii serios în domeniu, indiferent că se numește Informatică,
sau Informatică Economică, sau Automatică, sau Calculatoare, sau altcumva, care să nu conțină
într-un fel sau altul această disciplină sau elemente din această disciplină.
Lucrarea urmărește, până la un punct, calea clasică a predării programării orientate pe obiecte,
în sensul că:
• Limbajul utilizat pentru exemple este C++.
• Succesiunea logică a primei părți este cea urmată în mod obișnuit în parcurgerea acestei
discipline.
Ce lipsește din această carte? Lipsesc elementele de programare orientată pe obiecte bazată pe
prototipuri (care nu este specifică pentru limbajul C++) și care ar necesita o altă carte. Lipsesc
o mulțime alte informații care nu și-au mai făcut loc în spațiul redus al acestei lucrări și pentru
care iarăși ar mai fi nevoie de o altă carte. Lipsesc un număr de exemple de cod comentat pentru
secțiunea finală a cărții, exemple pe care le voi adăuga poate cu ocazia unei posibile ediții
viitoare.
Acestea fiind spuse, nu îmi rămâne decât să vă urez spor în lectura acestei cărți și în învățarea
programării orientate pe obiecte și nu uitați că învățarea programării se face prin încercare și
exercițiu!

1
https://www.tiobe.com/tiobe-index/

3
Elemente de bază ale programării orientate pe obiecte

Secțiunea 01 - Scurtă introducere în programarea orientată pe obiecte


Se numește programare orientată pe obiecte (sau programare orientată obiect) un mod de
programare în cadrul căruia programele sunt concepute ca imagini simplificate ale problemei
reale de rezolvat. În programele concepute în această manieră, entitățile din problema reală
(indivizi, ființe, obiecte, sisteme, subsisteme etc.) sunt reprezentate prin structuri de date
denumite obiecte.
Se numește clasă un tip de date structurat care conține:
● variabile în care vor fi stocate caracteristici ale unei categorii de entități din realitate
● funcții care vor reprezenta comportamentele specifice ale entităților din acea categorie.
Clasele descriu, în mod simplificat, structura și comportamentele tuturor obiectelor din
categoria respectivă. Clasele se folosesc ca orice alt tip de date.
Obs. Variabilele din compoziția unei clase se numesc "atribute" ale clasei respective. Funcțiile
din compoziția unei clase se numesc "metode" ale clasei respective.
Obs. Atributele și metodele unei clase se mai numesc și membri ai clasei respective.
Obs. Se spune despre obiectele care au fost create folosind o anumită clasă ca tip de date, că
sunt "instanțe" ale clasei respective sau că au fost create prin "instanțierea" clasei respective.
Obs. Conceptul de clasă este nu este specific tuturor limbajelor de programare orientate pe
obiecte ci doar acelora bazate pe clase2 (ex. C++, C#, Java). În limbajele de programare
orientate pe obiecte bazate pe prototipuri3 (ex. JavaScript, Self) nu există un echivalent al clasei
și obiectele se construiesc pe baza unui prototip, care este doar un alt obiect ”clonat” și
modificat corespunzător.
Programarea orientată pe obiecte (P.O.O.) are un număr de principii de bază care sunt valabile
indiferent de limbajul în care se face implementarea:
● Abstractizarea - clasa reprezintă o imagine abstractă, simplificată, a unei categorii de
entități din realitate iar obiectul reprezintă o imagine abstractă, simplificată a unei
anumite entități din realitate. În timpul execuției programului, obiectele interacționează
între ele prin acțiuni simple și nu este necesară expunerea către alte obiecte a modului
în care acțiunile respective au fost executate intern. Rezultatul operației de abstractizare
se numește simplificat abstracție.
● Încapsularea - datele conținute de obiectele dintr-o clasă ar trebui protejate, de așa
natură încât să nu fie accesibile din exterior decât în caz de nevoie și doar prin
intermediul metodelor clasei respective

2
https://en.wikipedia.org/wiki/Class-based_programming
3
https://en.wikipedia.org/wiki/Prototype-based_programming

5
● Moștenirea - este posibil ca o clasă (denumită clasă derivată) să moștenească o altă
clasă (denumită clasă de bază), în sensul că va prelua de la clasa de bază o selecție de
atribute și metode (acestea nu vor mai trebui rescrise în clasa derivată) la care se vor
adăuga, după necesități, atribute și metode specifice clasei derivate.
● Polimorfismul - un obiect aparținând unei clase derivate dintr-o altă clasă poate fi tratat
atât ca aparținând clasei derivate cât și ca făcând parte din clasa de bază (greacă: poli -
mai multe, morphos - formă - obiectul poate avea mai multe forme).
Obs. Se numește limbaj orientat pe obiecte un limbaj de programare care respectă parțial sau
integral principiile programării orientate pe obiecte.
Obs. Se numește limbaj parțial orientat pe obiecte un limbaj care nu respectă în întregime
cele patru principii de mai sus.
Exemple de limbaje parțial orientate pe obiecte sunt Java, C++, C#, Delphi / Object Pascal,
VB.NET.
Obs. Se numește limbaj pur orientat pe obiecte un limbaj care respectă toate cele patru
principii de mai sus și mai îndeplinește suplimentar următoarele trei criterii:
● Toate tipurile de date predefinite sunt clase/obiecte.
● Toate tipurile de date definite de utilizator sunt clase/obiecte
● Toate operațiile efectuate pe obiecte sunt executate numai prin intermediul metodelor
obiectelor respective.
Exemple de limbaje pur orientate pe obiecte sunt Python, Ruby, Scala, Smalltalk, Eiffel,
Emerald, JADE și Self.
Obs. Nivelul de “puritate” la unui limbaj orientat pe obiecte și modul în care este implementată
programarea orientată pe obiecte în limbaj au efecte consistente asupra modului de lucru în
limbajul respectiv și sunt întotdeauna elemente principale ale filosofiei limbajului respectiv.
Mai multe informații despre programarea orientată pe obiecte, despre diverse limbaje de
programare orientate pe obiecte, specificități ale acestora și diverse comparații pot fi găsite în
următoarele resurse:
● https://ro.wikipedia.org/wiki/Programare_orientat%C4%83_pe_obiecte
● https://en.wikipedia.org/wiki/Object-oriented_programming
● https://en.wikipedia.org/wiki/List_of_object-oriented_programming_languages
● https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(object-
oriented_programming)
● https://www.geeksforgeeks.org/c-partially-object-oriented-language/
● https://www.geeksforgeeks.org/java-not-purely-object-oriented-language/
● http://www.marcocantu.com/papers/ooplang.htm

6
● https://www.developer.com/net/csharp/article.php/3714896/Comparing-Object-
Oriented-Languages.htm
● https://www.c-sharpcorner.com/blogs/full-object-oriented-language-vs-pure-object-
oriented-language1
În orice clasă, indiferent de limbaj, exista două categorii speciale de metode:
● constructori
● destructori
Constructorii sunt metodele utilizate atunci când sunt create obiectele din clasa respectivă.
Destructorii sunt metodele utilizate atunci când sunt distruse obiectele din clasa respectivă.
Constructori
Constructorii au următoarele caracteristici:
● Au același nume cu clasa din care fac parte;
● Nu pot fi apelați (de fapt nu ar trebui să fie apelați) în mod explicit de către programator;
● Nu returnează nimic, nici măcar void;
● Sunt apelați automat în momentul în care este instanțiat (creat) un obiect din clasa
respectivă;
● Dacă un programator nu scrie constructorii pentru o anumită clasă, în majoritatea
limbajelor orientate pe obiecte, compilatorul crează automat constructorii esențiali
pentru funcționarea clasei respective (constructorul implicit, constructorul de asignare
/ atribuire, constructorul de copiere, constructorul de mutare). Acești constructori
generați automat sunt invizibili pentru programator. Mai multe detalii pot fi găsite în
rezumatul de la sfârșitul Tutorialului 19;
● Într-o clasă pot exista mai mulți constructori dar aceștia trebuie să difere între ei fie prin
tipul, fie prin numărul parametrilor primiți;
● La crearea unui obiect va fi selectat automat acel constructor care corespunde ca număr
și tip de parametrii cu situația respectivă.
Destructori
Destructorii au următoarele caracteristici:
● Au același nume cu numele clasei dar prefixat cu simbolul tilda(~);
● Nu pot fi apelați (de fapt nu ar trebui să fie apelați) în mod explicit de către programator;
● Nu au parametri;
● Nu returnează nimic, nici măcar void;
● În fiecare clasa exista un singur destructor;
● Sunt apelați automat în momentul în care este distrus un obiect din clasa respectivă;

7
● Dacă un programator nu scrie destructorul pentru o anumită clasă, în majoritatea
limbajelor orientate pe obiecte, compilatorul crează automat destructorul necesar pentru
funcționarea clasei respective. Acest destructor generat automat este invizibil pentru
programator. Mai multe detalii pot fi găsite în rezumatul de la sfârșitul Tutorialului 19.
Nivele de acces
Pentru a putea implementa principiul încapsulării, trebuie sa existe o modalitate de protejare a
atributelor și metodelor unei clase împotriva accesului neautorizat.
În majoritatea limbajelor orientate pe obiecte, aceasta protejare se face specificând pentru
atributul sau metoda respectivă nivelul de acces.
În majoritatea limbajelor orientate pe obiecte există trei nivele de acces:
● public - atributele și metodele marcate ca fiind public sunt accesibile din exteriorul
clasei respective;
● protected - atributele și metodele marcate ca fiind protected sunt accesibile din
exteriorul clasei respective, dar numai de către obiectele dintr-o clasă derivată direct
sau indirect din clasa respectivă;
● private - atributele și metodele marcate ca fiind private nu sunt accesibile din exteriorul
clasei respective.
Obs. În C++, dacă nu se precizează alt nivel de acces pentru atributele și metodele unei clase,
nivelul de acces implicit este "private".
Obs. Cel puțin una sau mai multe metode ale unei clase trebuie să fie totuși neprotejate (nivel
de acces public) pentru a putea lucra cu obiectele din clasa respectivă. De exemplu, în marea
majoritate a cazurilor, constructorii trebuie sa fie publici (există și situații particulare în care
nu este nevoie).
Pentru a asigura accesul din exterior la atribute, fără a încălca principiul încapsulării (deci fără
a face atributele publice pentru a le accesa direct), atunci când se dorește acest lucru, se
recomandă utilizarea unor metode specializate pentru acest scop:
● Metode care să citească valoarea atributelor și să le returneze în exterior. Sunt numite,
în funcție de limbaj, accesori sau getter-i;
● Metode care permit modificarea valorilor atributelor. Sunt numite, în funcție de limbaj,
mutatori sau setter-i.
Obs. Setter-ii și getter-ii ar trebui să fie publici, în majoritatea cazurilor (există și situații
particulare în care nu este nevoie).

8
Secțiunea 02

Tutorialul 01 - Constructori impliciți și expliciți, destructori.


Acest prim exemplu demonstrează modul în care se scriu și funcționează constructorii și
destructorii în C++. Exemplul conține trei constructori, ilustrând astfel și supraîncărcarea
constructorilor.
Def. supraîncărcarea unei funcții = operația prin care una și aceeași funcție este definită de mai
multe ori într-un program. Variantele funcției diferă între ele prin numărul și / sau tipul
parametrilor. La execuția propriu-zisă va fi selectată automat acea variantă a funcției care
corespunde prin număr și tip de parametri cu acel context în care se execută funcția.
Constructorii sunt cazuri particulare de metode, care, la rândul lor, sunt cazuri particulare de
funcții, așa încât este posibilă și supraîncărcarea constructorilor.
Obs. Nu trebuie făcută confuzie între supraîncărcarea unei funcții (function overloading) și
suprascrierea unei funcții (function overwriting). Se face supraîncărcare atunci când definește
de mai multe ori o funcție cu număr și / sau tip de parametri diferit de fiecare dată. Rezultatul
supraîncărcării este un set de funcții cu același nume dar având comportament diferite, în
funcție de numărul și tipul parametrilor furnizați. Se face suprascriere atunci când se definește
de mai multe ori o funcție, cu același număr și tip de parametri. Rezultatul va fi o singură
funcție, cea care a fost definită ultima (toate celelalte forme anterioare ale sale vor fi
suprascrise).
Def. constructor implicit = constructor fără parametri. Se folosește pentru crearea unui obiect
al cărui conținut (valori ale atributelor) nu este cunoscut încă. Scopul său este acordare a unor
valori inițiale neutre atributelor (ex. 0 pentru atribute numerice, string vid pentru atribute de tip
string etc.). Într-o clasă există un singur constructor implicit.
Def. constructor explicit = constructor cu unul sau mai mulți parametri. Se folosește pentru
crearea unui obiect al cărui conținut (valori ale atributelor) este cunoscut inițial (total sau
parțial). Într-o clasă pot exista mai mulți constructori expliciți, diferiți între ei prin numărul și
/ sau tipul parametrilor (a se vedea mai sus supraîncărcarea funcțiilor). Scopul constructorului
explicit este acordarea unor valori inițiale bine definite atributelor pentru care s-a furnizat un
parametru și respectiv unor valori inițiale neutre atributelor pentru care nu s-a furnizat un
parametru (dacă este cazul). Într-o clasă pot exista mai mulți constructori expliciți.

#include <iostream>

using namespace std;

class Student
{
string nume, prenume, nr_matricol;
public:
//constructorul implicit
Student()

9
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul implicit"<<endl;
nume="";
prenume="";
nr_matricol="";
}

//un constructor explicit


Student(string nume, string prenume, string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 3 parametri"<<endl;
//cuvantul cheie this identifica obiectul curent
//spre exemplu, aici il folosesc pentru a face distinctie intre
//atributul nume al obiectului curent si parametrul nume primit de constructor
this->nume=nume;
this->prenume=prenume;
this->nr_matricol=nr_matricol;
}

//alt constructor explicit


Student(string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 1 parametru"<<endl;
this->nume="";
this->prenume="";
this->nr_matricol=nr_matricol;
}

//destructorul
~Student()
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//destructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat destructorul"<<endl;
}
};

int main()
{
//aici se va executa primul constructor, cel implicit, pentru că nu am furnizat
//parametri
Student s1;
//aici se va executa al doilea constructor, cel cu trei parametri,
//pentru că am furnizat trei parametri
Student s2("Ionescu","Ion","200");
//aici se va executa al treilea constructor, cel cu un parametru,
//pentru că am furnizat un parametru

10
Student s3("201");
//aici se vor executa destructorii, pentru că cele trei obiecte au fost create local,
//la nivelul funcției main și atunci vor fi distruse la terminarea execuției acesteia
//(o variabilă este distrusă automat la terminarea secvenței în care a fost declarată)
return 0;
}

Obs. Cuvântul cheie this poate fi folosit în diverse scopuri:


● pentru a face distincție între o variabilă locală sau un parametru și un membru cu același
nume;
● pentru a returna o referința la obiectul apelant;
● pentru a face apeluri de funcții în lanț.
Pentru a vedea toate cele trei moduri de lucru cu this, urmăriți exemplul următor:

#include<iostream>
using namespace std;

class Test
{
private:
int x;
int y;
public:
Test(int x = 0, int y = 0)
{
//this folosit pentru a face distinctie intre un parametru si un membru cu acelasi
//nume
this->x = x;
this->y = y;
}

Test &set_x(int a)
{
x = a;
//this folosit pentru a returna o referinta la obiectul curent
return *this;
}

Test &set_y(int b)
{
y = b;
return *this;
}

void print()
{
cout << "x = " << x << endl << "y = " << y << endl;
}
};

int main()
{
Test obj1(5, 5);

11
//this folosit pentru apeluri de functii in lant. Toate apelurile se refera la acelasi
//obiect
obj1.set_x(10).set_y(20);
obj1.print();
return 0;
}

Obs. În introducere am specificat faptul că destructorii și constructorii sunt apelați automat și


nu ar trebui să fie apelați explicit de către programator. Dacă totuși se face acest lucru, efectele
pot fi nedorite:

#include <iostream>
using namespace std;

class Test
{
public:
Test()
{
cout << "S-a executat constructorul" << endl;
}
~Test()
{
cout << "S-a executat destructorul" << endl;
}
};

int main()
{
//o apelare explicita a constructorului. va crea un obiect temporar
//care va fi imediat si distrus
Test();
//o apelare implicita a constructorului. va crea un obiect permanent
Test t;
//o apelare explicita a destructorului pentru obiectul t
t.~Test();
//destructorul pentru obiectul t va fi apelat inca o data, pentru ca,
//nefiind ok ca programatorul sa apeleze explicit destructorul,
//compilatorul nu se asteapta la asa ceva si il executa si el implicit
return 0;
}

În exemplul de mai sus s-a văzut că apelarea explicită a constructorului este o metodă validă
pentru crearea de obiecte temporare, atunci când acest lucru este necesar.
Singura situație în care trebuie aplicat explicit destructorul este atunci când obiectul care
urmează să fie distrus a fost plasat într-o anumită locație în memorie, folosind operatorul new.

Întrebări de autoevaluare
1. Care dintre următoarele funcții supraîncărcate NU sunt permise în C++?
1) Declarații de funcție care diferă numai prin tipul de date returnat:

12
int fun (int x, int y);
void fun (int x, int y);
2) Funcții care diferă numai prin adăugarea cuvântului cheie static la tipul returnat:
int fun (int x, int y);
static int fun (int x, int y);
3) Declarații de parametri care diferă doar într-un indicator * față de un tablou []
int fun (int * ptr, int n);
int fun (int ptr [], int n);
4) Două declarații de parametri care diferă numai prin argumentele lor implicite:
int fun (int x, int y);
int fun (int x, int y = 10);
A. Toate cele de mai sus
B. Toate cu excepția 2)
C. Toate cu excepția 1)
D. Toate cu excepția 2) și 4)

2. La ce folosește cuvântul cheie this?


A. pentru a face distincție între o variabilă locală sau un parametru și un membru cu același
nume
B. pentru a returna o referința la obiectul apelant
C. pentru a face apeluri de funcții în lanț
D. toate cele de mai sus

Răspunsuri la întrebări
1. A. Toate cele de mai sus - supraîncărcarea unei funcții = operația prin care una și aceeași
funcție este definită de mai multe ori într-un program. Variantele funcției diferă între ele prin
numărul și / sau tipul parametrilor.
2. D. toate cele de mai sus - cuvântul cheie this poate fi folosit:
● pentru a face distincție între o variabilă locală sau un parametru și un membru cu același
nume;
● pentru a returna o referința la obiectul apelant;
● pentru a face apeluri de funcții în lanț.

Tutorialul 02 - Exemplul anterior completat cu setter-i și getter-i


Acest exemplu preia exemplul anterior și îl completează cu setter-i și getter-i, demonstrând
modul în care se scriu și se utilizează aceștia.
Def. setter = metodă care are ca scop atribuirea unei valori către un atribut al obiectului. Setter
este o denumire specifică jargonului programării orientate pe obiecte, utilizată în special în aria
limbajului C++. Pentru Java este utilizată denumirea echivalentă de mutator.

13
Def. getter = metodă care are ca scop obținerea valorii unui atribut al obiectului. Getter este o
denumire specifică jargonului programării orientate pe obiecte, utilizată în special în aria
limbajului C++. Pentru Java este utilizată denumirea echivalentă de accesor.

#include <iostream>

using namespace std;

class Student
{
string nume, prenume, nr_matricol;
public:
//constructorul implicit
Student()
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul implicit"<<endl;
nume="";
prenume="";
nr_matricol="";
}

//un constructor explicit


Student(string nume, string prenume, string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 3 parametri"<<endl;
//Cuvantul cheie this identifica obiectul curent.
//Spre exemplu, aici il folosesc pentru a face distinctie intre
//atributul nume al obiectului curent si parametrul nume primit de constructor
this->nume=nume;
this->prenume=prenume;
this->nr_matricol=nr_matricol;
}

//alt constructor explicit


Student(string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 1 parametru"<<endl;
this->nume="";
this->prenume="";
this->nr_matricol=nr_matricol;
}

//destructorul
~Student()
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un

14
//destructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat destructorul"<<endl;
}

//getter-i si setter-i
//(sau accesori si mutatori)

//getter pentru nume


string get_nume()
{
return nume;
}

//getter pentru prenume


string get_prenume()
{
return prenume;
}

//getter pentru nr_matricol


string get_nr_matricol()
{
return nr_matricol;
}

//setter pentru nume


void set_nume(string nume)
{
this->nume=nume;
}

//setter pentru prenume


void set_prenume(string prenume)
{
this->prenume=prenume;
}

//setter pentru nr_matricol


void set_nr_matricol(string nr_matricol)
{
this->nr_matricol=nr_matricol;
}
};

int main()
{
//aici se va executa primul constructor, cel implicit, pentru că nu am furnizat
//parametri
Student s1;
//aici se va executa al doilea constructor, cel cu trei parametri,
//pentru că am furnizat trei parametri
Student s2("Ionescu","Ion","200");
//aici se va executa al treilea constructor, cel cu un parametru,
//pentru că am furnizat un parametru
Student s3("201");
//demonstrez utilizarea getter-ilor

15
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s1 se numeste "<<s1.get_nume()<<" "
<<s1.get_prenume()<<" si are nr. matricol "
<<s1.get_nr_matricol()<<endl;
//afisarea informatiilor continute de obiectul s2:
cout<<"Studentul s2 se numeste "<<s2.get_nume()<<" "
<<s2.get_prenume()<<" si are nr. matricol "
<<s2.get_nr_matricol()<<endl;
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s3 se numeste "<<s3.get_nume()<<" "
<<s3.get_prenume()<<" si are nr. matricol "
<<s3.get_nr_matricol()<<endl;
//demonstrez utilizarea setter-ilor
s1.set_nume("Popescu");
s1.set_prenume("Marcela");
s1.set_nr_matricol("199");
s3.set_nume("Georgescu");
s3.set_prenume("Teodora");
//folosesc din nou getter-i pentru a vedea daca setter-i au avut efect
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s1 se numeste "<<s1.get_nume()<<" "
<<s1.get_prenume()<<" si are nr. matricol "
<<s1.get_nr_matricol()<<endl;
//afisarea informatiilor continute de obiectul s2:
cout<<"Studentul s2 se numeste "<<s2.get_nume()<<" "
<<s2.get_prenume()<<" si are nr. matricol "
<<s2.get_nr_matricol()<<endl;
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s3 se numeste "<<s3.get_nume()<<" "
<<s3.get_prenume()<<" si are nr. matricol "
<<s3.get_nr_matricol()<<endl;
//aici se vor executa destructorii, pentru că cele trei obiecte au fost create local,
//la nivelul funcției main și atunci vor fi distruse la terminarea execuției acesteia
//(o variabilă este distrusă automat la terminarea secvenței în care a fost declarată)
return 0;
}

Tutorialul 03 - Exemplul anterior completat cu o metoda to_string


Acest exemplu preia exemplul anterior și îl completează cu o metodă to_string, demonstrând
modul în care se scrie și se utilizează aceasta.
O modalitate des utilizata (mai puțin în C++, mai mult în alte limbaje orientate pe obiecte -
Java etc.) de afișare a conținutului unui obiect este scrierea unei funcții to_string (toString sau
ToString în diverse limbaje) care să returneze întreg conținutul obiectului într-un singur string.
Acest mod de lucru simplifică semnificativ afișarea și alte operații.

#include <iostream>

using namespace std;

class Student
{
string nume, prenume, nr_matricol;
public:

16
//constructorul implicit
Student()
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul implicit"<<endl;
nume="";
prenume="";
nr_matricol="";
}

//un constructor explicit


Student(string nume, string prenume, string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 3 parametri"<<endl;
//cuvantul cheie this identifica obiectul curent
//spre exemplu, aici il folosesc pentru a face distinctie intre
//atributul nume al obiectului curent si parametrul nume primit de constructor
this->nume=nume;
this->prenume=prenume;
this->nr_matricol=nr_matricol;
}

//alt constructor explicit


Student(string nr_matricol)
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//constructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat constructorul explicit cu 1 parametru"<<endl;
this->nume="";
this->prenume="";
this->nr_matricol=nr_matricol;
}

//destructorul
~Student()
{
//am pus mesajul aici doar pentru a arăta cum și când se execută automat un
//destructor
//în mod normal nu se pune un astfel de mesaj
cout<<"S-a executat destructorul"<<endl;
}

//getter-i si setter-i
//(sau accesori si mutatori)

//getter pentru nume


string get_nume()
{
return nume;
}

17
//getter pentru prenume
string get_prenume()
{
return prenume;
}

//getter pentru nr_matricol


string get_nr_matricol()
{
return nr_matricol;
}

//setter pentru nume


void set_nume(string nume)
{
this->nume=nume;
}

//setter pentru prenume


void set_prenume(string prenume)
{
this->prenume=prenume;
}

//setter pentru nr_matricol


void set_nr_matricol(string nr_matricol)
{
this->nr_matricol=nr_matricol;
}

//metoda to_string (a se vedea mai jos modul în care poate fi utilizată pentru afișare)
string to_string()
{
return nume+" "+prenume+", nr. matricol "+nr_matricol;
}
};

int main()
{
//aici se va executa primul constructor, cel implicit, pentru că nu am furnizat
//parametri
Student s1;
//aici se va executa al doilea constructor, cel cu trei parametri,
//pentru că am furnizat trei parametri
Student s2("Ionescu","Ion","200");
//aici se va executa al treilea constructor, cel cu un parametru,
//pentru că am furnizat un parametru
Student s3("201");
//demonstrez utilizarea getter-ilor
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s1: "<<s1.to_string()<<endl;
//afisarea informatiilor continute de obiectul s2:
cout<<"Studentul s2: "<<s2.to_string()<<endl;
//afisarea informatiilor continute de obiectul s3:
cout<<"Studentul s3: "<<s3.to_string()<<endl;
//demonstrez utilizarea setter-ilor
s1.set_nume("Popescu");

18
s1.set_prenume("Marcela");
s1.set_nr_matricol("199");
s3.set_nume("Georgescu");
s3.set_prenume("Teodora");
//folosesc din nou getter-i pentru a vedea daca setter-i au avut efect
//afisarea informatiilor continute de obiectul s1:
cout<<"Studentul s1: "<<s1.to_string()<<endl;
//afisarea informatiilor continute de obiectul s2:
cout<<"Studentul s2: "<<s2.to_string()<<endl;
//afisarea informatiilor continute de obiectul s3:
cout<<"Studentul s3: "<<s3.to_string()<<endl;
//aici se vor executa destructorii, pentru că cele trei obiecte au fost create local,
//la nivelul funcției main și atunci vor fi distruse la terminarea execuției acesteia
//(o variabilă este distrusă automat la terminarea secvenței în care a fost declarată)
return 0;
}

Tutorialul 04 - Metode de inițializare a obiectelor


În C++ sunt disponibile mai multe metode de inițializare a variabilelor (și a obiectelor -
obiectele nu sunt altceva decât niște cazuri particulare de variabile, al căror tip de date este o
clasă).
Def. inițializare = operația prin care se atribuie unei variabile o valoare inițială
Metode de inițializare:
● Inițializarea funcțională - crearea obiectului arată ca un apel de funcție
● Inițializarea prin atribuire / asignare - crearea obiectului arată ca o operație obișnuită
de atribuire. Dacă obiectul are un singur atribut, asignarea poate fi scrisă simplificat sau
complet. Dacă obiectul are mai multe atribute, asignarea trebuie scrisă complet (a se
vedea în exemplu)
● Inițializarea uniformă - la crearea obiectului, valorile atributelor viitorului obiect sunt
furnizate sub forma unei liste închisă între acolade. Acest mod de lucru simplifică codul
necesar pentru crearea unor obiecte mai particulare, cum ar fi containerele de tip vector
sau listă. De asemenea, simplifică agregarea valorilor pentru obiectele având structură
mai complexă (ex. obiecte conținând alte obiecte). Nu poate fi utilizată decât începând
cu varianta de C++ standardizată în 2003 (nu funcționează în varianta de C++
standardizată în 1998).
● Inițializarea POD-like - ca aparență, este o combinație între inițializarea prin asignare
și cea uniformă. Are aceleași caracteristici și avantaje cu inițializarea uniformă. POD
este prescurtarea denumirii Plain Old Data. Se folosește această denumire pentru că
această metodă de inițializare arată la fel cu o metodă de inițializare a vectorilor
disponibilă în limbajele de programare mai vechi.
Obs. Există multiple variante de C++, standardizate la diverse momente în timp (1998, 2003,
2011, 2014, 2017 și se lucrează la versiunea pentru 2020).
Câteva informații despre aceste versiuni pot fi găsite aici:
https://www.tutorialspoint.com/Different-Cplusplus-Versions

19
Organizația care se ocupă de standardizarea limbajului C++ se numește Standard C++
Foundation (https://isocpp.org).
Diversele versiuni de C++ sunt denumite prescurtat C++98, C++03 (sau C++0x), C++11,
C++14, C++17 și C++20.
În particular, în CodeBlocks, dacă la instalare a fost instalat și compilatorul de C++ (varianta
recomandată), atunci versiunea cea mai recentă de C++ disponibilă în CodeBlocks-ul respectiv
depinde de cât de vechi este CodeBlocks-ul respectiv (în unele cazuri, doar C++98, în altele
C++98 și C++0x, în altele C++98, C++0x și C++11 și în sfârșit, în versiunile foarte noi de
CodeBlocks, C++98, C++0x, C++11 și C++14). Comutarea între versiuni se face din meniul
Settings, opțiunea Compiler:

#include <iostream>

using namespace std;

//o clasa cu un singur atribut


class Patrat
{
double latura;
public:
//un singur constructor explicit
Patrat(double latura)
{
this->latura=latura;

20
}
};

//o clasa cu mai multe atribute


class Dreptunghi
{
double lungime, latime;
public:
//un singur constructor explicit
Dreptunghi(double lungime, double latime)
{
this->lungime=lungime;
this->latime=latime;
}
};

int main()
{
//initializarea functionala a unui obiect
Patrat p1(10);
Dreptunghi d1(20,5);

//initializarea prin asignare (atribuire) a unui obiect


//forma prescurtata
Patrat p2a=10;
//sau, altfel, forma completa
Patrat p2b=Patrat(10);
//pentru obiecte cu mai multe atribute nu se poate utiliza decât forma completa
Dreptunghi d2=Dreptunghi(20,5);

//initializarea uniforma a unui obiect


//nu functioneaza in versiuni vechi de C++, cum ar fi C++98
Patrat p3{10};
Dreptunghi d3{20,5};

//initializarea in stil POD (POD-like) a unui obiect


//POD = Plain Old Data
//nu functioneaza in versiuni vechi de C++, cum ar fi C++98
Patrat p4={10};
Dreptunghi d4={20,5};
return 0;
}

21
Secțiunea 03

Tutorialul 05 - Modul de utilizare a operatorului de rezoluție


Metodele conținute în clase, fiind doar cazuri particulare de funcții, beneficiază de toate
facilitățile care pot fi utilizate la funcțiile obișnuite. Una dintre aceste facilități este posibilitatea
de a scrie la un moment dat doar declarația unei funcții (antetul acesteia), urmând ca definiția
acesteia să se facă ulterior. Acest lucru are sens atunci când încercăm să facem mai ușor de
înțeles un program de întindere mai mare, moment în care putem opta pentru fragmentarea cât
mai mare a codului în bucăți mai ușor de înțeles.
Un exemplu (o scurtă recapitulare) de utilizare a declarației și definiției unei funcții:

#include <iostream>

using namespace std;

//Declaratia unei functii care va returna triplul unui numar.


//Se observa ca in declaratie nu este neaparat necesara (poate fi sarita)
//specificarea denumirilor parametrilor.
//Definirea propriu-zisa a functiei se poate face mai tarziu
//(nu conteaza in ce locatie)
double triplu(double);

int main()
{
//functia a fost deja declarata, asa ca poate fi folosita
//chiar daca definirea sa se va face mai tarziu
cout<<"Triplul lui 1.23 este "<<triplu(1.23);
return 0;
}

//Definitia functiei declarate anterior


double triplu(double x)
{
return 3*x;
}

Precum menționam anterior, separarea în două bucăți (declarație și definiție) se poate face și
cu o metodă (o funcție care face parte dintr-o clasă), doar că, în acest caz, trebuie menționată
și clasa din care face parte funcția. Pentru acest lucru se utilizează operatorul de rezoluție
(eng. scope resolution operator) :: precum se va vedea în exemplul de mai jos. Exemplul
ilustrează, prin comparație, faptul că definițiile metodelor pot fi scrise atât în interiorul
definiției clasei (rezultând o definiție de clasă mai voluminoasă) cât și în afara acesteia, lăsând
doar declarațiile metodelor în interior (caz în care vom avea o definiție de clasă mai redusă ca
dimensiune, dar va trebui să utilizăm operatorul de rezoluție).

#include <iostream>

using namespace std;

22
//in clasa fruct nu vom utiliza operatorul de rezolutie
//(definitiile metodelor vor fi scrise in interiorul definitiei clasei)
class Fruct
{
string denumire;
public:
//definitia constructorului implicit
Fruct(){}

//definitia unui constructor explicit


Fruct(string denumire)
{
this->denumire=denumire;
}

//definitia unui setter


void set_denumire(string denumire)
{
this->denumire=denumire;
}

//definitia unui getter


string get_denumire()
{
return denumire;
}
};

//in clasa Leguma vom utiliza operatorul de rezolutie la toate metodele


//(in definitia clasei vor fi scrise doar declaratiile metodelor,
//urmand ca definitiile lor sa fie scrise undeva in exterior)
//Se observa cat de simpla si usor de inteles devine definitia clasei
//Leguma, odata ce definitiile metodelor au fost inlocuite cu declaratii,
//urmand ca definitiile sa fie date ulterior
class Leguma
{
string denumire;
public:
//declaratia constructorului implicit
Leguma();

//declaratia unui constructor explicit


Leguma(string);

//declaratia unui setter


void set_denumire(string);

//declaratia unui getter


string get_denumire();
};

int main()
{
//exemplu de utilizare a ambilor constructori din clasa Fruct
Fruct a, b("mar");
//exemplu de utilizare a ambilor constructori din clasa Leguma

23
Leguma c, d("castravete");
//exemple de utilizare a setter-ilor
a.set_denumire("pruna");
c.set_denumire("rosie");
//exemple de utilizare a getter-ilor
cout<<"Fructe: "<<a.get_denumire()<<" si "<<b.get_denumire()<<endl;
cout<<"Legume: "<<c.get_denumire()<<" si "<<d.get_denumire()<<endl;
return 0;
}

//Acele metode / functii care au fost declarate anterior vor trebui


//si definite. Nu este relevant cat de departe sau unde se va face
//definirea, conteaza doar sa fie facuta

//Definitia constructorului implicit al clasei Leguma care


//a fost declarat anterior in definitia clasei.
//Se observa modul de utilizare a operatorului de rezolutie pentru
//a preciza ca avem de-a face cu metoda (constructorul) Leguma din
//clasa Leguma.
Leguma::Leguma(){}

//Definitia unui constructor explicit al clasei Leguma


//care a fost declarat anterior in definitia clasei.
//Se observa modul de utilizare a operatorului de rezolutie pentru
//a preciza ca avem de-a face cu metoda (constructorul) Leguma din
//clasa Leguma.
Leguma::Leguma(string denumire)
{
this->denumire=denumire;
}

//Definitia unui setter al clasei Leguma


//care a fost declarat anterior in definitia clasei.
//Se observa modul de utilizare a operatorului de rezolutie pentru
//a preciza ca avem de-a face cu metoda set_denumire din
//clasa Leguma.
void Leguma::set_denumire(string denumire)
{
this->denumire=denumire;
}

//Definitia unui getter al clasei Leguma


//care a fost declarat anterior in definitia clasei.
//Se observa modul de utilizare a operatorului de rezolutie pentru
//a preciza ca avem de-a face cu metoda get_denumire din
//clasa Leguma.
string Leguma::get_denumire()
{
return denumire;
}

Tutorialul 06 - Modalități de inițializare a atributelor în constructori

O facilitate disponibilă în C++ este abilitatea de a inițializa (de a da valori) atributelor atât în
corpul constructorului (modul clasic de lucru) cât și în antetul constructorului (abilitate

24
indisponibilă pentru lucrul cu funcțiile obișnuite, dar care permite obținerea unor constructori
mai compacți, cu mai puțin cod). Exemplul următor ilustrează, folosind trei clase foarte
asemănătoare ca structură, cele două moduri de lucru, precum și un mod combinat:

#include <iostream>

using namespace std;

//Trei clase aproape identice, exceptand denumirea


//pentru a ilustra cele trei moduri in care pot fi
//initializate atributele in constructori.
class Autovehicul
{
//pentru a usura comparatia, atributele sunt aceleasi in cele trei clase
string marca, tip;
public:
//In fiecare clasa un singur constructor explicit
//(cel implicit nu serveste la nimic in exemplul de fata asa ca nu va fi scris).
//Aici se exemplifica initializarea atributelor in corpul constructorului
//(modul clasic de lucru).
Autovehicul(string marca, string tip)
{
this->marca=marca;
this->tip=tip;
}

//o metoda to_string pentru afisare rapida


string to_string()
{
return marca+" "+tip;
}
};

class Barca
{
//pentru a usura comparatia, atributele sunt aceleasi in cele trei clase
string marca, tip;
public:
//In fiecare clasa un singur constructor explicit
//(cel implicit nu serveste la nimic in exemplul de fata asa ca nu va fi scris).
//Aici se exemplifica initializarea atributelor in antetul constructorului
//(un mod mai compact de a scrie un constructor).
//O instrucțiune de forma marca(marca) înseamnă ca atributul marca ia valoarea
//furnizată prin parametrul marca.
//Intre acolade nu se mai afla nimic, asa ca acest constructor, in mod foarte vizibil,
//nu face nimic altceva decat initializarea atributelor.
Barca(string marca, string tip): marca(marca), tip(tip) {}

//o metoda to_string pentru afisare rapida


string to_string()
{
return marca+" "+tip;
}
};

class Avion

25
{
//pentru a usura comparatia, atributele sunt aceleasi in cele trei clase
string marca, tip;
public:
//In fiecare clasa un singur constructor explicit
//(cel implicit nu serveste la nimic in exemplul de fata asa ca nu va fi scris).
//Aici se exemplifica initializarea mixta a atributelor
//(o parte din atribute sunt initializate in antetul constructorului si
//cealalta parte in corpul constructorului).
//Nu are foarte mult sens abordarea acestui mod de lucru - nu duce la obtinerea
//unui cod mai usor inteligibil sau mai compact, dar … e posibil. :)
Avion(string marca, string tip): marca(marca)
{
this->tip=tip;
}

//o metoda to_string pentru afisare rapida


string to_string()
{
return marca+" "+tip;
}
};

int main()
{
//Se observa ca este irelevant cum a fost facuta initializarea atributelor
//in fiecare constructor, acesta se utilizeaza la fel:
Autovehicul a("Ford","Ranger");
Barca b("Baja","24 Outlaw");
Avion c("Lockheed Martin","F-22 Raptor");
//afisari
cout<<"O masina: "<<a.to_string()<<endl;
cout<<"O barca: "<<b.to_string()<<endl;
cout<<"Un avion: "<<c.to_string()<<endl;
return 0;
}

Tutorialul 07 - Clase cu atribute alocate dinamic


Atributele unei clase, fiind practic doar cazuri particulare de variabile, beneficiază de aceleași
facilități ca și variabilele obișnuite. Pot spre exemplu să fie alocate static, așa cum am văzut în
exemplele anterioare, sau dinamic, precum vom vedea în exemplul următor.
Ne aducem aminte că utilizăm alocarea dinamică a memoriei pentru variabile atunci când avem
de-a face cu cantități mari de date sau pentru a flexibiliza utilizarea memoriei. Utilizarea
alocării dinamice în locul celei statice pune practic în sarcina și responsabilitatea
programatorului operațiile de alocare și eliberare a memoriei, cu toate avantajele, dar și
riscurile care decurg din acest lucru (cod suplimentar și mai complex, posibilitatea de a scăpa
din vedere o operație etc.).
Obs. În particular, atunci când variabilele alocate dinamic sunt atribute ale unei clase, cel mai
bun loc pentru a face alocarea memoriei este în constructori și cel mai bun loc pentru a face
eliberarea memoriei este în destructor.

26
Obs. Un fenomen particular legat de utilizarea variabilelor alocate dinamic este memory
leakage (scurgerile de memorie). O scurgere de memorie este un tip de pierdere de resurse care
apare atunci când un program gestionează incorect alocările de memorie astfel încât memoria
care nu mai este necesară nu este eliberată. Mai multe informații despre memory leakage pot
fi obținute de aici: https://en.wikipedia.org/wiki/Memory_leak

#include <iostream>

using namespace std;

class Tabara
{
//Atributul denumire este un string alocat dinamic.
string * denumire;
int nr_studenti;
//Atributul lista_studenti este un vector de stringuri alocat dinamic.
//Ne aducem aminte ca la declarare nu exista nicio diferenta intre
//o variabila simpla alocata dinamic si un vector alocat dinamic
string * lista_studenti;
public:
//Nu este necesar in acest exemplu decat un constructor explicit.
Tabara(string denumire, int nr_studenti, string lista_studenti[])
{
//In cazul in care o clasa contine atribute alocate dinamic, in C++,
//programatorul trebuie sa aiba grija sa faca alocarea memoriei, spre exemplu
//in constructor.
//Alocarea memoriei pentru denumire. Facem simultan si atribuirea
//valorii primite prin parametru.
this->denumire=new string(denumire);
this->nr_studenti=nr_studenti;
//Alocarea memoriei pentru lista de studenti. Nu mai e un singur string
//asa ca nu mai merge sa si atribuim valoarea primita prin parametru.
this->lista_studenti=new string[nr_studenti];
//Preluam valorile din vectorul de string-uri primit ca parametru.
for(int i=0;i<nr_studenti;i++)
this->lista_studenti[i]=lista_studenti[i];
}

//getter-i
string get_denumire()
{
//Nu are sens sa returnam pointeri (ne complicam dupa aceea la afisare).
//Returnam direct valori.
return *denumire;
}

int get_nr_studenti()
{
return nr_studenti;
}

//In cazul particular al stringurilor nu este nicio diferenta tehnica


//intre variabila care denumeste vectorul si un pointer catre inceputul
//vectorului.
string * get_lista_studenti()

27
{
return lista_studenti;
}

~Tabara()
{
//In cazul in care o clasa contine atribute alocate dinamic, in C++,
//programatorul trebuie sa aiba grija sa faca eliberarea memoriei, spre exemplu
//in destructor (prevenind astfel aparitia fenomenului de memory leakage).
delete denumire;
delete[] lista_studenti;
}
};

int main()
{
string studenti[]={"Maria","Ionel","Liana","Marcel","Elena"};
Tabara t("Nucsoara",5,studenti);
cout<<"In tabara "<<t.get_denumire()<<" se afla "<<t.get_nr_studenti()<<
"studenti:"<<endl;
for(int i=0;i<t.get_nr_studenti();i++)
cout<<t.get_lista_studenti()[i]<<endl;
return 0;
}

Tutorialul 08 - Obiecte alocate dinamic


Obiectele, dincolo de faptul că sunt instanțe ale unor clase, sunt niște variabile oarecare și pot
fi tratate ca atare. Pot fi, spre exemplu, alocate dinamic (a se face distincție între alocarea
dinamică a obiectelor și alocarea dinamică a atributelor).
Ne aducem aminte că utilizăm alocarea dinamică a memoriei pentru variabile atunci când avem
de-a face cu cantități mari de date sau pentru a flexibiliza utilizarea memoriei. Utilizarea
alocării dinamice în locul celei statice pune practic în sarcina și responsabilitatea
programatorului operațiile de alocare și eliberare a memoriei, cu toate avantajele (utilizarea
mai eficientă și mai flexibilă a memoriei disponibile), dar și riscurile care decurg din acest
lucru (cod suplimentar și mai complex, posibilitatea de a scăpa din vedere o operație etc.).
Exemplul următor demonstrează modul în care se face alocarea dinamică a obiectelor:

#include <iostream>

using namespace std;

//o clasa oarecare


class Carte
{
string titlu, autor;
public:
//Nu este necesar in acest exemplu decat un constructor explicit.
Carte(string titlu, string autor)
{
this->titlu=titlu;
this->autor=autor;

28
}

//getter-i
string get_titlu()
{
return titlu;
}

string get_autor()
{
return autor;
}
};

int main()
{
//Dat fiind ca este vorba despre o variabila alocata dinamic,
//programatorul trebuie sa asigure alocarea memoriei ...
Carte * a=new Carte("Dune","Frank Herbert");
cout<<"Vorbim despre cartea "<<a->get_titlu()<<" de "<<a->get_autor();
//Dat fiind ca este vorba despre o variabila alocata dinamic,
//programatorul trebuie sa asigure eliberarea memoriei ...
delete a;
return 0;
}

Alocarea dinamică a obiectelor și alocarea dinamică a atributelor nu au legătură una cu cealaltă.


Pot fi utilizate separat, sau combinate, după dorința programatorului. Exemplul anterior, în care
am ilustrat alocarea dinamică a obiectelor, la care adăugam și alocarea dinamică a atributelor,
ar arată cam așa:

#include <iostream>

using namespace std;

//o clasa oarecare


class Carte
{
string * titlu, * autor;
public:
//Nu este necesar in acest exemplu decat un constructor explicit.
Carte(string titlu, string autor)
{
//Atributele au fost declarate ca fiind alocate dinamic asa ca suntem obligati
//sa le alocam memorie.
this->titlu=new string(titlu);
this->autor=new string(autor);
}

//getter-i
string get_titlu()
{
//returnam valoarea, nu pointerul
return * titlu;
}

29
string get_autor()
{
//returnam valoarea, nu pointerul
return * autor;
}

~Carte()
{
//Atributele au fost declarate ca fiind alocate dinamic asa ca nu trebuie sa uitam
//sa eliberam memoria care le-a fost alocata.
delete titlu;
delete autor;
}
};

int main()
{
//Dat fiind ca obiectul insusi este o variabila alocata dinamic,
//programatorul trebuie sa asigure alocarea memoriei ...
Carte * a=new Carte("Dune","Frank Herbert");
cout<<"Vorbim despre cartea "<<a->get_titlu()<<" de "<<a->get_autor();
//Dat fiind ca obiectul insusi este o variabila alocata dinamic,
//programatorul trebuie sa asigure eliberarea memoriei ...
delete a;
return 0;

Tutorialul 09 - Modalități alternative de declarare a claselor în C++


În C++, pe lângă cuvântul cheie class, mai există două alte modalități de a crea tipuri de date
de date având aceeași funcționalitate cu clasele (se obțin practic tot clase, dar cu unele elemente
specifice).
Prima modalitate este utilizarea cuvântului cheie struct în locul cuvântului cheie class.
Diferența specifică este faptul că, dacă la utilizarea cuvântului cheie class nivelul de acces
implicit este private, la utilizarea cuvântului cheie struct nivelul de acces implicit este public.

#include <iostream>

using namespace std;

//Folosim cuvantul cheie struct pentru a defini o clasa.


struct Film
{
//Pentru ca am folosit string, nivelul de acces implicit este public.
//Nu am specificat niciun alt nivel de acces asa ca, atat atributele
//cat si constructorul sunt accesibile public (incalcam astfel principiul
//incapsularii dar asta serveste scopului acestui exemplu).
string titlu, gen;
//Un constructor explicit.
Film(string titlu, string gen): titlu(titlu), gen(gen){}
//Nu e nevoie de getter-i sau setter-i. atibutele sunt direct accesibile.
};

int main()
{

30
Film a("Aquaman","fantasy, comics");
//Atributele sunt direct accesibile, asa ca pot fi utilizate ca atare
cout<<"Filmul este "<<a.titlu<<" din categoria "<<a.gen;
return 0;
}

Se observă în exemplu de mai sus că utilizarea cuvântului cheie struct, cu modificarea de nivel
de acces corespunzătoare, are efecte semnificative asupra modului în care se comportă clasa.
Dacă dorim să utilizăm cuvântul cheie struct, dar în același timp să respectăm principiile POO,
exemplul de mai sus ar trebui rescris cam așa:

#include <iostream>

using namespace std;

//Folosim cuvantul cheie struct pentru a defini o clasa.


struct Film
{
//Schimbam nivelul de acces, care era implicit public, in private.
private:
string titlu, gen;
public:
//Un constructor explicit.
Film(string titlu, string gen): titlu(titlu), gen(gen){}

//getter-i
string get_titlu()
{
return titlu;
}

string get_gen()
{
return gen;
}
};

int main()
{
Film a("Aquaman","fantasy, comics");
//Atributele nu mai sunt direct accesibile, asa ca trebuie utilizati getter-ii
cout<<"Filmul este "<<a.get_titlu()<<" din categoria "<<a.get_gen();
return 0;
}

A doua modalitate este utilizarea cuvântului cheie union în locul cuvântului cheie class. Aici
sunt două diferențe specifice. Una este faptul că, dacă la utilizarea cuvântului cheie class
nivelul de acces implicit este private, la utilizarea cuvântului cheie union nivelul de acces
implicit este public. Cea de a doua diferență este dată de faptul că union nu conține practic
decât o singură locație de memorie, locație de memorie care poate fi utilizată în diverse moduri.
Un exemplu mai simplu de utilizare a unei uniuni ca o clasă:

31
#include <iostream>

using namespace std;

//Definim o "clasa" pe care o putem folosi pentru a converti intregi in caractere si invers
//Cu aceasta ocazie vedem si un exemplu de utilizare a cuvantului cheie union.
union Convert
{
//Nivelul de acces implicit este public. Trecem la private
private:
//Uniunile sunt un tip de date particular si mai putin folosit, in care
//datele membre nu sunt asezate una langa cealalta (sau in continuarea
//celeilalte), cum se intampla la struct si class, ci una PESTE cealalta.
//Spre exemplu aici, un intreg pe 4 octeti, unul pe 2 octeti un caracter pe 1 octet
//ocupa acelasi spatiu de memorie, permitand conversii libere intre cele trei tipuri de
//date.
int intreg;
short intreg_scurt;
char caracter;
public:
//Trei constructori expliciti, cate unul pentru fiecare tip de intrare.
Convert(int intreg):intreg(intreg){}

Convert(short intreg_scurt):intreg_scurt(intreg_scurt){}

Convert(char caracter):caracter(caracter){}

//Trei getter-i, cate unul pentru fiecare tip de iesire.


int get_intreg()
{
return intreg;
}

short int get_intreg_scurt()


{
return intreg_scurt;
}

char get_caracter()
{
return caracter;
}
};

int main()
{
Convert a(65);
cout<<"Caracterul care are codul "<<a.get_intreg()<<" este: "<<a.get_caracter();
return 0;
}

Un exemplu puțin mai complex de utilizare a unei uniuni ca o clasă:

#include <iostream>

32
using namespace std;

//Definim o "clasa" pe care o putem folosi pentru a combina sau separa


//valori intregi fara semn de diverse dimensiuni, dupa bunul plac.
//Cu aceasta ocazie vedem si un exemplu de utilizare a cuvantului cheie union.
//Am numit clasa Sep_comb - separare + combinare.
union Sep_comb
{
//Nivelul de acces implicit este public. trecem la private
private:
//Uniunile sunt un tip de date particular si mai putin folosit, in care
//datele membre nu sunt asezate una langa cealalta (sau in continuarea
//celeilalte), cum se intampla la struct si class, ci una PESTE cealalta.
//Spre exemplu aici, intregul pe 4 octeti, cei doi intregi pe 2 octeti si
//respectiv cele 4 caractere de 1 octet ocupa acelasi spatiu de memorie,
//permitand operatii de separare si combinare a datelor.
unsigned int intreg;
unsigned short intregi_scurti[2];
char caractere[4];
public:
//Trei constructori expliciti, cate unul pentru fiecare tip de intrare.
Sep_comb(unsigned int intreg):intreg(intreg){}

Sep_comb(unsigned short intregi_scurti[])


{
for(int i=0;i<2;i++)
this->intregi_scurti[i]=intregi_scurti[i];
}

Sep_comb(char caractere[])
{
for(int i=0;i<4;i++)
this->caractere[i]=caractere[i];
}

//Trei getter-i, cate unul pentru fiecare tip de iesire.


unsigned int get_intreg()
{
return intreg;
}

unsigned short int * get_intregi_scurti()


{
return intregi_scurti;
}

char * get_caractere()
{
return caractere;
}
};

int main()
{
//Sa vedem cum putem utiliza uniunea pentru a sparge o valoare intreaga
//pe 4 octeti, in valori pe 2 octeti si respectiv pe 1 octet ...
Sep_comb a(1198090089);

33
cout<<"Valoarea initiala: "<<a.get_intreg()<<endl;
cout<<"Conversie in 2 intregi scurti: "<<a.get_intregi_scurti()[1]<<
", "<<a.get_intregi_scurti()[0]<<endl;
cout<<"Conversie in 4 caractere: "<<a.get_caractere()[3]<<
", "<<a.get_caractere()[2]<<", "<<a.get_caractere()[1]<<
", "<<a.get_caractere()[0]<<endl;

//Sa vedem cum putem utiliza uniunea pentru a combina 4 caractere


//in valori pe 2 octeti si respectiv pe 4 octeti ...
cout<<endl;
char x[]={'i','m','i','M'};
Sep_comb b(x);
cout<<"Caracterele initiale: "<<b.get_caractere()[3]<<", "<<b.get_caractere()[2]<<
", "<<b.get_caractere()[1]<<", "<<b.get_caractere()[0]<<endl;
cout<<"Conversie in 2 intregi scurti: "<<b.get_intregi_scurti()[1]<<
", "<<b.get_intregi_scurti()[0]<<endl;
cout<<"Conversie intr-un intreg: "<<b.get_intreg()<<endl;

//Sa vedem cum putem utiliza uniunea pentru a combina 2 valori pe doi octeti
//in valori pe 4 octeti si respectiv caractere pe 1 octet ...
cout<<endl;
unsigned short y[]={27765,19573};
Sep_comb c(y);
cout<<"Valorile initiale: "<<c.get_intregi_scurti()[1]<<
", "<<c.get_intregi_scurti()[0]<<endl;
cout<<"Conversie intr-un intreg: "<<c.get_intreg()<<endl;
cout<<"Conversie in 4 caractere: "<<c.get_caractere()[3]<<
", "<<c.get_caractere()[2]<<", "<<c.get_caractere()[1]<<
", "<<c.get_caractere()[0]<<endl;

//Sa vedem cum putem utiliza uniunea pentru a combina 4 valori pe un octet,
//transformate in caractere, in valori pe 2 octeti si respectiv pe 4 octeti ...
cout<<endl;
char z[]={(char)255,(char)255,(char)255,(char)255};
Sep_comb d(z);
cout<<"Caracterele initiale: "<<d.get_caractere()[3]<<", "<<d.get_caractere()[2]<<
", "<<d.get_caractere()[1]<<", "<<d.get_caractere()[0]<<endl;
cout<<"Conversie in 2 intregi scurti: "<<d.get_intregi_scurti()[1]<<
", "<<d.get_intregi_scurti()[0]<<endl;
cout<<"Conversie intr-un intreg: "<<d.get_intreg()<<endl;

return 0;
}

34
Secțiunea 04

Tutorialul 10 - Supraîncărcarea operatorilor


Toți operatorii din C++ (corespunzători operațiilor matematice, logice etc.) sunt de fapt
implementați ca funcții.
O scurtă recapitulare a cunoștințelor despre operatorii disponibili în C++ poate fi lecturată aici:
https://docs.google.com/document/d/1pEvTt7kCIjp2q2RssR01LhPRcbRqjehWVW0S54IBqn
s/edit?usp=sharing
Datorită modalității de implementare, este posibilă supraîncărcarea operatorilor deja existenți
de așa natură încât să fie aplicabili și pentru tipurile de date nou create de către programator
prin intermediul claselor. Următorii operatori pot fi supraîncărcați în C++:

+ - * / = < > += -= *= /= << >> <<= >>= == != <= >= ++


-- % & ^ ! | ~ &= ^= |= && || %= [] () , ->* -> new delete
new[] delete[]

Obs. Nu pot fi supraîncărcați următorii operatori:


1. Operatorul de precizare a domeniului (::)
2. Operatorul de pointer la membri (.*)
3. Operatorul de acces la membri (.)
4. Operatorul ternar sau condițional (?:)
5. Operatorul dimensiune a obiectului (sizeof)
6. Operatorul tip al obiectului (typeid)

Clasele nou create nu au la dispoziție operatori implementați pentru ele, exceptând operatorul
pentru operația de atribuire care este implementat de către compilator, prin intermediul
metodelor operator de atribuire prin copiere și respectiv prin mutare furnizate automat (doar în
anumite condiții; mai multe informații despre acest subiect în rezumatul de la sfârșitul
Tutorialului 19).
Cade în sarcina programatorului să facă, pentru clasele proprii, supraîncărcarea operatorilor
pentru operațiile dorite.

Exemplu de supraîncărcare a operatorului de comparație >

Exemplul de mai jos ilustrează supraîncărcarea operatorului de comparație > pentru o clasă
creată de programator, clasă în care acest operator nu exista anterior.

#include <iostream>

using namespace std;

class Fruct
{

35
//niste atribute
string denumire;
double cantitate, pret_unitar;
public:
//un constructor explicit
Fruct(string denumire, double cantitate, double pret_unitar):
denumire(denumire),
cantitate(cantitate),
pret_unitar(pret_unitar){}

//Supraincarcarea operatorului >


//Folosim cuvantul cheie operator pentru a specifica faptul
//ca este vorba de functia care implementeaza operatorul >
//In acest caz spun ca un obiect este "mai mare" decat un
//alt obiect de tip fruct daca a costat mai mult.
//cost=catitate*pret unitar
//Obiectul de care apartine operatorul va fi primul termen din comparatie
//iar cel primit ca parametru va fi al doilea termen din comparatie.
//!!!Parametrul este transmis prin referinta - transmisia prin valoare ar crea
//o copie a obiectului, dar trebuie sa ma asigur ca nu il modific accidental, asa ca
//il fac constant.
bool operator>(const Fruct& param)
{
return (this->cantitate*this->pret_unitar)>(param.cantitate*param.pret_unitar);
}

//un getter
string get_denumire()
{
return denumire;
}
};

int main()
{
//creez doua obiecte de tip Fruct
Fruct x("pepeni",5,1.5),y("mere",3,3.99);
//le compar folosind operatorul > creat de programator (de fapt supraincarcat)
if(x>y)
{
cout<<x.get_denumire()<<" valoreaza mai mult decat "<<y.get_denumire();
}
else
{
cout<<y.get_denumire()<<" valoreaza mai mult decat "<<x.get_denumire();
}
return 0;
}

Obs. Ne aducem aminte ca operatorul pe care l-am supraîncărcat a fost de fapt o funcție. Astfel
încât poate fi folosit și ca operator, dar și ca funcție, astfel încât
x>y
din codul de mai sus este interschimbabil cu
x.operator>(y)

36
Exemplu de supraîncărcare a operatorului de adunare + ca funcție membră

Obs. În funcție de comportamentul specific al unui operator, acesta poate fi implementat atât
ca metodă a clasei cât și ca funcție non-membră. Cele două programe de mai jos ilustrează
comparativ modul în care se poate implementa operatorul de adunare atât ca funcție membră,
cât și ca funcție non-membră:

Exemplu cu operatorul + implementat ca funcție membră:

#include <iostream>

using namespace std;

//O clasa pentru manipularea numerelor complexe


class Complex
{
//Atribute pentru partea reala si coeficientul partii imaginare
double a,b;
public:
//Un constructor implicit
Complex(){}

//Un constructor explicit


Complex(double a, double b):a(a),b(b){}

//O metoda de afisare


void afisare()
{
if(b>=0)
cout<<a<<"+"<<b<<"*i"<<endl;
else
cout<<a<<b<<"*i"<<endl;
}

//Operatorul + implementat ca metoda a clasei


Complex operator+ (const Complex& param)
{
Complex rez;
rez.a = this->a + param.a;
rez.b = this->b + param.b;
return rez;
}
};

int main()
{
//trei obiecte de tip numar complex,
//doua cu valori cunoscute si unul fara
Complex z1(3,5),z2(-2,-7),z3;
//Afisari
cout<<"z1: ";
z1.afisare();
cout<<"z2: ";
z2.afisare();

37
//Operatia de adunare
z3=z1+z2;
//Afisare
cout<<"z3: ";
z3.afisare();
return 0;
}

Exemplu de supraîncărcare a operatorului de adunare + ca funcție non-membră

#include <iostream>

using namespace std;

//O clasa pentru manipularea numerelor complexe


class Complex
{
//Atribute pentru partea reala si coeficientul partii imaginare
double a,b;
public:
//Un constructor implicit
Complex(){}

//Un constructor explicit


Complex(double a, double b):a(a),b(b){}

//O metoda de afisare


void afisare()
{
if(b>=0)
cout<<a<<"+"<<b<<"*i"<<endl;
else
cout<<a<<b<<"*i"<<endl;
}

//Setteri si getteri pentru cele doua atribute.


//Acum am nevoie de ei pentru ca operatorul de adunare nu mai este metoda a clasei si nu
//mai are acces la atributele clasei
void set_a(double a)
{
this->a=a;
}

double get_a()
{
return a;
}

void set_b(double b)
{
this->b=b;
}

double get_b()
{
return b;

38
}
};

//Operatorul + implementat ca functie non-membra


//In mod normal, cei doi parametri ar trebui transmisi ca referinte constante, asa cum am
//facut la programul anterior, dar pentru asta ar trebui sa lucram cu obiecte constante,
//lucru pe care vom invata sa il facem la Tutorialul 13 :). Eliminam oarecum problema
//transmitand parametrii prin valoare (transmisia prin valoare face ca orice modificari ale
//variabilelor trimise ca si parametru in interiorul functiei sa fie valabile doar in
//interiorul functiei).
Complex operator+ (Complex param1, Complex param2)
{
Complex rez;
double temp;
temp=param1.get_a();
temp+=param2.get_a();
rez.set_a(temp);
rez.set_b(param1.get_b() + param2.get_b());
return rez;
}

int main()
{
//trei obiecte de tip numar complex,
//doua cu valori cunoscute si unul fara
Complex z1(3,5),z2(-2,-7),z3;
//Afisari
cout<<"z1: ";
z1.afisare();
cout<<"z2: ";
z2.afisare();
//Operatia de adunare
z3=z1+z2;
//Afisare
cout<<"z3: ";
z3.afisare();
return 0;
}

Exemplu de supraîncărcare a operatorului de incrementare ++, caz particular de supraîncărcare

Operatorul de incrementare (++), ca și cel de decrementare (--) pot fi aplicați atât ca prefix cât
și ca postfix. Pentru a face diferența la supraîncărcare între operatorul aplicat ca prefix și cel
aplicat ca postfix, la supraîncărcarea operatorului aplicat ca postfix se adaugă un parametru
neutilizat și anonim de tip întreg:

#include <iostream>
using namespace std;

class Supraincarcare {
private:
int valoare;

public:
Supraincarcare(){}

39
Supraincarcare(int i): valoare(i) {}

//operatorul supraincarcat de incrementare prefixata


Supraincarcare operator++()
{
valoare++;
return valoare;
}

//operatorul supraincarcat de incrementare postfixata


Supraincarcare operator++(int)
{
return valoare++;
}

int get_valoare()
{
return valoare;
}
};

int main()
{
Supraincarcare i(5), pre, post;
cout << "valoarea initiala a lui i: " << i.get_valoare() << endl;
//o incrementare prefixata (se face incrementarea lui i si dupa aceea pre ia valoarea
rezultata)
pre = ++i;
cout << "valoarea lui i, dupa incrementarea prefixata: " << i.get_valoare() << endl;
cout << "valoarea lui pre: " << pre.get_valoare() << endl;
//o incrementare postfixata (post ia valoarea lui i si dupa aceea se face incrementarea
lui i)
post = i++;
cout << "valoarea lui i, dupa incrementarea postfixata: " << i.get_valoare() << endl;
cout << "valoarea lui post: " << post.get_valoare() << endl;
return 0;
}

Exemplu de supraîncărcare a operatorilor de inserție (<<) și extracție (>>)

În particular, pentru ușurință în implementare, operatorii de inserție și respectiv de extracție


trebuie supraîncărcați ca funcții non-membre:

#include <iostream>
using namespace std;

class Complex
{
private:
double real, imag;
public:
Complex(double r = 0, double i =0): real(r), imag(i){}

double get_real()

40
{
return real;
}

void set_real(double r)
{
real=r;
}

double get_imag()
{
return imag;
}

void set_imag(double i)
{
imag=i;
}
};

//supraincarcarea operatorului <<


//in mod normal, parametrul de tip Complex, care este trimis ca referinta, ar
//trebui trimis ca o constanta, pentru a nu putea fi modificat accidental in
//functie, dar nu stim inca sa lucram cu obiecte constante, asa ca nu facem acest lucru.
ostream & operator << (ostream &out, Complex &c)
{
out << c.get_real();
out << "+i" << c.get_imag() << endl;
return out;
}

//supraincarcarea operatorului >>


istream & operator >> (istream &in, Complex &c)
{
double r, i;
cout << "Introduceti partea reala: ";
in >> r;
c.set_real(r);
cout << "introduceti coeficientul partii imaginare: ";
in >> i;
c.set_imag(i);
return in;
}

int main()
{
Complex c1;
cin >> c1;
cout << "Numarul complex introdus este: ";
cout << c1;
return 0;
}

41
Tutorialul 11 - Atribute statice
Un atribut al unei clase poate fi declarat ca fiind static. Această declarație face ca acel atribut
să nu mai caracterizeze un singur obiect din clasa respectivă ci clasa în întregime - atributul
static respectiv va avea aceeași valoare în toate obiectele instanțiate din clasa respectivă.
Din acest motiv, atributele statice se mai numesc și variabile ale clasei sau variabile de clasă
(class variables).
Obs. Atributele statice nu pot fi inițializate în interiorul clasei ci este obligatoriu să fie
inițializate în afara ei (va fi necesară utilizarea operatorului de rezoluție prezentat anterior).
Obs. Atributele statice pot fi utilizate atât ca atribute al unui obiect din clasă, cât și ca atribute
ale clasei în sine.
Obs. Atributele statice sunt stocate în afara obiectelor din clasa respectivă, astfel încât pot fi
utilizate chiar atunci când nu există niciun obiect din clasa respectivă. Ca să poată totuși fi
folosite în acest mod, ele trebuie să aibă nivelul de acces public (a se vedea și explicațiile de la
exemplul următor).
Atributele statice sunt utilizate în diverse scopuri precum:
- Asigurarea unei valori comune pentru toate obiectele dintr-o anumită clasă (cum se va
vedea în exemplul următor);
- Transmisia de date între obiectele clasei;
- Transmisia “prin referință” în limbajele în care este interzisă transmisia prin referință
(cum ar fi Java);
- Pot servi drept “variabile globale” în limbajele în care sunt interzise variabilele globale
(din nou Java).

#include <iostream>

using namespace std;

class Produs
{
//Niste atribute
string denumire;
double cantitate, pret_unitar;
public:
//Un atribut static. Se observa utilizarea cuvantului cheie static.
static double tva;
//Un constructor explicit. Se observa faptul ca nu initializez
//atributul static in constructor.
Produs(string denumire, double cantitate, double pret_unitar):
denumire(denumire),
cantitate(cantitate),
pret_unitar(pret_unitar){}

//Niste getter-i
string get_denumire()
{

42
return denumire;
}

double get_cantitate()
{
return cantitate;
}

double get_pret_unitar()
{
return pret_unitar;
}

//Niste falsi getter-i. Returneaza valori dar nu valorile unor atribute ci


//niste valori calculate.
double get_valoare()
{
return cantitate*pret_unitar;
}

double get_valoare_tva()
{
return cantitate*pret_unitar*tva;
}

double get_valoare_cu_tva()
{
return cantitate*pret_unitar*(1+tva);
}

//O functie de afisare ca sa imi simplific afisarile ceva mai jos


void afisare()
{
cout << "Produsul " << denumire << endl;
cout << "\tcantitate: " << cantitate <<endl;
cout << "\tpret_unitar: " << pret_unitar <<endl;
cout << "\tvaloare fara TVA: " << get_valoare() <<endl;
cout << "\tvaloarea TVA-ului: " << get_valoare_tva() <<endl;
cout << "\tvaloarea cu TVA: " << get_valoare_cu_tva() <<endl;
}
};

//Atributul static va fi initializat in afara clasei.


double Produs::tva=0.19;

int main()
{
cout << "Nu am inca niciun produs, dar stiu ca TVA-ul este de " <<
Produs::tva*100 << "%" << endl;
//10 caiete a 2 lei bucata, fara tva si 5 pixuri a 1 leu bucata, fara tva
Produs p1("caiet",10,2), p2("pix",5,1);
p1.afisare();
p2.afisare();
//Indiferent din care dintre obiecte, sau chiar din afara lor, iau TVA-ul,
//el va avea aceeasi valoare.
cout << "TVA-ul pentru primul produs este de " << p1.tva*100 << "%" << endl;
cout << "TVA-ul pentru al doilea produs este de " << p2.tva*100 << "%" << endl;

43
cout << "TVA-ul pentru toate produsele este de " << Produs::tva*100 << "%" << endl;
//Si daca il modific in oricare dintre obiecte, sau direct in clasa, schimbarea
//se va petrece peste tot, inclusiv in operatiile care depindeau de TVA.
p1.tva=0.2;
p1.afisare();
p2.afisare();
cout << "TVA-ul pentru primul produs este de " << p1.tva*100 << "%" << endl;
cout << "TVA-ul pentru al doilea produs este de " << p2.tva*100 << "%" << endl;
cout << "TVA-ul pentru toate produsele este de " << Produs::tva*100 << "%" << endl;
return 0;
}

Tutorialul 12 - Metode statice

Exemplul anterior, în care a fost demonstrat modul în care se pot declara și utiliza atribute
statice, prezintă o problemă conceptuală și anume, faptul că atributul static este accesibil public
încalcă principiul încapsulării. Această situație poate fi evitată, dacă pe lângă atribute statice
utilizăm și metode statice.
La fel ca și atributele statice, metodele statice pot fi folosite chiar dacă nu există nicio instanță
a clasei respective.
Obs. Ca și atributele statice, metodele statice vor fi doar declarate în clasă și definite efectiv în
afara acesteia.
Obs. Din cauza modului în care operează (pot fi folosite chiar dacă nu există niciun obiect din
clasa respectivă), metodele statice au o serie de limitări semnificative. Ele nu pot accesa niciun
atribut ne-static și nicio metodă ne-statică. De asemenea, în metodele statice nu poate fi utilizat
cuvântul cheie this (cuvântul cheie this este transmis ca un argument ascuns în toate apelurile
de funcții membre nestatice și este disponibil ca o variabilă locală în corpul tuturor funcțiilor
nestatice; this este un pointer constant care conține adresa de memorie a obiectului curent; this
nu este disponibil în funcțiile statice, deoarece funcțiile statice pot fi apelate în absența oricărui
obiect din clasa respectivă). Rezultă din aceasta ca metodele statice sunt uzual restrânse la
rolurile de getter-i și setter-i pentru atributele statice ale clasei (precum se va vedea în exemplul
următor).
Exemplul reia aproape exact codul din Tutorialul 11, exceptând faptul că atributul static tva nu
mai este public și deci, nu mai este accesibil din afara obiectelor clasei și atunci sunt necesari
un setter și un getter.

#include <iostream>

using namespace std;

class Produs
{
//Niste atribute
string denumire;
double cantitate, pret_unitar;
//Un atribut static. Se observa utilizarea cuvantului cheie static.

44
static double tva;
public:
//Un constructor explicit. Se observa faptul ca nu initializez
//atributul static in constructor.
Produs(string denumire, double cantitate, double pret_unitar):
denumire(denumire),
cantitate(cantitate),
pret_unitar(pret_unitar){}

//Niste getter-i
string get_denumire()
{
return denumire;
}

double get_cantitate()
{
return cantitate;
}

double get_pret_unitar()
{
return pret_unitar;
}

//Niste falsi getter-i. Returneaza valori dar nu valorile unor atribute ci


//niste valori calculate.
double get_valoare()
{
return cantitate*pret_unitar;
}

double get_valoare_tva()
{
return cantitate*pret_unitar*tva;
}

double get_valoare_cu_tva()
{
return cantitate*pret_unitar*(1+tva);
}

//O functie de afisare ca sa imi simplific afisarile ceva mai jos


void afisare()
{
cout << "Produsul " << denumire << endl;
cout << "\tcantitate: " << cantitate <<endl;
cout << "\tpret_unitar: " << pret_unitar <<endl;
cout << "\tvaloare fara TVA: " << get_valoare() <<endl;
cout << "\tvaloarea TVA-ului: " << get_valoare_tva() <<endl;
cout << "\tvaloarea cu TVA: " << get_valoare_cu_tva() <<endl;
}

//Metodele statice vor fi doar declarate în clasa. Ele nu pot fi și definite aici.
//Un getter static.
static double get_tva();
//Un setter static.

45
static void set_tva(double);
};

//Atributul static va fi initializat in afara clasei.


double Produs::tva=0.19;

//Metodele statice vor fi definite in afara clasei.


double Produs::get_tva()
{
return Produs::tva;
}

void Produs::set_tva(double param)


{
Produs::tva=param;
}

int main()
{
cout << "Nu am inca niciun produs, dar stiu ca TVA-ul este de " <<
Produs::get_tva()*100 << "%" << endl;
//10 caiete a 2 lei bucata, fara tva si 5 pixuri a 1 leu bucata, fara tva
Produs p1("caiet",10,2), p2("pix",5,1);
p1.set_tva(0.19);
p1.afisare();
p2.afisare();
//Indiferent din care dintre obiecte, sau chiar din afara lor, iau TVA-ul,
//el va avea aceeasi valoare.
cout << "TVA-ul pentru primul produs este de " << p1.get_tva()*100 << "%" << endl;
cout << "TVA-ul pentru al doilea produs este de " << p2.get_tva()*100 << "%" << endl;
cout << "TVA-ul pentru toate produsele este de " << Produs::get_tva()*100 << "%"
<< endl;
//Si daca il modific in oricare dintre obiecte, sau direct in clasa, schimbarea
//se va petrece peste tot, inclusiv in operatiile care depindeau de TVA.
Produs::set_tva(0.2);
p1.afisare();
p2.afisare();
cout << "TVA-ul pentru primul produs este de " << p1.get_tva()*100 << "%" << endl;
cout << "TVA-ul pentru al doilea produs este de " << p2.get_tva()*100 << "%" << endl;
cout << "TVA-ul pentru toate produsele este de " << Produs::get_tva()*100 << "%"
<< endl;
return 0;
}

Obs. Conceptul de obiect static nu are nimic de-a face cu faptul că respectivul obiect are sau
că nu are atribute sau metode statice în compunere. În momentul în care declarăm un obiect ca
fiind static, efectul este același ca pentru orice altă variabilă - în loc să fie eliminat din memorie
odată cu blocul în care a fost creat, rămâne în memorie pe toată durata execuției programului.
Programele următoare demonstrează efectul declarației static pentru un obiect alocat local și
respectiv pentru unul alocat global.
Exemplu de obiect alocat local fără declarația static:

#include <iostream>

46
using namespace std;

class Test
{
public:
Test()
{
cout << "S-a executat constructorul" << endl;
}
~Test()
{
cout << "S-a executat destructorul" << endl;
}
};

void functie()
{
//obiect alocat local
//obiectul va fi creat aici
Test obiect;
//și va fi distrus aici
}

int main()
{
cout << "A inceput executia functiei main" << endl;
functie();
cout << "S-a terminat executia functiei main" << endl;
return 0;
}

Exemplu de obiect alocat local și declarat static (față de programul anterior, se schimbă
momentul distrugerii obiectului):

#include <iostream>

using namespace std;

class Test
{
public:
Test()
{
cout << "S-a executat constructorul" << endl;
}
~Test()
{
cout << "S-a executat destructorul" << endl;
}
};

void functie()
{
//obiect alocat local si static
//obiectul va fi creat aici
static Test obiect;

47
}

int main()
{
cout << "A inceput executia functiei main" << endl;
functie();
cout << "S-a terminat executia functiei main" << endl;
return 0;
}
//si va fi distrus aici

Exemplu de obiect alocat global fără declarația static (obiectul fiind alocat global, diferă
momentele creării și distrugerii sale față de cazul unui obiect alocat static):

#include <iostream>

using namespace std;

class Test
{
public:
Test()
{
cout << "S-a executat constructorul" << endl;
}
~Test()
{
cout << "S-a executat destructorul" << endl;
}
};

//obiect alocat global


//obiectul va fi creat aici
Test obiect;

int main()
{
cout << "A inceput executia functiei main" << endl;
cout << "S-a terminat executia functiei main" << endl;
return 0;
}
//si va fi distrus aici

Exemplu de obiect alocat global și declarat static (nicio modificare față de programul anterior):

#include <iostream>

using namespace std;

class Test
{
public:
Test()
{

48
cout << "S-a executat constructorul" << endl;
}
~Test()
{
cout << "S-a executat destructorul" << endl;
}
};

//obiect alocat global si static


//obiectul va fi creat aici
static Test obiect;

int main()
{
cout << "A inceput executia functiei main" << endl;
cout << "S-a terminat executia functiei main" << endl;
return 0;
}
//si va fi distrus aici

Întrebări de autoevaluare
1. Care dintre următoarele este adevărat despre cuvântul cheie this?
A. Este transmis ca un argument ascuns tuturor apelurilor de funcții
B. Este transmis ca un argument ascuns tuturor apelurilor de funcții nestatice
C. Este transmis ca argument ascuns la toate funcțiile statice
D. Nici una dintre cele de mai sus

2. Care va fi rezultatul execuției următorului program C++?


#include<iostream>
using namespace std;

class Test
{
private:
int x;
int y;
public:
Test(int x = 0, int y = 0) { this->x = x; this->y = y; }
static void fun1() { cout << "Inside fun1()"; }
static void fun2() { cout << "Inside fun2()"; this->fun1(); }
};

int main()
{
Test obj;
obj.fun2();
return 0;
}
A. Inside fun2() Inside fun1()
B. Inside fun2()
C. Inside fun1() Inside fun2()
D. Eroare de compilare

49
Răspunsuri la întrebări
1. B. Este transmis ca un argument ascuns tuturor apelurilor de funcții nestatice - Cuvântul
cheie this este transmis ca un argument ascuns în toate apelurile de funcții membre nestatice și
este disponibil ca o variabilă locală în corpul tuturor funcțiilor nestatice. Pointerul this este un
pointer constant care conține adresa de memorie a obiectului curent. this nu este disponibil în
funcțiile statice, deoarece funcțiile statice pot fi apelate fără niciun obiect (prin numele clasei).
2. D. Eroare de compilare - Există o eroare în fun2(). Este o metodă statică și încearcă să
acceseze this. this nu este disponibil pentru metode statice, deoarece o metodă statică poate fi
apelată fără niciun obiect.

Tutorialul 13 - Obiecte constante și metode constante

Ca orice altă variabilă din C++, un obiect oarecare poate fi declarat ca fiind constant. În acest
caz, atributele conținute vor fi și ele constante și singurul moment în care li se va putea atribui
o valoare va fi la inițializare, în constructor.
Ca o consecință logică, orice metode obișnuite ale clasei nu vor putea fi folosite pentru
obiectele constante, pentru ca ar putea modifica vreun atribut, ceea ce nu este permis.
Obs. De cele mai multe ori obiectele sunt declarate ca fiind constante atunci când sunt trimise
prin referință, ca parametru, unei funcții, dar doresc să mă asigur că atributele lor nu vor fi
modificate pe parcursul funcției respective. Am întâlnit o astfel de situație la Tutorialul 11, la
supraîncărcarea operatorului >.

#include <iostream>

using namespace std;

//O clasa oarecare.


class O_clasa
{
//Un atribut oarecare.
int ceva;
public:
//Un constructor explicit
O_clasa(int ceva):ceva(ceva){}

//Un getter
int get_ceva()
{
return ceva;
}

//Un setter
void set_ceva(int ceva)
{
this->ceva=ceva;
}
};

50
int main()
{
//Un obiect oarecare. Totul functioneaza cum trebuie
O_clasa a(15);
cout << "Continutul obiectului a este, la inceput, " << a.get_ceva() << endl;
a.set_ceva(20);
cout << "Continutul obiectului a este, dupa modificare, " << a.get_ceva() << endl;

//Un obiect constant. Obiectul poate fi creat dar, atat setter-ul cat si getter-ul,
//asa cum sunt scrise mai sus, vor fi neutilizabile.
const O_clasa b(15);
//Aceasta instructiune va da eroare. get_ceva() este o metoda obisnuita, incapabila
//sa lucreze cu obiecte constante.
cout << "Continutul obiectului b este, la inceput, " << b.get_ceva() << endl;
//Si aceasta instructiune va da eroare. set_ceva() este o metoda obisnuita, incapabila
//sa lucreze cu obiecte constante.
b.set_ceva(20);
//Si aceasta instructiune va da eroare. get_ceva() este o metoda obisnuita, incapabila
//sa lucreze cu obiecte constante.
cout << "Continutul obiectului b este, dupa modificare, " << b.get_ceva() << endl;
return 0;
}

Dacă doresc să fac ca anumite metode să poată accesa atributele din obiecte constante,
metodele în sine trebuie declarate ca fiind prevăzute pentru lucrul cu obiecte constante.
Obs. Dacă o metodă este declarată ca fiind prevăzută pentru lucrul cu obiecte constante, ea nu
va putea modifica, direct sau indirect (prin apelarea altor metode), atribute.

#include <iostream>

using namespace std;

//O clasa oarecare.


class O_clasa
{
//Un atribut oarecare.
int ceva;
public:
//Un constructor explicit
O_clasa(int ceva):ceva(ceva){}

//Un getter, doar ca de data aceasta declaram ca are voie sa lucreze cu


//obiecte constante
int get_ceva() const
{
return ceva;
}

//Un setter
void set_ceva(int ceva)
{
this->ceva=ceva;
}
};

51
int main()
{
//Un obiect oarecare. Totul functioneaza cum trebuie
O_clasa a(15);
cout << "Continutul obiectului a este, la inceput, " << a.get_ceva() << endl;
a.set_ceva(20);
cout << "Continutul obiectului a este, dupa modificare, " << a.get_ceva() << endl;

//Un obiect constant. Obiectul poate fi creat dar valorile atributelor sale
//vor putea fi doar citite.
const O_clasa b(15);
//Getter-ul functioneaza, pentru ca s-a declarat ca lucreaza cu obiecte constante
cout << "Continutul obiectului b este, la inceput, " << b.get_ceva() << endl;
//Aceasta instructiune va da eroare. set_ceva() este o metoda obisnuita, incapabila
//sa lucreze cu obiecte constante.
//Iar daca am modifica in definitia clasei metoda set_ceva si am declara ca lucreaza
//cu obiecte constante, ea nu ar mai fi capabila sa modifice atributul si ar da, de
//asemenea, eroare, doar ca mai sus.
b.set_ceva(20);
//Getter-ul functioneaza, pentru ca s-a declarat ca lucreaza cu obiecte constante
cout << "Continutul obiectului b este, dupa incercarea de modificare, " << b.get_ceva()
<< endl;
return 0;
}

În C++, datorită accesului nerestricționat la memorie pe care îl oferă (lucru care nu este
caracteristic pentru alte limbaje, precum Java și C#), se poate scrie o metodă care poate fi
folosită simultan atât ca getter cât și ca setter. Acest lucru se realizează returnând ca rezultat al
metodei respective adresa atributului (folosind operatorul de referință &) în loc de valoarea sa,
precum se poate vedea în exemplul următor:

#include <iostream>

using namespace std;

//O clasa oarecare.


class O_clasa
{
//Un atribut oarecare.
int ceva;
public:
//Un constructor explicit
O_clasa(int ceva):ceva(ceva){}

//Un getter care poate fi folosit si ca setter :D


//In loc sa returneze valoarea atributului returneaza adresa sa,
//fapt care expune direct atributul putand fi astfel scris.
//Aceasta modalitate de lucru neaga principiul incapsularii, dar
//se poate dovedi a fi folositoare in unele cazuri.
//I-am dat un nume mai complicat ca sa fie evident ca metoda poate
//fi folosita in ambele moduri. In practica, o astfel de metoda este
//denumita de obicei ca un getter, deci in cazul asta ar fi get_ceva().
int& set_get_ceva()
{

52
return ceva;
}
};

int main()
{
//Un obiect oarecare
O_clasa a(15);
//Metoda folosita ca getter
cout << "Continutul obiectului a este, la inceput, " << a.set_get_ceva() << endl;
//Metoda folosita ca setter. a.set_get_ceva() este de fapt o referinta la atributul
//ceva si atunci pot sa ii atribui valori, ca unei variabile obisnuite
a.set_get_ceva()=20;
//Metoda folosita din nou ca getter
cout << "Continutul obiectului a este, dupa modificare, " << a.set_get_ceva() << endl;
return 0;
}

Este evident că, dacă lucrăm cu un astfel de setter-getter (nu se folosește această denumire, dar
o folosim aici pentru mai buna înțelegere a ceea ce se întâmplă), vom avea probleme speciale
atunci când avem de-a face cu obiecte constante, în sensul ca va trebui cumva să îl facem să
funcționeze și pentru obiecte constante, al căror conținut nu are voie să fie modificat, dar și
pentru obiecte oarecare, pe al căror conținut avem voie să îl modificăm. Putem realiza acest
lucru prin supraîncărcarea setter-getter-ului, precum în exemplul următor:

#include <iostream>

using namespace std;

//O clasa oarecare.


class O_clasa
{
//Un atribut oarecare.
int ceva;
public:
//Un constructor explicit
O_clasa(int ceva):ceva(ceva){}

//Un getter care poate fi folosit si ca setter :D


//In loc sa returneze valoarea atributului returneaza adresa sa,
//fapt care expune direct atributul putand fi astfel scris.
//Aceasta modalitate de lucru neaga principiul incapsularii, dar
//se poate dovedi a fi folositoare in unele cazuri.
//I-am dat un nume mai complicat ca sa fie evident ca metoda poate
//fi folosita in ambele moduri. In practica, o astfel de metoda este
//denumita de obicei ca un getter, deci in cazul asta ar fi get_ceva().

//Varianta pentru obiecte obisnuite


int& set_get_ceva()
{
return ceva;
}

//Varianta supraincarcata pentru obiecte constante


//Primul const se refera la faptul ca nu vreau sa se poata modifica din

53
//greseala valoarea unui atribut dintr-un obiect constant, iar aceasta metoda,
//returnand o referinta la un atribut, ar putea permite asa ceva.
//Al doilea const se refera la faptul ca aceasta varianta a metodei va fi cea folosita
//pentru obiecte constante.
const int& set_get_ceva() const
{
return ceva;
}
};

int main()
{
//Un obiect oarecare
O_clasa a(15);
//Metoda folosita ca getter
cout << "Continutul obiectului a este, la inceput, " << a.set_get_ceva() << endl;
//Metoda folosita ca setter. a.set_get_ceva() este de fapt o referinta la atributul
//ceva si atunci pot sa ii atribui valori, ca unei variabile obisnuite
a.set_get_ceva()=20;
//Metoda folosita din nou ca getter
cout << "Continutul obiectului a este, dupa modificare, " << a.set_get_ceva() << endl;

//Un obiect constant. Obiectul poate fi creat dar valorile atributelor sale
//vor putea fi doar citite.
const O_clasa b(15);
//Setter-getter-ul functioneaza, pentru ca are o varianta supraincarcata despre
//s-a declarat ca lucreaza cu obiecte constante
cout << "Continutul obiectului b este, la inceput, " << b.set_get_ceva() << endl;
//Aceasta instructiune va da eroare. Despre set_get_ceva am spus in definitia clasei
//ca returneaza o valoare constanta, nemodificabila, iar aici se incearca modificarea
//acestei valori.
//b.set_get_ceva()=20;
//Setter-getter-ul functioneaza, pentru ca are o varianta supraincarcata despre
//s-a declarat ca lucreaza cu obiecte constante
cout << "Continutul obiectului b este, dupa incercarea de modificare, " <<
b.set_get_ceva();
return 0;
}

54
Secțiunea 05

Tutorialul 14 - Template-uri (șabloane) de clase


Uneori nu se poate cunoaște tipul (tipurile) de date pe care le va avea o clasă ca și atribute,
înainte de crearea propriu-zisă a unui obiect din clasa respectivă. Alteori este de dorit ca o clasă
să fie flexibilă, să își poate schimba tipul (tipurile) de date folosite pentru atribute. Un exemplu
tipic este cazul în care mi-aș dori să creez o clasă pentru stocare datelor din vectori, dar nu am
de unde să știu inițial ce fel de date voi pune în vectorii respectivi și îmi doresc să pot crea
diverși vectori, fiecare conținând date de un anumit tip.
Pentru astfel de cazuri există posibilitatea de crea clase cu atribute de tip (tipuri) neprecizat.
Aceste clase se numesc clase șablon sau clase template (class template, în limba engleză).
Obs. Toate containerele disponibile în biblioteca standard (array, vector, list etc.) sunt clase
template.

#include <iostream>

using namespace std;

//Specific ca este vorba de un template si specific niste denumiri


//(nu conteaza ce denumiri) pentru tipul sau tipurile de date necunoscute inca pe care le
voi
//folosi pentru atribute in clasa.
template <class tip>
//Imediat dupa ce am specificat ca va fi vorba de un template, incep definitia clasei
class Vector
{
//In definitia clasei pot folosi si tipuri de date obisnuite
int nr_elemente;
//Dar si tipurile de date pe care inca nu le cunosc, dar pe care le-am anuntat mai sus
tip * elemente;
public:
//Un constructor explicit. Fac in el doar alocarea memoriei pentru vector
Vector(int nr_elemente)
{
this->nr_elemente = nr_elemente;
elemente = new tip[nr_elemente];
}

//Un getter pentru numarul de elemente


int get_nr_elemente()
{
return nr_elemente;
}

//Un setter pentru elementele vectorului.


//Se observa ca pe tot parcursul clasei pot sa folosesc liber tipurile de
//date inca necunoscute, dar pe care le-am anuntat mai sus.
void set_elemente(tip valori[])
{

55
for(int i=0;i<nr_elemente;i++)
elemente[i]=valori[i];
}

//Un getter pentru elementele vectorului.


//Se observa ca pe tot parcursul clasei pot sa folosesc liber tipurile de
//date inca necunoscute, dar pe care le-am anuntat mai sus.
tip * get_elemente()
{
return elemente;
}
};

int main()
{
//La crearea propriu-zisa a obiectelor pe baza clasei template, se va preciza
//intre < si > tipul (tipurile) de date care va fi folosit efectiv
//Aici un vector de intregi
Vector<int> v1(5);
//Aici un vector de string-uri
Vector<string> v2(3);
//O variabila care contine datele care vor fi incarcate in primul vector
int date1[]={12,23,5,-7,99};
//O variabila care contine datele care vor fi incarcate in al doilea vector
string date2[]={"Maria","Ionel","Elena"};
//Incarc datele in primul vector folosind setter-ul
v1.set_elemente(date1);
//Incarc datele in al doilea vector folosind setter-ul
v2.set_elemente(date2);
//Afisez datele din primul vector folosind cei doi getter-i
cout<<"Elementele vectorului v1 sunt:"<<endl;
for(int i=0;i<v1.get_nr_elemente();i++)
cout<<v1.get_elemente()[i]<<endl;
//Afisez datele din al doilea vector folosind cei doi getter-i
cout<<"Elementele vectorului v2 sunt:"<<endl;
for(int i=0;i<v2.get_nr_elemente();i++)
cout<<v2.get_elemente()[i]<<endl;
return 0;
}

Tutorialul 15 - Specializarea template-urilor (șabloanelor) de clase


Se poate întâmpla să ne dorim ca un template de clasă să se comporte în mod particular pentru
un anumit tip de date. Acest lucru se poate obține prin specializarea template-ului respectiv.
Specializarea unui template pentru un anumit tip de date se face prin rescrierea clasei respective
pentru tipul respectiv de date. Dacă se dorește specializarea pentru mai multe tipuri de date, se
va rescrie clasa pentru fiecare dintre tipurile de date pentru care se dorește specializarea.
Obs. Într-un anumit sens, specializarea unui template de clasă poate fi privită ca o
supraîncărcare a clasei înseși.
Exemplul următor preia codul din exemplul anterior și îi adaugă o specializare a clasei template
pentru tipul de date string:

56
#include <iostream>

using namespace std;

//Specific ca este vorba de un template si specific niste denumiri


//(nu conteaza ce denumiri) pentru tipul sau tipurile de date necunoscute inca pe care le
voi
//folosi pentru atribute in clasa.
template <class tip>
//Imediat dupa ce am specificat ca va fi vorba de un template, incep definitia clasei
class Vector
{
//In definitia clasei pot folosi si tipuri de date obisnuite
int nr_elemente;
//Dar si tipurile de date pe care inca nu le cunosc, dar pe care le-am anuntat mai sus
tip * elemente;
public:
//Un constructor explicit. Fac in el doar alocarea memoriei pentru vector
Vector(int nr_elemente)
{
this->nr_elemente = nr_elemente;
elemente = new tip[nr_elemente];
}

//Un getter pentru numarul de elemente


int get_nr_elemente()
{
return nr_elemente;
}

//Un setter pentru elementele vectorului.


//Se observa ca pe tot parcursul clasei pot sa folosesc liber tipurile de
//date inca necunoscute, dar pe care le-am anuntat mai sus.
void set_elemente(tip valori[])
{
for(int i=0;i<nr_elemente;i++)
elemente[i]=valori[i];
}

//Un getter pentru elementele vectorului.


//Se observa ca pe tot parcursul clasei pot sa folosesc liber tipurile de
//date inca necunoscute, dar pe care le-am anuntat mai sus.
tip * get_elemente()
{
return elemente;
}
};

//O specializare a clasei Vector pentru tipul string.


//Nu se mai specifica denumiri generice pentru tipul / tipurile necunoscute pentru ca
//aici stim cu ce tip / tipuri vom avea de-a face.
template <>
class Vector<string>
{
int nr_elemente;
//in locul tipului de date generic folosesc tipul de date pentru care fac specializarea
string * elemente;

57
public:
//Un constructor explicit. Fac in el doar alocarea memoriei pentru vector
Vector(int nr_elemente)
{
this->nr_elemente = nr_elemente;
elemente = new string[nr_elemente];
}

//Un getter pentru numarul de elemente


int get_nr_elemente()
{
return nr_elemente;
}

//Un setter pentru elementele vectorului.


void set_elemente(string valori[])
{
for(int i=0;i<nr_elemente;i++)
elemente[i]=valori[i];
}

//Un getter pentru elementele vectorului.


string * get_elemente()
{
return elemente;
}

//Un getter particularizat care functioneaza doar pentru string, deci nu as fi putut
//sa il definesc in template-ul initial de clasa.
//Acest getter va returna elementele vectorului de string-uri, dar scrise integral cu
//litere mari.
string * get_upper()
{
//Declar un vector auxiliar de stringuri
string * aux;
//Ii aloc memorie
aux = new string[nr_elemente];
//De la un capat la altul al vectorului
for(int i=0;i<nr_elemente;i++)
{
//Copiez stringurile din vectorul initial in noul vector
aux[i]=elemente[i];
//Caracter cu caracter, trec literele din fiecare nou string la litere mari
for(int j=0;j<aux[i].length();j++)
aux[i][j]=toupper(aux[i][j]);
}
return aux;
}

//Un alt getter particularizat care functioneaza doar pentru string, deci nu as fi
//putut sa il definesc in template-ul initial de clasa.
//Acest getter va returna elementele vectorului de string-uri, dar scrise integral cu
//litere mici.
string * get_lower()
{
//Declar un vector auxiliar de stringuri
string * aux;

58
//Ii aloc memorie
aux = new string[nr_elemente];
//De la un capat la altul al vectorului
for(int i=0;i<nr_elemente;i++)
{
//Copiez stringurile din vectorul initial in noul vector
aux[i]=elemente[i];
//Caracter cu caracter, trec literele din fiecare nou string la litere mici
for(int j=0;j<aux[i].length();j++)
aux[i][j]=tolower(aux[i][j]);
}
return aux;
}
};

int main()
{
//La crearea propriu-zisa a obiectelor pe baza clasei template, se va preciza
//intre < si > tipul (tipurile) de date care va fi folosit efectiv
//Aici un vector de intregi
Vector<int> v1(5);
//Aici un vector de string-uri
Vector<string> v2(3);
//O variabila care contine datele care vor fi incarcate in primul vector
int date1[]={12,23,5,-7,99};
//O variabila care contine datele care vor fi incarcate in al doilea vector
string date2[]={"Maria","Ionel","Elena"};
//Incarc datele in primul vector folosind setter-ul
v1.set_elemente(date1);
//Incarc datele in al doilea vector folosind setter-ul
v2.set_elemente(date2);
//Afisez datele din primul vector folosind cei doi getter-i
cout<<"Elementele vectorului v1 sunt:"<<endl;
for(int i=0;i<v1.get_nr_elemente();i++)
cout<<v1.get_elemente()[i]<<endl;
//Afisez datele din al doilea vector folosind cei doi getter-i
cout<<"Elementele vectorului v2 sunt:"<<endl;
for(int i=0;i<v2.get_nr_elemente();i++)
cout<<v2.get_elemente()[i]<<endl;
cout<<"Elementele vectorului v2, scrise cu litere mari sunt:"<<endl;
for(int i=0;i<v2.get_nr_elemente();i++)
cout<<v2.get_upper()[i]<<endl;
cout<<"Elementele vectorului v2, scrise cu litere mici sunt:"<<endl;
for(int i=0;i<v2.get_nr_elemente();i++)
cout<<v2.get_lower()[i]<<endl;
return 0;
}

Tutorialul 16 - Constructori de copiere


Se numește constructor de copiere un constructor care este apelat atunci când un obiect dintr-
o clasă este creat prin copierea unui alt obiect din aceeași clasă. Noul obiect va avea un conținut
absolut identic cu obiectul de la care s-a plecat.
Precum s-a precizat anterior, în cazul în care programatorul nu furnizează un constructor de
copiere (a se vedea și rezumatul de la sfârșitul Tutorialului 19), compilatorul va furniza, în

59
anumite condiții, în mod automat, un astfel de constructor, precum se poate vedea în exemplul
următor:

#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat static
int x;
public:
//Un constructor explicit
Ceva(int a):x(a){}

//Un getter
int get_x()
{
return x;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect pornind de la un alt obiect de acelasi tip
//Va fi utilizat constructorul de copiere furnizat automat
Ceva b(a);
//Afisam valorile continute de cele doua obiecte
//Constatam ca ele contin aceeasi valoare
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

În multe cazuri, acest constructor de copiere furnizat automat este suficient și nu va fi nevoie
de scrierea de către programator a unui constructor de copiere dedicat.
!!! Constructorul de copiere furnizat implicit face o copiere superficială a ceea ce este stocat în
atribute. Cât timp atributele sunt alocate static (conțin direct valorile) acest lucru nu este o
problemă și programatorul se poate mulțumi cu acel constructor de copiere furnizat automat.
Exemplul următor adaugă la exemplul anterior un setter și ilustrează faptul că un obiect care
conține atribute alocate static, creat folosind constructorul de copiere implicit, este independent
de obiectul pe baza căruia a fost creat prin copiere:

#include <iostream>

using namespace std;

60
//O clasa oarecare
class Ceva
{
//Un atribut alocat static
int x;
public:
//Un constructor explicit
Ceva(int a):x(a){}

//Un getter
int get_x()
{
return x;
}

//Un setter
void set_x(int a)
{
x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect pornind de la un alt obiect de acelasi tip
//Va fi utilizat constructorul de copiere furnizat automat
Ceva b(a);
//Afisam valorile continute de cele doua obiecte
//Constatam ca ele contin aceeasi valoare
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt va ramane neschimbata
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

Dacă, în schimb, există și atribute alocate dinamic (adică nu conțin direct valorile, ci conțin
adresele zonelor de memorie în care sunt stocate valorile), lucrurile se complică, în sensul că
nu vor fi copiate valorile, ci adresele. Acest lucru va face ca obiectul copiat să includă de fapt
aceeași zonă de memorie cu obiectul pe baza căruia s-a făcut copia.
Obs. În limbajele în care programatorul nu controlează direct gestiunea memoriei (ex. Java,
C#), acest efect nu apare.
Cele două obiecte vor fi practic gemeni siamezi și modificarea valorilor atributelor într-unul
dintre ele va duce la modificarea valorilor atributelor în celălalt obiect (efect care este nedorit,
de cele mai multe ori, dar poate fi folosit, în cazuri particulare, pentru a transmite valori între
obiecte, ca un fel de atribute statice de uz restrâns):

61
#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat dinamic
int * x;
public:
//Un constructor explicit
Ceva(int a)
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
*x=a;
}

//Un destructor. Avem atribute alocate dinamic.


//Nu trebuie sa uitam sa "facem curat".
~Ceva()
{
delete x;
}

//Un getter
int get_x()
{
return *x;
}

//Un setter
void set_x(int a)
{
*x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect pornind de la un alt obiect de acelasi tip
//Va fi utilizat constructorul de copiere furnizat automat
Ceva b(a);
//Afisam valorile continute de cele doua obiecte
//Constatam ca ele contin aceeasi valoare
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt se va schimba si ea simultan
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;

62
return 0;
}

Pentru a împiedica apariția acestui efect de modificare simultană a obiectelor cu atribute alocate
dinamic, programatorul va trebui să își scrie propriul constructor de copiere, care să facă o
copiere în adâncime (o copiere a valorilor stocate la adrese, nu a adreselor):

#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat dinamic
int * x;
public:
//Un constructor explicit
Ceva(int a)
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
*x=a;
}

//Un constructor de copiere scris de programator.


//Putem sa ne dam seama relativ usor ca este constructor de copiere penru ca primeste
//ca si parametru o referinta la un obiect din aceeasi clasa, dar pe care nu are voie
//sa il modifice (este const).
Ceva (const Ceva& ob_copiat)
{
//Alocarea memoriei necesare pentru obiectul copie
x=new int;
//Transferul valorii din obiectul copiat in obiectul copie
*x=ob_copiat.get_x();
}

//Un destructor. Avem atribute alocate dinamic.


//Nu trebuie sa uitam sa "facem curat".
~Ceva()
{
delete x;
}

//Un getter
//Va trebui sa precizez ca este capabil sa lucreze si pentru obiecte constante, pentru
//ca il folosesc pentru un obiect constant in constructorul de copiere.
//A se revedea la nevoie exemplele cu obiecte constante de mai sus.
int get_x() const
{
return *x;
}

//Un setter
void set_x(int a)

63
{
*x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect pornind de la un alt obiect de acelasi tip
//Va fi utilizat constructorul de copiere scris de programator
Ceva b(a);
//Afisam valorile continute de cele doua obiecte.
//Constatam ca ele contin aceeasi valoare.
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt va ramane neschimbata
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

Obs. Când ne creăm propriul constructor de copiere, transmitem un obiect prin referință și
regula este să îl transmitem ca o referință const. Un motiv de trecere a referinței const este că
ar trebui să folosim const în C++, acolo unde este posibil, pentru ca obiectele să nu fie
modificate accidental. Dincolo de acest motiv, compilatoarele care nu fac copy elision (o
tehnică de optimizare a compilatorului care evită copierea inutilă a obiectelor), sau o fac doar
în anumite situații, vor da eroare dacă nu este precizat faptul că obiectul transmis este const.
Spre exemplu, programul următor, deși aparent corect, va da eroare la compilare în astfel de
compilatoare, la linia la care se apelează constructorul de copiere:

#include<iostream>
using namespace std;

class Test
{
public:
Test(Test &t){}
Test(){}
};

Test fun()
{
cout << "fun() Called\n";
Test t;
return t;
}

int main()
{
Test t1;

64
Test t2 = fun();
return 0;
}

Obs. Copy elision, tehnica de optimizare a compilatorului care evită copierea inutilă a
obiectelor, este aplicată automat de către compilator și dacă este activată, aplicarea sau
neaplicarea ei nu poate fi controlată de către programator. Facilitatea poate fi totuși complet
inhibată, folosind opțiune de compilare ‑fno‑elide‑constructors. Mai multe informații despre
situațiile în care copy elision se aplică obligatoriu și cele în care se aplică opțional pot fi găsite
aici: https://en.cppreference.com/w/cpp/language/copy_elision și respectiv aici:
https://en.wikipedia.org/wiki/Copy_elision
Un exemplu de cod în care copy elision va fi aplicată automat (dacă este și compilatorul capabil
de așa ceva - facilitatea este disponibilă începând cu C++11) este următorul:

#include <iostream>
using namespace std;

class B
{
public:
//un constructor cu parametri, aici un parametru cu valoare implicita
B(const char* str = "\0")
//un string fara continut; \0 este caracterul invizibil de terminare a unui string
{
cout << "A fost apelat constructorul explicit" << endl;
}

//constructorul de copiere
B(const B &b)
{
cout << "A fost apelat constructorul de copiere" << endl;
}
};

int main()
{
//aparent ar trebui sa fie apelat si constructorul explicit si constructorul de
//copiere, pentru ca este creat un obiect temporar si apoi acesta este copiat,
//dar daca intra in actiune copy elision, va fi apelat doar constructorul explicit
B ob = "copy me";
return 0;
}

Obs. Pentru a aplica o opțiune explicită de compilare (în cazul de mai sus, pentru a inhiba copy
elision), în CodeBlocks trebuie executate operațiile:
1. Se deschide fereastra Settings -> Compiler:

65
2. Dacă opțiunea de compilare dorită este un principală (un compiler flag; nu este cazul
de față), ea se va găsi explicit pe tab-ul Compiler Flags:

3. Dacă opțiunea de compilare dorită este un secundară (cum este cazul de față) ea va
trebui scrisă manual în caseta de text de pe tab-ul Other compiler options:

După scrierea și salvarea opțiunii, compilatorul se va comporta așa cum ne-am fi așteptat inițial
- va fi apelat atât constructorul explicit cât și cel de copiere.

66
Tutorialul 17 - Asignare (atribuire) prin copiere
O altă modalitate de copiere a conținutului unui obiect într-un alt obiect (doar ca de data aceasta
este un obiect deja existent, nu unul nou, ca la Tutorialul 16) este utilizarea operatorului de
atribuire (asignare). Toate informațiile specificate mai sus pentru constructorul de copiere sunt
valabile și aici, pentru operatorul de asignare:
● Dacă programatorul nu scrie un operator de atribuire, acesta va fi furnizat automat în
anumite condiții (a se vedea și rezumatul de la sfârșitul Tutorialului 19).
● Operatorul de atribuire furnizat automat face copierea superficială a conținutului
obiectelor.
● Această copiere superficială funcționează bine pentru obiectele care au numai atribute
alocate static și atunci programatorul poate utiliza fără probleme operatorul de atribuire
furnizat automat.
● Pentru obiectele care au și atribute alocate dinamic, copierea superficială are, probabil,
efecte nedorite și atunci programatorul va trebui să își scrie propriul operator de
atribuire care să facă o copiere în adâncime.
Obs. Constructorul de copiere este folosit atunci când este vorba de construirea unui obiect
nou ca o copie a unui obiect deja existent. Atribuirea prin copiere are loc atunci când obiectul
în care se copiază există deja. Deci:
Obiect nou - exemplu de construire prin copiere:
Ceva a(10);
Ceva b(a);
Tot obiect nou - alt exemplu de construire prin copiere. Chiar dacă apare simbolul egal, nu se
utilizează operatorul de atribuire (furnizat automat sau nu), ci constructorul de copiere:
Ceva a(10);
Ceva b=a;
Obiect deja existent - exemplu de utilizare a operatorului de atribuire (furnizat automat sau nu):
Ceva a(10);
Ceva b;
b=a;

Prima situație: utilizarea operatorului de atribuire furnizat implicit pentru obiecte care au numai
atribute alocate static. Se observă că obiectele sunt complet separate unul de celălalt:

#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat static
int x;
public:
//Un constructor implicit
Ceva()
{

67
}

//Un constructor explicit


Ceva(int a):x(a){}

//Un getter
int get_x()
{
return x;
}

//Un setter
void set_x(int a)
{
x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect nou fara continut
//Va fi utilizat constructorul implicit definit mai sus
Ceva b;
//Utilizam operatorul de atribuire furnizat automat
b=a;
//Afisam valorile continute de cele doua obiecte
//Constatam ca ele contin aceeasi valoare
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt va ramane neschimbata
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

A doua situație: utilizarea operatorului de atribuire furnizat implicit (care face copiere
superficială) pentru obiecte care au și atribute alocate dinamic. Se observă că obiectele au
conținut comun:

#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat dinamic
int * x;
public:
//Un constructor implicit
Ceva()

68
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
}

//Un constructor explicit


Ceva(int a)
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
*x=a;
}

//Un destructor. Avem atribute alocate dinamic.


//Nu trebuie sa uitam sa "facem curat".
~Ceva()
{
delete x;
}

//Un getter
int get_x()
{
return *x;
}

//Un setter
void set_x(int a)
{
*x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect nou fara continut
//Va fi utilizat constructorul implicit definit mai sus
Ceva b;
//Utilizam operatorul de atribuire furnizat automat
b=a;
//Afisam valorile continute de cele doua obiecte
//Constatam ca ele contin aceeasi valoare
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt se va schimba si ea simultan
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

69
A treia situație: utilizarea operatorului de atribuire scris de programator și care face copiere în
adâncime. Se observă că obiectele sunt complet separate unul de celălalt:

#include <iostream>

using namespace std;

//O clasa oarecare


class Ceva
{
//Un atribut alocat dinamic
int * x;
public:
//Un constructor implicit
Ceva()
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
}

//Un constructor explicit


Ceva(int a)
{
//Atributul este alocat dinamic.
//Trebuie sa ii alocam memorie.
x=new int;
*x=a;
}

//Un operator de atribuire scris de programator.


//A se revedea, la nevoie, exemplul de supraincarcare a operatorilor (Tutorialul 10).
Ceva& operator=(const Ceva& ob_copiat)
{
//Transferul valorii din obiectul copiat in obiectul copie
*x=ob_copiat.get_x();
//Returnarea obiectului copie.
return *this;
}

//Un destructor. Avem atribute alocate dinamic.


//Nu trebuie sa uitam sa "facem curat".
~Ceva()
{
delete x;
}

//Un getter
//Va trebui sa precizez ca este capabil sa lucreze si pentru obiecte constante, pentru
//ca il folosesc pentru un obiect constant in operatorul de atribuire.
//A se revedea, la nevoie, exemplele cu obiecte constante (Tutorialul 13).
int get_x() const
{
return *x;
}

70
//Un setter
void set_x(int a)
{
*x=a;
}
};

int main()
{
//Cream un obiect pornind de la o valoare intreaga
//Va fi utilizat constructorul explicit definit mai sus
Ceva a(10);
//Cream un obiect nou fara continut
//Va fi utilizat constructorul implicit definit mai sus
Ceva b;
//Utilizam operatorul de atribuire scris de programator
b=a;
//Afisam valorile continute de cele doua obiecte.
//Constatam ca ele contin aceeasi valoare.
cout<<"a contine valoarea: "<<a.get_x()<<endl;
cout<<"b contine valoarea: "<<b.get_x()<<endl;
//Daca schimbam valoarea intr-unul dintre obiecte
a.set_x(99);
//Valoarea din celalalt va ramane neschimbata
cout<<"dupa schimbare, a contine valoarea: "<<a.get_x()<<endl;
cout<<"dupa schimbare, b contine valoarea: "<<b.get_x()<<endl;
return 0;
}

71
Secțiunea 06

Tutorialul 18 - Constructori de mutare


Se numește constructor de mutare un constructor care este apelat atunci când un obiect dintr-
o clasă este creat prin mutarea conținutului unui alt obiect din aceeași clasă. Noul obiect va
avea un conținut absolut identic cu obiectul de la care s-a plecat.
Precum s-a precizat anterior, în cazul în care programatorul nu furnizează un constructor de
mutare (a se vedea și rezumatul de la sfârșitul Tutorialului 19), compilatorul va furniza, în
anumite condiții, în mod automat un astfel de constructor, precum se poate vedea în exemplul
următor:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string informatie;
public:
//Un constructor explicit.
Ceva(string info):informatie(info){}

//Un getter.
string get_info()
{
return informatie;
}
};

int main()
{
Ceva a("continutul lui a");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
//Apelam o functie care muta continutul unui obiect in alt obiect.
//Functia move este disponibila incepand cu C++03 (versiunea de C++ standardizata in
//2003)
Ceva b = move(a);
cout<<"Acum gasim in b: "<<b.get_info()<<endl;
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

În cele mai multe cazuri, acest constructor de mutare furnizat automat este suficient și nu va fi
nevoie de scrierea de către programator a unui constructor de mutare dedicat.
Exemplul următor adaugă la exemplul anterior un constructor de mutare scris de programator,
ilustrând modul în care ar trebui scris un astfel de constructor, pentru o clasă având atribute
alocate static:

72
#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string informatie;
public:
//Un constructor explicit.
Ceva(string info):informatie(info){}

//Un constructor de mutare.


//Se distinge de alti constructori prin faptul ca primeste ca parametru
//un obiect din aceeasi clasa, dar, spre deosebire de constructorul de copiere,
//care primeste acest parametru ca referinta simpla (&) si constanta,
//constructorul de mutare primeste parametrul ca referinta dubla (&&) si
//neconstanta (ca sa poata fi mutata). O astfel de referinta se numeste rvalue
//(valoare referita).
Ceva(Ceva&& param) : informatie(param.informatie)
{
//Golesc vechiul obiect (am apucat deja sa copiez ce era in el in antetul
//constructorului).
param.informatie="";
cout<<"S-a executat constructorul de mutare"<<endl;
}

//Un getter.
string get_info()
{
return informatie;
}
};

int main()
{
Ceva a("continutul lui a");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
//Apelam o functie care muta continutul unui obiect in alt obiect.
//Functia move este disponibila incepand cu C++03 (versiunea de C++ standardizata in
//2003)
Ceva b = move(a);
cout<<"Acum gasim in b: "<<b.get_info()<<endl;
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

!!! Constructorul de mutare furnizat implicit face o mutarea efectivă a conținutului care a fost
stocat în atributele vechiului obiect, în noul obiect, numai dacă obiectul are atribute alocate
static. Dacă obiectul are atribute alocate dinamic, mutarea se va face superficial și obiectul din
care a fost mutat conținutul își va păstra de fapt conținutul, precum se poate vedea în exemplul
următor:

#include <iostream>

73
using namespace std;

//O clasa oarecare.


class Ceva
{
string * informatie;
public:
//Un constructor explicit.
Ceva(string info)
{
informatie=new string(info);
}

//Un destructor, ca sa am cum sa eliberez memoria ocupata.


~Ceva()
{
delete informatie;
}

//Un getter.
string get_info()
{
return * informatie;
}
};

int main()
{
Ceva a("continutul lui a");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
//Apelam o functie care muta continutul unui obiect in alt obiect.
//Functia move este disponibila incepand cu C++03 (versiunea de C++ standardizata in
//2003)
Ceva b = move(a);
cout<<"Acum gasim in b: "<<b.get_info()<<endl;
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

Din acest motiv, ca și la constructorul de copiere și la operatorul de atribuire prin copiere,


pentru obiectele având conținut alocat dinamic, de cele mai multe ori programatorul va trebui
să își scrie propriul constructor de mutare, precum se poate vedea în exemplu următor:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string * informatie;
public:
//Un constructor explicit.
Ceva(string info)
{

74
informatie=new string(info);
}

//Un destructor, ca sa am cum sa eliberez memoria ocupata.


~Ceva()
{
delete informatie;
}

//Un constructor de mutare.


//Se distinge de alti constructori prin faptul ca primeste ca parametru
//un obiect din aceeasi clasa, dar, spre deosebire de constructorul de copiere,
//care primeste acest parametru ca referinta simpla (&) si constanta,
//constructorul de mutare primeste parametrul ca referinta dubla (&&).
//O astfel de referinta se numeste rvalue (valoare referita).
Ceva(Ceva&& param) : informatie(param.informatie)
{
//Golesc vechiul obiect (am apucat deja sa copiez ce era in el in antetul
//constructorului). Nu putem sa stergem pur si simplu zona de memorie alocata (cu
//delete) pentru ca acum aceasta zona nu mai apartine vechiului obiect, ci
//noului obiect si daca as folosi delete, as lasa ambele obiecte fara continut.
//Folosim o varianta alternativa, ii dam pointerului din vechiul obiect
//valoarea predefinita nullptr (pointer care indica spre nimic).
//nullptr este o constanta introdusa in C++11 (versiunea de C++ standardizata in
//2011). In versiuni mai vechi de C++ se poate folosi 0 sau NULL ca valoare pentru
un //pointer care sa indice spre nimic.
param.informatie=nullptr;
cout<<"S-a executat constructorul de mutare"<<endl;
}

//Un getter. Modificam getter-ul pentru a ne avertiza daca obiectul nu mai are
//continut. Altfel ar da eroare, cand am incerca sa afisam un continut
//inexistent.
string get_info()
{
if(informatie!=nullptr)
return * informatie;
else
return "obiectul nu are continut";
}
};

int main()
{
Ceva a("continutul lui a");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
//Apelam o functie care muta continutul unui obiect in alt obiect.
//Functia move este disponibila incepand cu C++03 (versiunea de C++ standardizata in
//2003)
Ceva b = move(a);
cout<<"Acum gasim in b: "<<b.get_info()<<endl;
//Aici ar fi dat eroare, daca nu modificam getter-ul, pentru ca a nu mai avea
//continut.
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

75
Tutorialul 19 - Asignare (atribuire) prin mutare

Se numește operator de atribuire prin mutare acel operator de atribuire care este apelat
atunci când într-un obiect deja existent dintr-o clasă se mută conținutul unui obiect fără nume
(unnamed) din aceeași clasă.
Precum s-a precizat anterior, în cazul în care programatorul nu furnizează un operator de
atribuire prin mutare (a se vedea și rezumatul de la sfârșitul Tutorialului 19), compilatorul va
furniza, în anumite condiții, în mod automat un astfel de operator, precum se poate vedea în
exemplul următor:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string informatie;
public:
//Un constructor implicit.
Ceva(){}

//Un constructor explicit.


Ceva(string info):informatie(info){}

//Un getter.
string get_info()
{
return informatie;
}
};

int main()
{
//Cream un obiect ca sa existe deja atunci cand vom face atribuirea (altfel nu
//va fi utilizat operatorul de atribuire prin mutare, ci constructorul de
//copiere).
Ceva a;
//Facem atribuirea. Dam ca valoare un obiect fara nume. Va fi apelat operatorul
//de atribuire prin mutare.
a = Ceva("continutul unui obiect fara nume");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

În cele mai multe cazuri, acest operator de atribuire prin mutare furnizat automat este suficient
și nu va fi nevoie de scrierea de către programator a unui operator de atribuire prin mutare
dedicat.

76
Exemplul următor adaugă la exemplul anterior un operator de atribuire prin mutare scris de
programator, ilustrând modul în care ar trebui scris un astfel de operator, pentru o clasă având
atribute alocate static:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string informatie;
public:
//Un constructor implicit.
Ceva(){}

//Un constructor explicit.


Ceva(string info):informatie(info){}

//Un operator de atribuire prin mutare.


//Se distinge de operatorul de atribuire prin copiere prin faptul
//ca desi, primeste ca parametru, dar, spre deosebire de operatorul
//de atribuire prin copiere, care primeste acest parametru ca
//referinta simpla (&) si constanta, operatorul de atribuire prin
//mutare primeste parametrul ca referinta dubla (&&).
//O astfel de referinta se numeste rvalue (valoare referita).
Ceva& operator= (Ceva&& param)
{
informatie=param.informatie;
//Tinand cont de faptul ca obiectul fara nume este distrus imediat,
//aceasta operatie nu ar mai fi necesara. O lasam totusi, ca sa vedem
//analogia cu constructorul de mutare.
param.informatie="";
cout<<"S-a executat operatorul de atribuire prin mutare"<<endl;
}

//Un getter.
string get_info()
{
return informatie;
}
};

int main()
{
//Cream un obiect ca sa existe deja atunci cand vom face atribuirea (altfel nu
//va fi utilizat operatorul de atribuire prin mutare, ci constructorul de
//copiere).
Ceva a;
//Facem atribuirea. Dam ca valoare un obiect fara nume. Va fi apelat operatorul
//de atribuire prin mutare.
a = Ceva("continutul unui obiect fara nume");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

77
În cazul atribuirii prin mutare, nu este relevant dacă mutarea se face superficial sau nu, pentru
că obiectul fără nume din care se preiau datele va fi oricum distrus imediat, la sfârșitul
instrucțiunii de atribuire:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string informatie;
public:
//Un constructor implicit.
Ceva()
{
//Scriem mesajul asta doar ca sa vedem cand este creat obiectul numit a
cout<<"A fost apelat constructorul implicit (pentru obiectul denumit a)"<<endl;
}

//Un constructor explicit.


Ceva(string info):informatie(info)
{
//Scriem mesajul asta doar ca sa vedem cand este creat obiectul fara nume
cout<<"A fost apelat constructorul explicit (pentru obiectul fara nume)"<<endl;
}

//Scriem si destructorul doar ca sa vedem cand sunt distruse cele doua obiecte
~Ceva()
{
cout<<"A fost apelat destructorul."<<endl;
}

//Un operator de atribuire prin mutare.


//Se distinge de operatorul de atribuire prin copiere prin faptul
//ca desi, primeste ca parametru, dar, spre deosebire de operatorul
//de atribuire prin copiere, care primeste acest parametru ca
//referinta simpla (&) si constanta, operatorul de atribuire prin
//mutare primeste parametrul ca referinta dubla (&&).
//O astfel de referinta se numeste rvalue (valoare referita).
Ceva& operator= (Ceva&& param)
{
informatie=param.informatie;
cout<<"S-a executat operatorul de atribuire prin mutare"<<endl;
}

//Un getter.
string get_info()
{
return informatie;
}
};

int main()
{
//Cream un obiect ca sa existe deja atunci cand vom face atribuirea (altfel nu

78
//va fi utilizat operatorul de atribuire prin mutare, ci constructorul de
//copiere).
Ceva a;
//Facem atribuirea. Dam ca valoare un obiect fara nume. Va fi apelat operatorul
//de atribuire prin mutare. Obiectul fara nume va fi creat si distrus in cursul
//acestei atribuiri. Mesajele pe care le-am pus in constructori si destructor
//ne vor arata acest lucru.
a = Ceva("continutul unui obiect fara nume");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
//Aici se termina executia functiei main. Va fi distrus automat si obiectul numit a.
return 0;
}

Așa cum spuneam mai sus, la atribuirea prin mutare, nu este relevant dacă obiectul are conținut
alocat dinamic, pentru că obiectul fără nume al cărui conținut este mutat are durată de viața
foarte redusă - este practic un obiect “de unică folosință”, fiind distrus automat la sfârșitul
instrucțiunii în care a fost utilizat. Pentru a completa totuși analogia cu ceea ce am văzut în
Tutorialul 18 la constructorul de mutare, să vedem cum ar trebui scris un operator de atribuire
prin mutare, în cazul în care clasa ar avea atribute alocate dinamic:

#include <iostream>

using namespace std;

//O clasa oarecare.


class Ceva
{
string * informatie;
public:
//Un constructor implicit.
Ceva()
{
informatie=new string;
}

//Un constructor explicit.


Ceva(string info)
{
informatie=new string(info);
}

//Un destructor, ca sa am cum sa eliberez memoria ocupata.


~Ceva()
{
delete informatie;
}

//Un operator de atribuire prin mutare.


//Se distinge de operatorul de atribuire prin copiere prin faptul
//ca desi, primeste ca parametru, dar, spre deosebire de operatorul
//de atribuire prin copiere, care primeste acest parametru ca
//referinta simpla (&) si constanta, operatorul de atribuire prin
//mutare primeste parametrul ca referinta dubla (&&).
//O astfel de referinta se numeste rvalue (valoare referita).

79
Ceva& operator= (Ceva&& param)
{
informatie=param.informatie;
//Tinand cont de faptul ca obiectul fara nume este distrus imediat,
//aceasta operatie nu ar mai fi necesara. O lasam totusi, ca sa vedem
//analogia cu constructorul de mutare.
//nullptr este o constanta introdusa in C++11 (versiunea de C++ standardizata in
//2011). In versiuni mai vechi de C++ se poate folosi 0 sau NULL ca valoare pentru
un //pointer care sa indice spre nimic.
param.informatie=nullptr;
cout<<"S-a executat operatorul de atribuire prin mutare"<<endl;
}

//Un getter.
string get_info()
{
return * informatie;
}
};

int main()
{
//Cream un obiect ca sa existe deja atunci cand vom face atribuirea (altfel nu
//va fi utilizat operatorul de atribuire prin mutare, ci constructorul de
//copiere).
Ceva a;
//Facem atribuirea. Dam ca valoare un obiect fara nume. Va fi apelat operatorul
//de atribuire prin mutare.
a = Ceva("continutul unui obiect fara nume");
cout<<"Acum gasim in a: "<<a.get_info()<<endl;
return 0;
}

Rezumat funcții membre speciale


În C++ sunt furnizate automat șase metode speciale, în diverse condiții:

Metoda (funcția Va fi furnizată automat de către compilator Ce face varianta


membră) atunci când: furnizată de către
compilator:

Constructorul Programatorul nu a scris niciun constructor. Construiește un


implicit obiect gol.

Destructorul Programatorul nu a scris un destructor. Distruge un obiect.

Constructorul de Programatorul nu a scris un constructor de Copiază (superficial)


copiere copiere. toate atributele.
Programatorul nu a scris un constructor de
mutare.
Programatorul nu a scris un operator de
atribuire prin mutare.

80
Operatorul de Programatorul nu a scris un constructor de Copiază (superficial)
atribuire prin copiere. toate atributele.
copiere Programatorul nu a scris un constructor de
mutare.
Programatorul nu a scris un operator de
atribuire prin mutare.

Constructorul de Programatorul nu a scris un destructor. Mută toate


mutare Programatorul nu a scris un constructor de atributele.
copiere.
Programatorul nu a scris un constructor de
mutare.
Programatorul nu a scris un operator de
atribuire prin copiere.
Programatorul nu a scris un operator de
atribuire prin mutare.

Operatorul de Programatorul nu a scris un destructor. Mută toate


atribuire prin Programatorul nu a scris un constructor de atributele.
mutare copiere.
Programatorul nu a scris un constructor de
mutare.
Programatorul nu a scris un operator de
atribuire prin copiere.
Programatorul nu a scris un operator de
atribuire prin mutare.

Tutorialul 20 - Funcții friend

Se numește funcție friend o funcție care nu face parte dintr-o clasă (nu este metodă / funcție
membră a clasei) dar are acces la membrii (atribute și metode) protejați (private și protected)
ai clasei.
În mod tipic, o astfel de funcție este necesară atunci când se execută operații privind conținutul
protejat al obiectelor din două sau mai multe clase diferite. O funcție declarată friend pentru
ambele clase, va avea acces la conținutul celor două clase, fără să facă parte din vreuna dintre
ele.
Obs. Utilizarea funcțiilor friend este o încălcare parțială a principiului încapsulării. În limbajele
de programare care implementează mai strict principiile POO (ex. Java), nu există această
facilitate.
Exemplul următor ilustrează modul în care se declară și se utilizează o funcție friend:

81
#include <iostream>

using namespace std;

// O clasa oarecare
class Dreptunghi
{
//Atribute
int latime, inaltime;
public:
//Un constructor implicit
Dreptunghi(){}

//Un constructor explicit


Dreptunghi (int x, int y): latime(x), inaltime(y) {}

//O metoda oarecare


int arie()
{
return latime * inaltime;
}

//O functie friend. Se declara in clasa si se defineste


//in afara acesteia.
friend Dreptunghi dublu(const Dreptunghi&);
};

//Definitia functiei friend.


//In cazul de fata va crea un dreptunghi de doua ori mai mare
//decat cel primit ca parametru.
Dreptunghi dublu (const Dreptunghi& param)
{
Dreptunghi rez;
rez.latime = param.latime*2;
rez.inaltime = param.inaltime*2;
return rez;
}

int main ()
{
//Declar doua dreptunghiuri, unul cu laturi cunoscute si unul fara
Dreptunghi d1(4,6), d2;
//Spun ca al doilea este dublul primului.
d2 = dublu(d1);
//Afisez ariile
cout<<"Aria lui d1 este "<<d1.arie()<<endl;
//Daca are laturile de doua ori mai mari, ma astept ca aria sa fie
//de patru ori mai mare.
cout<<"Aria lui d2 este "<<d2.arie()<<endl;
return 0;
}

82
Tutorialul 21 - Clase friend

Se numește clasă friend o clasă care are acces la membrii (atribute și metode) protejați (private
și protected) ai unei alte clase.
În mod tipic, o astfel de funcție este necesară atunci când se execută operații privind conținutul
protejat al obiectelor din două sau mai multe clase diferite. O funcție declarată friend pentru
ambele clase, va avea acces la conținutul celor două clase, fără să facă parte din vreuna dintre
ele.
Obs. Utilizarea claselor friend este o încălcare parțială a principiului încapsulării. În limbajele
de programare care implementează mai strict principiile POO (ex. Java), nu există această
facilitate.
Exemplul următor ilustrează modul în care se declară și se utilizează o clasă friend:

#include <iostream>
//Declaram si biblioteca cmath. Vom avea nevoie de ea pentru sqrt
#include <cmath>

using namespace std;

//Declaram o clasa Patrat. Nu o definim inca.


//O vom defini mai tarziu, dar o declaram acum, pentru a putea
//spune in urmatoarea clasa, clasa Cerc ca niste functii au
//parametri de tip Patrat.
class Patrat;

//declaram o clasa Cerc


class Cerc
{
//Atributele
double raza;
public:
//Constructorul implicit
Cerc(){}

//Constructorul explicit
Cerc(double x):raza(x){}

//Un getter
double get_raza()
{
return raza;
}

//Declaram aceasta functie, dar nu o putem scrie inca


//pentru ca in acest moment nu am dat inca definitia
//clasei Patrat, compilatorul nu ii cunoaste structura
//drept pentru care nu o putem folosi inca.
//Functia calculeaza ce raza ar trebui sa aiba cercul
//pentru a fi inscris in patratul primit ca parametru.
void inscris(Patrat);

83
//Declaram aceasta functie, dar nu o putem scrie inca
//pentru ca in acest moment nu am dat inca definitia
//clasei Patrat, compilatorul nu ii cunoaste structura
//drept pentru care nu o putem folosi inca.
//Functia calculeaza ce raza ar trebui sa aiba cercul
//pentru a fi circumscris patratului primit ca parametru.
void circumscris(Patrat);
};

class Patrat
{
//spunem ca clasa Cerc este clasa friend, deci are voie
//sa acceseze membrii protejati ai clasei Patrat
friend class Cerc;
//Atributele
double latura;
public:
//Constructorul explicit
Patrat(double x):latura(x){}

//Un getter
double get_latura()
{
return latura;
}
};

//Acum sunt disponibile toate definitiile de clase necesare,


//asa ca putem scrie definitia functiei inscris
void Cerc::inscris(Patrat param)
{
//Raza cercului inscris este jumatate din latura.
raza=param.latura/2;
}

//Acum sunt disponibile toate definitiile de clase necesare,


//asa ca putem scrie definitia functiei circumscris
void Cerc::circumscris(Patrat param)
{
//Raza cercului circumscris este latura * radical din 2, pe 2.
raza=param.latura*sqrt(2)/2;
}

int main ()
{
//Declaram un patrat cu latura de 10
Patrat p(10);
//Declaram doua cercuri cu raza necunoscuta
Cerc c1, c2;
//Spunem despre primul cerc ca este inscris in patrat
c1.inscris(p);
//Spunem despre al doilea cerc ca este circumscris patratului
c2.circumscris(p);
//Afisam diversele dimensiuni obtinute.
cout<<"Patratul are latura de "<<p.get_latura()<<endl;
cout<<"Cercul inscris in patrat are raza de "<<c1.get_raza()<<endl;
cout<<"Cercul circumscris patratului are raza de "<<c2.get_raza()<<endl;

84
return 0;
}

Obs. “Prietenia” între clase nu este simetrică. Dacă o clasă A are acces la membrii protejați ai
clasei B, reciproca nu este în mod automat adevărată. Pentru a obține o “prietenie” simetrică,
este necesară o dublă declarație (A trebuie declarată ca fiind friend pentru B și B trebuie
declarată ca fiind friend pentru A).
Obs. “Prietenia” între clase nu este tranzitivă. Dacă o clasă A este friend pentru o clasă B și
clasa B este friend pentru o clasă C, acest lucru nu implică faptul că A este friend pentru C.
Obs. Combinația de “prietenii” între funcții și clase nu este tranzitivă. Dacă o funcție F este
friend pentru o clasă A și clasa A este friend pentru o clasă B, acest lucru nu implică faptul că
F este friend pentru B.
Ca exercițiu de tehnică, să extindem exemplul precedent pentru a ilustra o “prietenie” simetrică
(o dublă “prietenie”):

#include <iostream>
//Declaram si biblioteca cmath. Vom avea nevoie de ea pentru sqrt
#include <cmath>

using namespace std;

//Declaram o clasa Patrat. Nu o definim inca.


//O vom defini mai tarziu, dar o declaram acum, pentru a putea
//spune in urmatoarea clasa, clasa Cerc ca niste functii au
//parametri de tip Patrat.
class Patrat;

//declaram o clasa Cerc


class Cerc
{
friend class Patrat;
//Atributele
double raza;
public:
//Constructorul implicit
Cerc(){}

//Constructorul explicit
Cerc(double x):raza(x){}

//Un getter
double get_raza()
{
return raza;
}

//Declaram aceasta functie, dar nu o putem scrie inca


//pentru ca in acest moment nu am dat inca definitia
//clasei Patrat, compilatorul nu ii cunoaste structura
//drept pentru care nu o putem folosi inca.
//Functia calculeaza ce raza ar trebui sa aiba cercul
//pentru a fi inscris in patratul primit ca parametru.

85
void inscris(Patrat);

//Declaram aceasta functie, dar nu o putem scrie inca


//pentru ca in acest moment nu am dat inca definitia
//clasei Patrat, compilatorul nu ii cunoaste structura
//drept pentru care nu o putem folosi inca.
//Functia calculeaza ce raza ar trebui sa aiba cercul
//pentru a fi circumscris patratului primit ca parametru.
void circumscris(Patrat);
};

class Patrat
{
//spunem ca clasa Cerc este clas friend, deci are voie
//sa acceseze membrii protejati ai clasei Patrat
friend class Cerc;
//Atributele
double latura;
public:
//Constructorul implicit
Patrat(){}

//Constructorul explicit
Patrat(double x):latura(x){}

//Un getter
double get_latura()
{
return latura;
}

//Functia calculeaza ce latura ar trebui sa aiba patratul


//pentru a fi inscris in cercul primit ca parametru.
//Spre deosebire de functia similara din clasa Cerc, nu e
//nevoie sa o definim mai tarziu, pentru ca in acest moment
//sunt deja cunoscute toate structurile de clase.
void inscris(Cerc param)
{
latura=param.raza*sqrt(2);
}

//Functia calculeaza ce latura ar trebui sa aiba patratul


//pentru a fi circumscris cercului primit ca parametru.
//Spre deosebire de functia similara din clasa Cerc, nu e
//nevoie sa o definim mai tarziu, pentru ca in acest moment
//sunt deja cunoscute toate structurile de clase.
void circumscris(Cerc param)
{
latura=param.raza*2;
}
};

//Acum sunt disponibile toate definitiile de clase necesare,


//asa ca putem scrie definitia functiei inscris din clasa Cerc.
void Cerc::inscris(Patrat param)
{
//Raza cercului inscris este jumatate din latura.

86
raza=param.latura/2;
}

//Acum sunt disponibile toate definitiile de clase necesare,


//asa ca putem scrie definitia functiei circumscris din clasa Cerc.
void Cerc::circumscris(Patrat param)
{
//Raza cercului circumscris este latura * radical din 2, pe 2.
raza=param.latura*sqrt(2)/2;
}

int main ()
{
//Declaram un patrat cu latura de 10 si doua patrate cu laturi necunoscute
Patrat p1(10), p2, p3;
//Declaram doua cercuri cu raza necunoscuta
Cerc c1, c2;
//Spunem despre primul cerc ca este inscris in patrat
c1.inscris(p1);
//Spunem despre al doilea cerc ca este circumscris patratului
c2.circumscris(p1);
//Spunem despre al doilea patrat ca este inscris in cercul pe care l-am
//circumscris primului patrat. Acest patrat ar trebui sa coincida cu primul patrat.
p2.inscris(c2);
//Spunem despre al treilea patrat ca este circumscris cercului pe care l-am
//inscris in primul patrat. Si acest patrat ar trebui sa coincida cu primul patrat.
p3.circumscris(c1);
//Afisam diversele dimensiuni obtinute.
cout<<"Patratul initial are latura de "<<p1.get_latura()<<endl;
cout<<"Cercul inscris in patratul initial are raza de "<<c1.get_raza()<<endl;
cout<<"Cercul circumscris patratului initial are raza de "<<c2.get_raza()<<endl;
cout<<"Patratul circumscris cercului inscris in patratul initial are latura
de "<<p2.get_latura()<<endl;
cout<<"Patratul inscris in cercul circumscris patratului initial are latura
de "<<p3.get_latura()<<endl;
return 0;
}

87
Secțiunea 07

Tutorialul 22 - Derivare și moștenire


În aplicații practice, se pune deseori problema implementării mai multor clase care au membri
(atribute și / sau metode) comuni. Într-o astfel de situație, clasele respective se pot implementa,
desigur, complet independente unele de celelalte dar, de cele mai multe ori, este mai eficient
să existe un mecanism prin care o clasă mai specializată să moștenească, în mod controlat,
membri ai unei alte clase mai puțin specializate, de așa natură încât să se obțină două avantaje
semnificative:
● O porțiune semnificativă a codului nu va mai trebui rescrisă;
● Dacă este necesară o modificare a codului la un moment dat, modificările efectuate pe
atributele și metodele moștenite vor fi transmise automat prin moștenire, nefiind
necesare multiple modificări ale codului și evitând și unele posibile greșeli de
implementare.
Unul dintre principiile de bază ale programării orientate pe obiecte se referă tocmai la acest
aspect, la abilitatea oferită de limbaj ca o clasă, numită clasă de bază, să fie moștenită de către
o altă clasă, numită clasă derivată. Ceea ce se transmite de la clasa de bază la clasa derivată
se numește moștenire, iar procesul în sine se numește derivare.
Obs. Denumirile de clasă de bază și clasă derivată sunt relative. O clasă B poate fi, în același
timp, clasă de bază pentru o clasă derivată C, dar și clasă derivată dintr-o altă clasă de bază A.
În C++ derivarea se face foarte ușor, specificând în antetul clasei derivate care este clasa de
bază pe care o moștenește. Se va specifica de asemenea care este nivelul de acces minim cu
care vor fi transmiși membrii moșteniți de la clasa de bază la clasa derivată.
Obs. Nu vor fi moștenite niciodată următoarele elemente care țin de clasa de bază:
● Constructorii și destructorul.
● Operatorul de atribuire (atât cel de copiere cât și cel de mutare).
● Funcțiile și clasele friend.
● Atributele și metodele specificate în clasa de bază ca fiind private (pentru a putea fi
moștenite, ele trebuie să aibe nivelul de acces cel mult protected).
Sintaxa utilizată pentru derivare în C++ este următoarea:
class numele_clasei_derivate: nivelul_minim_de_acces numele_clasei_de_bază
{
//codul clasei derivate
};

unde nivelul_minim_de_acces poate fi, la fel ca și nivelul de acces pentru membrii clasei, public,
protected sau private și reprezintă cel mai redus nivel de acces pe care îl vor avea atributele și
metodele moștenite în clasa derivată.

88
Rezultatele diverselor combinații de nivel de acces inițial al membrului din clasa de bază și
respectiv nivel minim de acces specificat la derivare se regăsesc în tabelul următor:

Dacă nivelul de
Și nivelul
acces inițial al
minim
membrului din
specificat la
clasa de bază a public protected private
derivare a fost:
fost:

↓ →
public Atunci nivelul public protected private
de acces al
membrului
protected moștenit în protected protected private
clasa derivată
este: nu va fi nu va fi nu va fi

private
moștenit moștenit moștenit

Obs. Specificarea nivelului minim de acces la derivare este opțională. Dacă programatorul nu
specifică niciun nivel minim de acces atunci:
● Dacă se folosește cuvântul cheie class pentru declararea clasei, nivelul minim de acces
asumat la derivare va fi private.
● Dacă se folosește cuvântul cheie struct pentru declararea clasei, nivelul minim de acces
asumat la derivare va fi public.
(a se revedea și Tutorialul 09).
Obs. Conceptul de moștenire nu este specific tuturor limbajelor de programare orientate pe
obiecte ci doar acelora bazate pe clase (ex. C++, C#, Java). Echivalentul moștenirii pentru
limbajele de programare orientate pe obiecte bazate pe prototipuri (ex. JavaScript, Self) este
delegarea4.
Obs. În marea majoritate a limbajelor de programare orientate obiect, bibliotecile puse la
dispoziție programatorului sunt alcătuite din definiții de clase mai mult sau mai puțin
specializate, derivate unele din altele pe multiple nivele. Obiectele instanțiate de către
programator din respectivele clase pot fi utilizate pentru prelucrări tipice de date sau pentru
alte acțiuni. Un exemplu tipic este utilizarea containerelor, care sunt clase template definite în
biblioteca standard a C++.
Obs. În unele limbaje de programare orientate pe obiecte (ex. Java) și respectiv în unele
biblioteci (ex. MFC - Microsoft Foundation Class5) sau framework-uri (ex. .Net Framework6)

4
https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming)
5
https://en.wikipedia.org/wiki/Microsoft_Foundation_Class_Library
6
https://docs.microsoft.com/en-us/dotnet/standard/class-library-overview

89
există o (unică) clasă de bază care este moștenită de către toate celelalte clase, fie că sunt
preluate dintr-o bibliotecă sau definite de către programator (această clasă de bază comună se
numește, pentru exemplele de mai sus, Object în Java7, CObject în MFC8 și respectiv Object
în .Net Framework9).
Exemplul următor ilustrează modul în care se face derivarea în C++. Se poate vedea ce se
întâmplă cu atributele și metodele moștenite:

#include <iostream>

using namespace std;

//Clasa de baza.
class Persoana
{
//Atributele din clasa de baza.
//Le acordam nivelul de acces protected in loc de
//acel private implicit folosit in exemplele anterioare
//pentru a le putea mosteni.
protected:
string nume, prenume, CNP;
public:
//Constructorul implicit din clasa de baza.
//Nu va fi mostenit chiar daca e public.
Persoana(){}

//Un constructor explicit din clasa de baza.


//Nu va fi mostenit chiar daca e public.
Persoana(string nume, string prenume, string CNP):
nume(nume),
prenume(prenume),
CNP(CNP){}

//Setter-ii si getter-ii din clasa de baza.


//Sunt publici asa ca vor fi toti mosteniti.
void set_nume(string nume)
{
this->nume=nume;
}

string get_nume()
{
return nume;
}

void set_prenume(string prenume)


{
this->prenume=prenume;
}

string get_prenume()

7
https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
8
https://docs.microsoft.com/en-us/cpp/mfc/reference/cobject-class?view=vs-2019
9
https://docs.microsoft.com/en-us/dotnet/api/system.object?view=netframework-4.8

90
{
return prenume;
}

void set_CNP(string CNP)


{
this->CNP=CNP;
}

string get_CNP()
{
return CNP;
}
};

//Clasa derivata. Se observa modul in care se specifica faptul ca va mosteni clasa de baza.
//Se observa de asemenea faptul ca nivelul minim de acces pe care il vor avea atributele si
//metodele mostenite va fi public.
class Student:public Persoana
{
//Atributele suplimentare din clasa derivata.
string nr_matricol, grupa;
public:
//Constructorul implicit din clasa derivata.
Student(){}

//Un constructor explicit din clasa derivata.


//Va avea acces atat la atributele mostenite cat si la cele proprii, dar atributele
//mostenite nu au chiar acelasi statut cu cele proprii, asa ca nu pot fi initializate
//in antetul constructorului ci trebuie initializate in corpul acestuia.
Student(string nume, string prenume, string CNP, string nr_matricol, string grupa):
nr_matricol(nr_matricol),
grupa(grupa)
{
this->nume=nume;
this->prenume=prenume;
this->CNP=CNP;
}

//Setter-ii si getter-ii suplimentari din clasa derivata.


//Nu e nevoie sa scriu setter-i si getter-i pentru atributele mostenite din clasa de
//baza, pentru ca se mostenesc nu numai atribute ci si metode.
void set_nr_matricol(string nr_matricol)
{
this->nr_matricol=nr_matricol;
}

string get_nr_matricol()
{
return nr_matricol;
}

void set_grupa(string grupa)


{
this->grupa=grupa;
}

91
string get_grupa()
{
return grupa;
}
};

int main()
{
//Obiecte instantiate din clasa de baza.
Persoana a;
Persoana a2("Georgescu","Marian","1780615299974");
//Obiecte instantiate din clasa derivata.
Student b;
Student b2("Popa","Simona","2770920299962","10101","50302");
//Doua dintre obiecte au fost create folosind constructorii
//impliciti, asa ca nu contin date.
//Vom folosi setter-ii pentru a le da continut.
a.set_nume("Ionescu");
a.set_prenume("Marcela");
a.set_CNP("2800101299997");

//Se observa ca, chiar daca in clasa Student nu am scris setter-i pentru nume, prenume
//si CNP, nici nu este necesar, pentru ca au fost mosteniti cei din clasa Persoana si
//pot fi folositi fara probleme:
b.set_nume("Popescu");
b.set_prenume("Emanuel");
b.set_CNP("1791231299985");
b.set_nr_matricol("10000");
b.set_grupa("50301");

//Sa facem si afisari, ca sa vedem ca totul a decurs fara probleme.


//Vom folosi getter-ii corespunzatori:
cout<<"O persoana: "<<endl;
cout<<a.get_nume()<<" "<<a.get_prenume()<<", "<<a.get_CNP()<<endl;
cout<<"O alta persoana: "<<endl;
cout<<a2.get_nume()<<" "<<a2.get_prenume()<<", "<<a2.get_CNP()<<endl;

//Se observa ca, chiar daca in clasa Student nu am scris getter-i pentru nume, prenume
//si CNP, nici nu este necesar, pentru ca au fost mosteniti cei din clasa Persoana si
//pot fi folositi fara probleme:
cout<<"Un student: "<<endl;
cout<<b.get_nume()<<" "<<b.get_prenume()<<", "<<b.get_CNP()<<", "<<
b.get_nr_matricol()<<", "<<b.get_grupa()<<endl;
cout<<"Inca un student: "<<endl;
cout<<b2.get_nume()<<" "<<b2.get_prenume()<<", "<<b2.get_CNP()<<", "<<
b2.get_nr_matricol()<<", "<<b2.get_grupa()<<endl;
return 0;
}

Obs. Constructorii clasei de bază, chiar dacă nu pot moșteniți direct (apar pe lista de excepții
de la moștenire furnizată mai sus), sunt utilizați în mod automat și pot fi utilizați în mod
controlat în constructorii clasei derivate. Dacă în constructorul clasei derivate nu se specifică
nimic în acest sens, apelarea unui constructor oarecare al clasei derivate implică apelarea
automată (invizibilă pentru programator) a constructorului implicit din clasa de bază.

92
Pe de altă parte, programatorul poate opta să controleze ce constructor al clasei de bază să fie
apelat automat la apelarea unui constructor oarecare din clasa derivata, precum se va vedea în
exemplul următor. Acest program reia codul din programul anterior, exceptând faptul că în
constructorul explicit al clasei derivate se va face referire directă la constructorul explicit al
clasei de bază. În acest mod se va reduce puțin lungimea codului și în același timp, ne vom
asigura ca o eventuală modificare făcută în constructorul clasei de bază se va propaga și în
clasa derivată, fără a fi nevoie să modificăm codul în două locuri (N.B. o parte din codul acestui
program va fi reluat și la Tutorialul 23):

#include <iostream>

using namespace std;

//Clasa de baza.
class Persoana
{
//Atributele din clasa de baza.
//Le acordam nivelul de acces protected in loc de
//acel private implicit folosit in exemplele anterioare
//pentru a le putea mosteni.
protected:
string nume, prenume, CNP;
public:
//Constructorul implicit din clasa de baza.
//Nu va fi mostenit chiar daca e public.
Persoana(){}

//Un constructor explicit din clasa de baza.


//Nu va fi mostenit chiar daca e public.
Persoana(string nume, string prenume, string CNP):
nume(nume),
prenume(prenume),
CNP(CNP){}

//Setter-ii si getter-ii din clasa de baza.


//Sunt publici asa ca vor fi toti mosteniti.
void set_nume(string nume)
{
this->nume=nume;
}

string get_nume()
{
return nume;
}

void set_prenume(string prenume)


{
this->prenume=prenume;
}

string get_prenume()
{
return prenume;

93
}

void set_CNP(string CNP)


{
this->CNP=CNP;
}

string get_CNP()
{
return CNP;
}
};

//Clasa derivata. Se observa modul in care se specifica faptul ca va mosteni clasa de baza.
//Se observa de asemenea faptul ca nivelul minim de acces pe care il vor avea atributele si
//metodele mostenite va fi public.
class Student:public Persoana
{
//Atributele suplimentare din clasa derivata.
string nr_matricol, grupa;
public:
//Constructorul implicit din clasa derivata.
Student(){}

//Un constructor explicit din clasa derivata.


//Va avea acces atat la atributele mostenite cat si la cele proprii.
Student(string nume, string prenume, string CNP, string nr_matricol, string grupa):
//Pe linia urmatoare spunem ca, chiar daca nu poate fi mostenit
//constructorul explicit din clasa Persoana, acesta va fi utilizat in apelul
//constructorului explicit al clasei Student.
//Daca nu am fi facut acest lucru, aici ar fi fost inclus automat, invizibil pentru
//noi, constructorul implicit din clasa de baza.
Persoana(nume, prenume, CNP),
nr_matricol(nr_matricol),
grupa(grupa){}

//Setter-ii si getter-ii suplimentari din clasa derivata.


//Nu e nevoie sa scriu setter-i si getter-i pentru atributele mostenite din clasa de
//baza, pentru ca se mostenesc nu numai atribute ci si metode.
void set_nr_matricol(string nr_matricol)
{
this->nr_matricol=nr_matricol;
}

string get_nr_matricol()
{
return nr_matricol;
}

void set_grupa(string grupa)


{
this->grupa=grupa;
}

string get_grupa()
{
return grupa;

94
}
};

int main()
{
//Obiecte instantiate din clasa de baza.
Persoana a;
Persoana a2("Georgescu","Marian","1780615299974");
//Obiecte instantiate din clasa derivata.
Student b;
Student b2("Popa","Simona","2770920299962","10101","50302");
//Doua dintre obiecte au fost create folosind constructorii
//impliciti, asa ca nu contin date.
//Vom folosi setter-ii pentru a le da continut.
a.set_nume("Ionescu");
a.set_prenume("Marcela");
a.set_CNP("2800101299997");

//Se observa ca, chiar daca in clasa Student nu am scris setter-i pentru nume, prenume
//si CNP, nici nu este necesar, pentru ca au fost mosteniti cei din clasa Persoana si
//pot fi folositi fara probleme:
b.set_nume("Popescu");
b.set_prenume("Emanuel");
b.set_CNP("1791231299985");
b.set_nr_matricol("10000");
b.set_grupa("50301");

//Sa facem si afisari, ca sa vedem ca totul a decurs fara probleme.


//Vom folosi getter-ii corespunzatori:
cout<<"O persoana: "<<endl;
cout<<a.get_nume()<<" "<<a.get_prenume()<<", "<<a.get_CNP()<<endl;
cout<<"O alta persoana: "<<endl;
cout<<a2.get_nume()<<" "<<a2.get_prenume()<<", "<<a2.get_CNP()<<endl;

//Se observa ca, chiar daca in clasa Student nu am scris getter-i pentru nume, prenume
//si CNP, nici nu este necesar, pentru ca au fost mosteniti cei din clasa Persoana si
//pot fi folositi fara probleme:
cout<<"Un student: "<<endl;
cout<<b.get_nume()<<" "<<b.get_prenume()<<", "<<b.get_CNP()<<", "<<
b.get_nr_matricol()<<", "<<b.get_grupa()<<endl;
cout<<"Inca un student: "<<endl;
cout<<b2.get_nume()<<" "<<b2.get_prenume()<<", "<<b2.get_CNP()<<", "<<
b2.get_nr_matricol()<<", "<<b2.get_grupa()<<endl;
return 0;
}

Programul următor, mult simplificat, ilustrează mai bine modul în care constructorii din clasa
de baza ajung să fie apelați automat de către constructorii clasei derivate. Exemplul mai
ilustrează și faptul că două sau mai multe clase derivate pot moșteni aceeași clasă de bază:

#include <iostream>

using namespace std;

//O clasa de baza.


class Baza

95
{
//Atributul care va fi mostenit.
protected:
int x;
public:
//Constructorul implicit al clasei de baza. Nu va fi mostenit dar va fi apelat
//automat la apelarea unor constructori din clasele derivate.
Baza()
{
cout<<"A fost apelat constructorul implicit al clasei de baza."<<endl;
}

//Un constructor explicit al clasei de baza. Nu va fi mostenit dar va fi apelat


//automat la apelarea unor constructori din clasele derivate.
Baza(int x):x(x)
{
cout<<"A fost apelat constructorul explicit al clasei de baza."<<endl;
}
};

//O prima clasa derivata


class Derivata1:public Baza
{
public:
//Constructorul implicit al primei clase derivate.
//Nu este vizibil acest lucru, dar el face apel automat la constructorul implicit din
//clasa de baza.
Derivata1()
{
cout<<"A fost apelat constructorul implicit al clasei derivate 1."<<endl;
}

//Un constructor explicit al primei clase derivate.


//Nu este vizibil acest lucru, dar el face apel automat la constructorul implicit din
//clasa de baza.
Derivata1(int x)
{
this->x=x;
cout<<"A fost apelat constructorul explicit al clasei derivate 1."<<endl;
}
};

//O alta clasa derivata


class Derivata2:public Baza
{
public:
//Constructorul implicit al primei clase derivate.
//Nu este vizibil acest lucru, dar el face apel automat la constructorul implicit din
//clasa de baza.
Derivata2()
{
cout<<"A fost apelat constructorul implicit al clasei derivate 2."<<endl;
}

//Un constructor explicit al primei clase derivate.


//Aici programatorul solicita apelarea automata a constructorului explicit din
//clasa de baza.

96
Derivata2(int x):Baza(x)
{
cout<<"A fost apelat constructorul explicit al clasei derivate 2."<<endl;
}
};

int main ()
{
cout<<"Ce se intampla de fapt cand cream, fara parametri,
un obiect din clasa derivata 1:"<<endl;
Derivata1 a;
cout<<"Ce se intampla de fapt cand cream, cu parametri,
un obiect din clasa derivata 1:"<<endl;
Derivata1 b(0);
cout<<"Ce se intampla de fapt cand cream, fara parametri,
un obiect din clasa derivata 2:"<<endl;
Derivata2 c;
cout<<"Ce se intampla de fapt cand cream, cu parametri,
un obiect din clasa derivata 2:"<<endl;
Derivata2 d(0);
return 0;
}

Tutorialul 23 - Moștenire multiplă

În C++, ca și în alte limbaje de programare orientate obiect (nu în toate; mai multe informații
despre acest subiect pot fi găsite aici: https://en.wikipedia.org/wiki/Multiple_inheritance) este
posibil ca o clasă derivată să moștenească membri din mai multe clase de bază simultan.
Exemplul următor demonstrează modul în care două clase derivate (Student și Profesor)
moștenesc, fiecare, două clase de bază (Iesire si Persoana). Schema de derivare este cea din
figură:

#include <iostream>

using namespace std;

//O prima clasa de baza.


//Sa zicem ca este o clasa care sa se ocupa de operatii de iesire

97
//(in cazul de fata avem o singura metoda de afisare, dar intr-o aplicatie
//reala lucrurile ar putea fi mai complexe).
class Iesire
{
public:
//Putem sa declaram (sau nu) aceasta unica functie din aceasta clasa ca fiind
//statica pentru a face utilizarea ei mai flexibila (a se vedea comentariul din main).
static void afisare(string s1, string s2, string s3, string s4, string s5)
{
cout<<s1<<" "<<s2<<", "<<s3<<", "<<s4<<", "<<s5<<endl;
}
};

//O alta clasa de baza.


//O clasa folosita pentru stocarea datelor.
class Persoana
{
//Atributele din clasa de baza.
//Le acordam nivelul de acces protected in loc de
//acel private implicit folosit in exemplele anterioare
//pentru a le putea mosteni.
protected:
string nume, prenume, CNP;
public:
//Constructorul implicit din clasa de baza.
//Nu va fi mostenit chiar daca e public.
Persoana(){}

//Un constructor explicit din clasa de baza.


//Nu va fi mostenit chiar daca e public.
Persoana(string nume, string prenume, string CNP):
nume(nume),
prenume(prenume),
CNP(CNP){}

//Setter-ii si getter-ii din clasa de baza.


//Sunt publici asa ca vor fi toti mosteniti.
void set_nume(string nume)
{
this->nume=nume;
}

string get_nume()
{
return nume;
}

void set_prenume(string prenume)


{
this->prenume=prenume;
}

string get_prenume()
{
return prenume;
}

98
void set_CNP(string CNP)
{
this->CNP=CNP;
}

string get_CNP()
{
return CNP;
}
};

//O prima clasa derivata.


//Se observa modul in care mosteneste doua clase,
//cea pentru stocarea datelor si cea pentru operatii de iesire.
class Student:public Persoana, public Iesire
{
//Atributele suplimentare din clasa derivata.
string nr_matricol, grupa;
public:
//Constructorul implicit din clasa derivata.
Student(){}

//Un constructor explicit din clasa derivata.


//Va avea acces atat la atributele mostenite cat si la cele proprii.
Student(string nume, string prenume, string CNP, string nr_matricol, string grupa):
//Pe linia urmatoare spunem ca, chiar daca nu poate fi mostenit
//constructorul explicit din clasa Persoana, acesta va fi utilizat in apelul
//constructorului explicit al clasei Student.
//Daca nu am fi facut acest lucru, aici ar fi fost inclus automat, invizibil pentru
//noi, constructorul implicit din clasa de baza.
Persoana(nume, prenume, CNP),
nr_matricol(nr_matricol),
grupa(grupa){}

//Setter-ii si getter-ii suplimentari din clasa derivata.


//Nu e nevoie sa scriu setter-i si getter-i pentru atributele mostenite din clasa de
//baza, pentru ca se mostenesc nu numai atribute ci si metode.
void set_nr_matricol(string nr_matricol)
{
this->nr_matricol=nr_matricol;
}

string get_nr_matricol()
{
return nr_matricol;
}

void set_grupa(string grupa)


{
this->grupa=grupa;
}

string get_grupa()
{
return grupa;
}
};

99
//O a doua clasa derivata.
//Se observa modul in care mosteneste doua clase,
//cea pentru stocarea datelor si cea pentru operatii de iesire.
class Profesor:public Persoana, public Iesire
{
//Atributele suplimentare din clasa derivata.
string id_angajat, departament;
public:
//Constructorul implicit din clasa derivata.
Profesor(){}

//Un constructor explicit din clasa derivata.


//Va avea acces atat la atributele mostenite cat si la cele proprii.
Profesor(string nume, string prenume, string CNP, string id_angajat, string departament):
//Pe linia urmatoare spunem ca, chiar daca nu poate fi mostenit
//constructorul explicit din clasa Persoana, acesta va fi utilizat in apelul
//constructorului explicit al clasei Student.
//Daca nu am fi facut acest lucru, aici ar fi fost inclus automat, invizibil pentru
//noi, constructorul implicit din clasa de baza.
Persoana(nume, prenume, CNP),
id_angajat(id_angajat),
departament(departament){}

//Setter-ii si getter-ii suplimentari din clasa derivata.


//Nu e nevoie sa scriu setter-i si getter-i pentru atributele mostenite din clasa de
//baza, pentru ca se mostenesc nu numai atribute ci si metode.
void set_id_angajat(string id_angajat)
{
this->id_angajat=id_angajat;
}

string get_id_angajat()
{
return id_angajat;
}

void set_departament(string departament)


{
this->departament=departament;
}

string get_departament()
{
return departament;
}
};

int main()
{
//Un obiect instantiat din prima clasa derivata.
Student b("Popa","Simona","2770920299962","10101","50302");
//Un obiect instantiat din a doua clasa derivata.
Profesor c("Ionescu","Teodor","1670920299951","130","CIF");

//Sa facem si afisari, ca sa vedem ca totul a decurs fara probleme.


//Vom folosi functia de afisare:

100
cout<<"Un student: "<<endl;
b.afisare(b.get_nume(), b.get_prenume(), b.get_CNP(), b.get_nr_matricol(),
b.get_grupa());
cout<<"Un profesor: "<<endl;
c.afisare(c.get_nume(), c.get_prenume(), c.get_CNP(), c.get_id_angajat(),
c.get_departament());

//Observatie. Daca functia de afisare a fost declarata ca fiind statica,


//ea poate fi utilizata si facand referire la clasa de care apartine,
//in loc sa facem referire la un obiect anume (a se revedea si Tutorialul 12).
//Deci, daca metoda afisare din clasa Iesire este statica, atunci afisarea
//se poate face si asa. Altfel ... eroare :)
cout<<"Acelasi lucru, afisat din nou ..."<<endl;
cout<<"Un student: "<<endl;
Student::afisare(b.get_nume(), b.get_prenume(), b.get_CNP(), b.get_nr_matricol(),
b.get_grupa());
cout<<"Un profesor: "<<endl;
Profesor::afisare(c.get_nume(), c.get_prenume(), c.get_CNP(), c.get_id_angajat(),
c.get_departament());

return 0;
}

Obs. În anumite condiții, moștenirea multiplă poate duce la situații ambigue. Această problemă
este numită problema diamant. Mai multe informații despre acest subiect pot fi găsite atât pe
pagina menționată mai sus, cât și în alte surse. O modalitate simplă de a provoca apariția
problemei diamant este să moștenim și să remoștenim una și aceeași clasa la diversele nivele
ale unei ierarhii de moșteniri. O demonstrație imediată poate fi obținută adăugând, în programul
anterior, o moștenire a clasei Ieșire și în clasa Persoană. Schema de derivare este acum cea din
figură:

//Acelasi program, dar ...


#include <iostream>

101
using namespace std;

class Iesire
{
//...
};

//Singura diferenta este ca adaug o mostenire a clasei Iesire la clasa Persoana


class Persoana: public Iesire
{
//...
};

//Acum metoda afisare este mostenita, in mod ambiguu, direct de catre clasa Student dar si
//indirect, prin intermediul clasei Persoana. Astfel provocam aparitia problemei diamant.
class Student: public Persoana, public Iesire
{
//...
};

//Si aici metoda afisare este mostenita, in mod ambiguu, direct de catre clasa Profesor dar
//si indirect, prin intermediul clasei Persoana. Si aici apare problema diamant.
class Profesor: public Persoana, public Iesire
{
//...
};

int main()
{
//...
}

Obs. Există multiple modalități de rezolvare a problemei diamant 10, specifice diverselor
limbaje în care apare această problemă.

10
În C++, de exemplu, o metodă de rezolvare a problemei diamant este înlocuirea moștenirii multiple cu o
moștenire virtuală (mai multe informații despre membri virtuali ai claselor în Tutorialul 25), așa cum este
explicat în https://medium.freecodecamp.org/multiple-inheritance-in-c-and-the-diamond-problem-
7c12a9ddbbec

102
Secțiunea 08

Tutorialul 24 - Polimofism

Se spune despre un limbaj orientat pe obiecte că are proprietatea de polimofism (unul dintre
cele patru principii fundamentale ale programării orientate pe obiecte), dacă un obiect
aparținând unei clase derivate dintr-o altă clasă poate fi tratat atât ca aparținând clasei derivate
cât și ca făcând parte din clasa de bază.
Un exemplu care ilustrează acest principiu fundamental este:

#include <iostream>
#include <string>

using namespace std;

//Clasa de baza
class Persoana
{
string nume, prenume;
public:
Persoana(){}
Persoana(string a, string b):nume(a),prenume(b){}
string get_nume(){return nume;}
string get_prenume(){return prenume;}
};

//O clasa derivata


class Student: public Persoana
{
string grupa, nr_matricol;
public:
Student(){}
Student(string a, string b, string c, string d):Persoana(a,b)
{
grupa=c;
nr_matricol=d;
}
string get_grupa(){return grupa;}
string get_nr_matricol(){return nr_matricol;}
};

//O alta clasa derivata


class Angajat: public Persoana
{
string departament, nr_matricol;
public:
Angajat(){}
Angajat(string a, string b, string c, string d):Persoana(a,b)
{
departament=c;
nr_matricol=d;

103
}
string get_departament(){return departament;}
string get_nr_matricol(){return nr_matricol;}
};

int main()
{
//Declar doi pointeri la obiecte din clasa persoana
Persoana *p1, *p2;
//Declar si initializez un pointer la un obiect din clasa student
Student *s=new Student("Ionescu","Marcel","50901","12345");
//Declar si initializez un pointer la un obiect din clasa angajat
Angajat *a=new Angajat("Popescu","Marcelina","Contabilitate","67890");
//Demonstratie de polimorfism:
//Apelez obiectul din clasa Student (clasa derivata) si ca obiect din clasa Persoana
//(clasa de baza):
p1=s;
//O alta demonstratie de polimorfism
//Apelez obiectul din clasa Angajat (clasa derivata) si ca obiect din clasa Persoana
//(clasa de baza):
p2=a;
//Acum pot folosi obiectul din clasa Student (clasa derivata) si ca obiect din clasa
//Persoana (clasa de baza):
cout<<"Numele ob. din clasa student folosit ca si ob. din clasa
persoana: "<<p1->get_nume()<<endl;
cout<<"Prenumele ob. din clasa student folosit ca si ob. din clasa
persoana: "<<p1->get_prenume()<<endl;
//Acum pot folosi obiectul din clasa Angajat (clasa derivata) si ca obiect din clasa
//Persoana (clasa de baza):
cout<<"Numele ob. din clasa angajat folosit ca si ob. din clasa
persoana: "<<p2->get_nume()<<endl;
cout<<"Prenumele ob. din clasa angajat folosit ca si ob. din clasa
persoana: "<<p2->get_prenume()<<endl;
return 0;
}

Obs. Exemplul de mai sus nu reprezintă o ilustrare completă a polimorfismului. Obiectele din
clasa de bază nu “arată” la fel cu cele din clasele derivate (cele din clasele derivate conțin
metode suplimentare, care nu există în clasa de bază și care nu sunt aceleași în cele două clase
derivate). Pentru a face ca obiectele din clasa de bază să “arate” la fel cu cele din clasele
derivate (să aibă aceleași atribute și metode), obținând astfel un polimorfism complet, sunt
necesare încă două mecanisme auxiliare (metodele virtuale și clasele de bază abstracte), care
vor fi prezentate în tutorialele următoare.

Întrebări de autoevaluare
1. Care dintre următoarele elemente din programarea orientată pe obiecte este suportat de
supraîncărcarea funcțiilor și caracteristicile implicite ale argumentelor C++?
A. Moştenirea
B. Polimorfismul
C. Încapsularea
D. Nici una dintre cele de mai sus

104
Răspunsuri la întrebări
1. B. Polimorfismul - supraîncărcarea funcțiilor este un element de bază pentru polimorfism,
precum s-a văzut în exemplele de mai sus.

Tutorialul 25 - Membri virtuali

Un membru virtual este o funcție membră (metodă) a unei clase de bază care poate fi
redefinită într-o clasă derivată, dar păstrând în același timp modul în care face apeluri prin
referință (metoda respectivă se apelează la fel și în clasa de bază și în clasa derivată, chiar dacă
nu este implementată la fel; ea nu este nici supraîncărcată, nici suprascrisă, ci doar se comportă
de parcă ar fi suprascrisă, dar numai în cazul în care un obiect este simultan din clasa de bază
și din cea derivată). Practic, o metodă virtuală a unui obiect din clasa de bază se comportă
diferit depinzând de faptul că obiectul respectiv este sau nu simultan și un obiect dintr-o clasă
derivată - dacă nu este și obiect dintr-o clasă derivată se va “manifesta” implementarea metodei
din clasa de bază, în caz contrar se va “manifesta” cea din clasa derivată corespunzătoare.
Pentru ca o funcție să devină virtuală ea trebuie precedată de cuvântul cheie virtual, precum se
poate vedea în exemplul următor:

#include <iostream>

using namespace std;

//O clasa de baza


class Poligon
{
protected:
int baza, inaltime;
public:
//Un setter pentru ambele atribute
void set_dimensiuni (int a, int b)
{
baza=a; inaltime=b;
}

//Daca nu cunoastem tipul de poligon, nu avem la dispozitie nici o metoda pentru a ii


//calcula aria. Ne dorim sa avem totusi la dispozitie o metoda numita "aria" si in
//clasa de baza, de asa natura incat obiectele din clasa de baza si cele din clasele
//derivate sa "arate" la fel. Dar, ne dorim ca aceasta metoda numita "aria" sa fie
//inlocuita, atunci cand e cazul, de o echivalenta a sa cu acelasi nume, dar cu o
//alta implementare, aflata intr-o clasa derivata. In acest scop folosim cuvantul
//cheie virtual:
virtual double aria ()
{
return 0;
}
};

//O prima clasa derivata


class Dreptunghi: public Poligon
{
public:

105
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime;
}
};

//O a doua clasa derivata


class Triunghi: public Poligon
{
public:
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime / 2;
}
};

int main ()
{
//Un obiect de tip dreptunghi
Dreptunghi d;
//Un obiect de tip triunghi
Triunghi t;
//Un obiect de tip poligon oarecare
Poligon p;
//Obiectul de tip dreptunghi utilizat ca un obiect de tip poligon
Poligon * p1 = &d;
//Obiectul de tip triunghi utilizat ca un obiect de tip poligon
Poligon * p2 = &t;
//Obiectul de tip poligon utilizat ca un obiect de tip poligon
Poligon * p3 = &p;
//Setam dimensiunile pentru cele trei poligoane
p1->set_dimensiuni (4,5);
p2->set_dimensiuni (4,5);
p3->set_dimensiuni (4,5);
//Afisam aria primului poligon. Acesta este la origine un dreptunghi,
//drept pentru care va fi utilizata metoda aria din clasa Dreptunghi,
//chiar daca obiectul este de tip Poligon (metoda aria din clasa de
//baza este virtuala, asa ca a fost inlocuita de metoda aria din clasa derivata)
cout << "Aria primului poligon (un dreptunghi) este: " << p1->aria() << endl;
//Afisam aria celui de-al doilea poligon. Acesta este la origine un triunghi,
//drept pentru care va fi utilizata metoda aria din clasa Triunghi,
//chiar daca obiectul este de tip Poligon (metoda aria din clasa de
//baza este virtuala, asa ca a fost inlocuita de metoda aria din clasa derivata)
cout << "Aria celui de-al doilea poligon (un triunghi) este: " << p2->aria() << endl;
//Afisam aria celui de-al treilea poligon. Acesta este la origine un poligon oarecare,
//drept pentru care va fi utilizata metoda aria din clasa Poligon
//(metoda aria din clasa de baza este virtuala, dar nu a avut de cine sa fie inlocuita)
cout << "Aria celui de-al treilea poligon (un poligon de tip necunoscut) este: " <<
p3->aria() << endl;
return 0;
}

106
Obs. O clasă care declară sau moștenește o funcție virtuală se numește o clasă polimorfă.

Tutorialul 26 - Clase de bază abstracte

Se numește clasă abstractă o clasă care poate fi utilizată doar ca o clasă de bază, neputând fi
și instanțiată (nu pot fi create obiecte dintr-o clasă abstractă). Metodele virtuale din clasele
abstracte nu au nevoie de definiție - definiția lor este înlocuită prin sintaxa “=0”. Această
sintaxă nu trebuie interpretată ca o atribuire.
Utilizarea sintaxei “=0” face ca o metodă virtuală să devină o metodă pur virtuală.
Clasele care conțin cel puțin o metodă pur virtuală devin clase abstracte.
Un exemplu de utilizare a unei clase abstracte se poate vedea în programul următor (o variantă
modificată a celui anterior):

#include <iostream>

using namespace std;

//O clasa de baza


class Poligon
{
protected:
int baza, inaltime;
public:
//Un setter pentru ambele atribute
void set_dimensiuni (int a, int b)
{
baza=a; inaltime=b;
}

//O metoda virtuala. Metoda este de fapt pur virtuala pentru ca am folosit sintaxa "=0"
//In acest moment clasa Poligon a devenit abstracta si nu mai poate fi instantiata
//(nu mai pot declara obiecte din aceasta clasa).
virtual double aria () =0;
};

//O prima clasa derivata


class Dreptunghi: public Poligon
{
public:
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime;
}
};

//O a doua clasa derivata


class Triunghi: public Poligon

107
{
public:
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime / 2;
}
};

int main ()
{
//Un obiect de tip dreptunghi
Dreptunghi d;
//Un obiect de tip triunghi
Triunghi t;
//Obiectul de tip dreptunghi utilizat ca un obiect de tip poligon
Poligon * p1 = &d;
//Obiectul de tip triunghi utilizat ca un obiect de tip poligon
Poligon * p2 = &t;
//Setam dimensiunile pentru cele doua poligoane
p1->set_dimensiuni (4,5);
p2->set_dimensiuni (4,5);
//Afisam aria primului poligon. Acesta este la origine un dreptunghi,
//drept pentru care va fi utilizata metoda aria din clasa Dreptunghi,
//chiar daca obiectul este de tip Poligon (metoda aria din clasa de
//baza este virtuala, asa ca a fost inlocuita de metoda aria din clasa derivata)
cout << "Aria primului poligon (un dreptunghi) este: " << p1->aria() << endl;
//Afisam aria celui de-al doilea poligon. Acesta este la origine un triunghi,
//drept pentru care va fi utilizata metoda aria din clasa Triunghi,
//chiar daca obiectul este de tip Poligon (metoda aria din clasa de
//baza este virtuala, asa ca a fost inlocuita de metoda aria din clasa derivata)
cout << "Aria celui de-al doilea poligon (un triunghi) este: " << p2->aria() << endl;
return 0;
}

Obs. La prima vedere transformarea unei clase în clasă abstractă nu aduce decât dezavantaje
(nu mai putem instanția obiecte din clasa respectivă). Pe de altă parte se pot utiliza pointeri la
clasa abstractă, precum s-a văzut în exemplul anterior și acest lucru duce la nașterea unui
mecanism interesant de lucru - o metodă din clasa abstractă poate apela, în funcție de context,
metode din clasele derivate, lucru în mod normal imposibil. Exemplul următor reia exemplul
anterior dar profită de acest nou mecanism de lucru pentru a face afișarea mai flexibilă:

#include <iostream>

using namespace std;

//O clasa de baza


class Poligon
{
protected:
int baza, inaltime;
public:
//Un setter pentru ambele atribute

108
void set_dimensiuni (int a, int b)
{
baza=a; inaltime=b;
}

//O metoda virtuala. Metoda este de fapt pur virtuala pentru ca am folosit sintaxa "=0"
//In acest moment clasa Poligon a devenit abstracta si nu mai poate fi instantiata
//(nu mai pot declara obiecte din aceasta clasa).
virtual double aria () =0;

void afisare()
{
//Ne aflam in clasa Poligon, deci instructiunea de mai jos ar trebui sa afiseze
//aria asa cum este ea implementata (sau mai bine zis cum nu este) in clasa
//Poligon.
//Dar metoda aria este o metoda virtuala si clasa poligon este o clasa abstracta,
//astfel incat va fi preluata implementarea metodei aria din acea clasa derivata
//care corespunde contextului:
cout << "Aria primului poligonului este: " << this->aria() << endl;
}
};

//O prima clasa derivata


class Dreptunghi: public Poligon
{
public:
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime;
}
};

//O a doua clasa derivata


class Triunghi: public Poligon
{
public:
//Cele doua clase derivate difera practic prin modul
//in care se calculeaza aria. Vom avea doua immplementari
//diferite ale unei metode numite la fel.
double aria ()
{
return baza * inaltime / 2;
}
};

int main ()
{
//Un obiect de tip dreptunghi
Dreptunghi d;
//Un obiect de tip triunghi
Triunghi t;
//Obiectul de tip dreptunghi utilizat ca un obiect de tip poligon
Poligon * p1 = &d;
//Obiectul de tip triunghi utilizat ca un obiect de tip poligon

109
Poligon * p2 = &t;
//Setam dimensiunile pentru cele doua poligoane
p1->set_dimensiuni (4,5);
p2->set_dimensiuni (4,5);
p1->afisare();
p2->afisare();
return 0;
}

Obs. În sfârșit, este adevărat că nu pot fi create obiecte din clasele abstracte dar, pe de altă
parte, esența polimorfismului este că obiectele, atât cele din clasa de bază cât și cele din clasele
derivate, “arată” la fel, astfel încât, chiar dacă nu putem instanția obiecte din clasa abstractă,
nu ne oprește nimic să trimitem pointeri din clasa de baza abstractă către obiecte din clasele
derivate, precum se va vedea în exemplul următor (exemplul este o combinație a mai multor
tehnici văzute în tutorialele precedente):

#include <iostream>

using namespace std;

//O clasa de baza


class Poligon
{
protected:
int baza, inaltime;
public:
//Un constructor explicit
Poligon (int baza, int inaltime): baza(baza), inaltime(inaltime){}

//O metoda pur virtuala. In acest moment clasa Poligon a devenit abstracta.
virtual double aria () =0;

//O metoda pentru afisare


void afisare()
{
cout << "Aria primului poligonului este: " << this->aria() << endl;
}
};

//O prima clasa derivata


class Dreptunghi: public Poligon
{
public:
//Un constructor explicit care utilizeaza constructorul din clasa de baza
Dreptunghi (int baza, int inaltime):Poligon(baza, inaltime){}

double aria ()
{
return baza * inaltime;
}
};

//O a doua clasa derivata


class Triunghi: public Poligon
{

110
public:
//Un constructor explicit care utilizeaza constructorul din clasa de baza
Triunghi (int baza, int inaltime):Poligon(baza, inaltime){}

double aria ()
{
return baza * inaltime / 2;
}
};

int main ()
{
//Un pointer la tipul Poligon dar care indica spre un obiect nou de tip Dreptunghi
Poligon * p1 = new Dreptunghi(4,5);
//Un pointer la tipul Poligon dar care indica spre un obiect nou de tip Triunghi
Poligon * p2 = new Triunghi(4,5);
p1->afisare();
p2->afisare();
//Lucram cu obiecte alocate dinamic, deci nu trebuie sa uitam sa facem si eliberarea
//memoriei:
delete p1;
delete p2;
return 0;
}

Obs. Spuneam în tutorialul 22 că, în marea majoritate a limbajelor de programare orientate


obiect, bibliotecile puse la dispoziție programatorului sunt practic ierarhii de clase, derivate
unele din altele pe multiple nivele. Acum putem adăuga la această observație și faptul că
rădăcinile acestor ierarhii de clase sau diverse etaje intermediare sunt de multe ori clase
abstracte. Câteva exemple de astfel de “rădăcini” de ierarhii pentru .Net Framework (pachet de
biblioteci utilizate în C# și Visual Basic) pot fi văzute în figura următoare:

111
112
Secțiunea 09 - Aplicații rezolvate și aplicații propuse

Aplicații rezolvate

Aplicația 01
Să se implementeze clasa Triunghi cu atribute lungimile laturilor și să se implementeze o
funcție friend care să constate dacă se poate forma într-adevăr un triunghi cu laturi de lungimile
furnizate și o altă funcție friend care să returneze tipul triunghiului.
#include <iostream>

using namespace std;

class Triunghi
{
double lat1, lat2, lat3;
public:
Triunghi(double a, double b, double c):lat1(a),lat2(b),lat3(c){}
friend bool este_triunghi(Triunghi);
friend string tipul_triunghiului(Triunghi);
};

bool este_triunghi(Triunghi t)
{
bool rez=true;
if((t.lat1>=t.lat2+t.lat3)||(t.lat2>=t.lat1+t.lat3)||(t.lat3>=t.lat1+t.lat2))
rez=false;
return rez;
}

string tipul_triunghiului(Triunghi t)
{
//daca toate laturile sunt egale
if((t.lat1==t.lat2)&&(t.lat2==t.lat3))
return "echilateral";
//daca se respecta teorema lui Pitagora
if((t.lat1*t.lat1==t.lat2*t.lat2+t.lat3*t.lat3)||
(t.lat2*t.lat2==t.lat1*t.lat1+t.lat3*t.lat3)||
(t.lat3*t.lat3==t.lat1*t.lat1+t.lat2*t.lat2))
//daca, suplimentar, avem si doua laturi egale
if((t.lat1==t.lat2)||(t.lat2==t.lat3)||(t.lat1==t.lat3))
return "dreptunghic isoscel";
//sau nu
else
return "dreptunghic";
//daca numai doua laturi sunt egale
if((t.lat1==t.lat2)||(t.lat2==t.lat3)||(t.lat1==t.lat3))
return "isoscel";
//daca nicio conditie din cele de mai sus nu a fost adevarata si am ajuns pana aici
return "oarecare";
}

int main()
{

113
Triunghi t1(3,4,5);
cout<<"Este t1 triunghi (1=adevarat, 0=fals)? "<<este_triunghi(t1)<<endl;
if(este_triunghi(t1))
cout<<"Triunghiul t1 este un triunghi "<<tipul_triunghiului(t1)<<".";
return 0;
}

Obs. Implementarea de mai sus este o implementare de “școală”, care nu ține cont de
problemele ridicate de precizia tipurilor de date. Greșeala este că, la verificarea aplicabilității
teoremei lui Pitagora, implementarea nu ține cont de faptul că e posibil ca egalitatea să nu fie
confirmată, chiar dacă, aparent, ar trebui să fie. Acest lucru se datorează trunchierilor /
rotunjirilor efectuate la calcule (de noi sau de către calculator).
Spre exemplu, pentru niște mărimi ale laturilor de 1, 1 și 1.41 (1, 1 și ceea ce aproximăm noi
ca fiind√2), programul va afișa că triunghiul este doar isoscel, nu dreptunghic isoscel, cum
credem noi că este. Și dacă vom crește precizia cu care furnizăm valorile, nu vom rezolva
realmente problema (valorile 1, 1 și 1.4142135623731 vor duce tot la concluzia că triunghiul
este doar isoscel și nu dreptunghic isoscel, pentru că nu am ajuns încă la precizia solicitată de
tipul double - 17 zecimale exacte). Problema ar fi cu adevărat complet rezolvată numai dacă
ne-am scrie propria funcție de constatare a “egalității”, funcție care să țină cont de precizia
tipului de date folosit (aici double) și care să admită o anumită imprecizie în egalitate.
Acest detaliu de implementare nu ține, oricum, de programarea orientată pe obiecte, așa că vom
considera implementarea de mai sus ca fiind corectă.

Aplicația 02
Pornind de la clasa de baza Persoana (atribute: nume, prenume; metode: un constructor cu
parametri) să se implementeze clasa derivata Student (atribute: grupa, media; metode:
constructor cu parametri, metoda care returnează valoarea mediei). Să se definească o funcție
friend pentru clasa Student care sa determine dacă media este mai mare sau egală cu 8.50.
#include <iostream>

using namespace std;

class Persoana
{
protected:
string nume, prenume;
public:
Persoana(string a, string b):nume(a), prenume(b){}
};

class Student:public Persoana


{
string grupa;
double media;
public:
Student(string a, string b, string c, double x):
Persoana(a,b),
grupa(c),
media(x){}

114
double get_medie()
{
return media;
}

string get_nume()
{
return nume;
}

string get_prenume()
{
return prenume;
}

friend bool este_mai_mare(Student);


};

bool este_mai_mare(Student s)
{
if(s.media>=8.5)
return true;
else
return false;
}

int main()
{
Student s1("Ionescu","Maria","50999",8.51);
cout<<"Are "<<s1.get_nume()<<" "<<s1.get_prenume()<<
" media mai mare sau egala cu 8.50 (1=adevarat, 0=fals)? "<<
este_mai_mare(s1);
return 0;
}

Aplicații propuse

Aplicația 01
Să se implementeze o clasa Dreptunghi și să se realizeze o metoda de calcul a ariei și o funcție
friend care calculează perimetrul.

Aplicația 02
Să se implementeze clasa Carte cu atribute autor și ISBN derivata din clasa Publicatie care are
atributele nume și număr de pagini.

Aplicația 03
Să se implementeze clasa Dreptunghi cu atribute lungimile laturilor și să se implementeze o
funcție friend care calculează aria figurii formate prin alăturarea a doua dreptunghiuri.

115
Aplicația 04
Definiți o clasă denumită Triunghi având ca date membru trei elemente (A, B, C) de tip Punct
(x,y). Alături de constructorii implicit și cel cu parametri, Includeți în clasă metode pentru
calculul dimensiunii laturilor, a perimetrului și a ariei triunghiului. Realizați un program care
să citească de la tastatură coordonatele vârfurilor unui triunghi și să afișeze informațiile despre
acesta accesibile prin intermediul metodelor clasei.

Aplicația 05
Să se implementeze clasa Automobil cu atribute: culoare, număr de uși, marca, clasă care este
derivata din clasa de baza Masina cu atribute: număr de roți, greutate.

Aplicația 06
Să se implementeze clasa numerelor raționale care conține ca atribute numitorul și numărătorul.
Clasa conține un constructor cu parametrii și metode pentru operațiile uzuale cu numere
raționale: adunare, scădere, înmulțire.

Aplicația 07
Să se implementeze clasa Dreptunghi având ca atribute lățime și lungime și ca metode un
constructor implicit, un constructor cu parametri. Să se definească funcții friend pentru calculul
ariei dreptunghiului și perimetrului dreptunghiului.

Aplicația 08
Să se implementeze clasa Medic având ca atribute nume, specializare, vârsta, numărul
pacienților și ca metode: un constructor implicit, un constructor cu parametri, un destructor și
o funcție de afișare. Să se definească o funcție friend care determina numărul de ore lucrate
lunar de un medic, ținând cont ca pentru un pacient sunt alocate 2 ore pe luna

Aplicația 09
Pornind de la clasa de baza Angajat (atribute: nume, prenume, vârsta; metode: un constructor
implicit, un constructor cu parametri), sa se implementeze clasa derivata Strungar (atribute:
secția de lucru, număr piese lucrate; metode: un constructor, o metoda de setare a numărului
de piese lucrate și o funcție de afișare).

Aplicația 10
Să se implementeze clasa Punct (atribute: abscisa, ordonata; metode: un constructor implicit,
un constructor cu parametri, un destructor și funcții de setare a abscisei și ordonatei. Să se
definească o funcție friend care calculează distanta dintre doua puncte.

Aplicația 11
Să se implementeze clasa Carte, având ca atribute titlu, autor, număr de pagini și ca metode un
constructor cu parametri, o metoda de returnare a numărului de pagini. Să se definească o

116
funcție friend care verifică dacă numărul de pagini ale unei cărți depășește sau nu o valoare x
citita de la tastatura.

Aplicația 12
Să se implementeze clasa Angajat (atribute: nume, salariu, departament, stare civila; metode:
un constructor cu parametri, o metoda care returnează starea civila și o funcție de afișare a
valorilor tuturor atributelor). Să se definească o funcție friend care verifică dacă salariul unui
angajat căsătorit depășește sau nu o valoare x citita de la tastatura.

Aplicația 13
Să se implementeze clasa Strungar, având ca atribute: nume, secție, numărul de piese lucrate
pe zi și ca metode: un constructor cu parametri, o funcție de setare a numelui și o funcție care
returnează numărul de piese lucrate pe zi. Să se definească o funcție friend care verifică dacă
numărul de piese lucrate de un strungar într-o luna de 20 de zile lucrătoare se afla într-un
interval (a,b) citit de la tastatura.

Aplicația 14
Pornind de la clasa de baza Persoana (atribute: nume, prenume; metode: un constructor cu
parametri) să se implementeze clasa derivata Elev (atribute: clasa, medie; metode: constructor
cu parametri, metoda care returnează valoarea mediei). Să se definească o funcție friend pentru
clasa Elev care sa crească media pentru un elev cu un punct doar dacă elevul este în clasa a VI-
a.

Aplicația 15
Pornind de la clasa de baza Persoana, având ca atribute numele și prenumele și ca metode un
constructor cu parametri, să se implementeze clasa Elev cu atributele nota1, nota2. Definiți un
constructor cu parametri pentru clasa Elev, care să utilizeze constructorul clasei de baza.
Definiți o funcție friend pentru clasa derivată care să calculeze media celor doua note.

Aplicația 16
Să se implementeze clasa Complex, având ca atribute real și imaginar și ca metode un
constructor implicit, un constructor cu parametri și metode de setare a valorilor atributelor. Să
se definească o funcție friend pentru calculul modulului unui număr complex.

Aplicația 17
Să se implementeze clasa Cerc având ca atribute coordonatele centrului și raza. Metodele clasei
sunt: un constructor implicit, un constructor cu parametri, metode care returnează valorile
atributelor și o metodă care calculează aria unui cerc.

Aplicația 18
Să se implementeze clasa Copil având ca atribute nume, prenume, anul nașterii și ca metode
un constructor cu parametri, o metoda care returnează anul nașterii și o funcție de afișare a

117
tuturor atributelor. Să se definească o funcție friend pentru clasa Copil care să returneze
valoarea 0 dacă un copil a depășit vârsta de 10 ani și valoarea 1 în caz contrar.

Aplicația 19
Să se implementeze clasa Student având ca atribute nume, prenume, nota1, nota2 (toate
atributele vor fi alocate dinamic) și ca metode: un constructor cu parametri, funcții de acces
pentru toate atributele și o metodă care determină nota maxima dintre cele doua note.

Aplicația 20
Să se implementeze clasa Dreptunghi având ca atribute lățime și lungime (atributele vor fi
alocate dinamic) și ca metode un constructor implicit, un constructor cu parametri. Să se
definească funcții friend pentru calculul ariei dreptunghiului și perimetrului dreptunghiului.

118
Secțiunea 10 - Aplicație rezolvată
Cerințele problemei: să se elaboreze o aplicație C++ orientată pe obiecte care să implementeze
gestiunea simplificată a cărților dintr-o bibliotecă. Se va implementa o clasă Carte, cu atributele
autor, titlu, editură, an de publicare, preț. Se vor implementa metodele considerate a fi necesare.
Datele despre cărțile din bibliotecă vor fi stocate sub forma unui vector de obiecte de tip Carte.
Va fi posibilă afișarea datelor din vector și calcularea valorii totale a colecției de cărți.

#include <iostream>
//Includem si biblioteca vector.
//Vom folosi, in locul unui vector simplu, un container de tip vector.
//Acest lucru va simplifica operatiile efectuate si codul scris.
#include <vector>

using namespace std;

//O clasa pentru stocarea informatiilor despre carti.


class Carte
{
//Atributele solicitate in enunt.
string autor, titlu, editura, an_publicare;
double pret;
public:
//Un constructor explicit.
Carte(string autor, string titlu, string editura, string an_publicare, double pret):
autor(autor),
titlu(titlu),
editura(editura),
an_publicare(an_publicare),
pret(pret){}
void afisare()
{
cout<<autor<<", "<<titlu<<", editura "<<editura<<", "<<an_publicare<<",
"<<pret<<" ron"<<endl;
}

//Un getter.
double get_pret()
{
return pret;
}
};

int main()
{
//Declaram un vector (container pentru vectori de lungime variabila) de obiecte
//de tip Carte.
//Utilizarea unui container in loc de un vector obisnuit imi simplifica mult
//existenta:
//- nu trebuie sa stiu de la inceput cate carti voi avea
//- pot sa inserez oricand o carte noua, oriunde in lista
//- pot sa afisez cartile in ce ordine vreau
//- pot sa elimin oricand o carte din lista samd

119
//- deci ... remember Structuri de date, anul 2, semestrul 1 :D
vector<Carte> carti;
//Inseram la sfarsitul vectorului un obiect nou de tip Carte
carti.insert(carti.end(), Carte("Ioan Dan", "Curierul secret", "Minerva",
"2008", 24.5));
//Inseram la sfarsitul vectorului un obiect nou de tip Carte
carti.insert(carti.end(), Carte("Ioan Dan", "Cavalerii", "Minerva", "2008",
28));
//Inseram la sfarsitul vectorului un obiect nou de tip Carte
//Putem sa facem asta ori de cate ori avem nevoie ...
carti.insert(carti.end(), Carte("Ioan Dan", "Taina cavalerilor", "Minerva",
"2008", 32.5));
//Facem si afisarea si suma valorilor simultan
double suma=0;
for(int i=0;i!=carti.size();i++)
{
carti[i].afisare();
suma+=carti[i].get_pret();
}
cout<<"valoarea totala a cartilor din biblioteca este de "<<suma<<
" ron."<<endl;
return 0;
}

120
Secțiunea 11 - Aplicație rezolvată
Cerințele problemei: să se elaboreze o aplicație C++ orientată pe obiecte care să implementeze
(mai puțin interfața grafică) operațiile necesare pentru selecția de către clienta unei pizza dintr-
un magazin online (ex. Domino Pizza). Vor fi implementate clasele cu atributele și metodele
necesare pentru a crea diverse produse de tip pizza, cu diverse rețete, standard sau personalizate
de către client, se va face calculația de preț și se va putea urmări starea produsului (obiect gol,
fără ingrediente stabilite -> pre comanda -> comanda -> in curs de coacere -> coapta -> in curs
de livrare -> livrat).

#include <iostream>

using namespace std;

//o clasa pentru stocarea datelor despre ingrediente


class Ingredient
{
string denumire, descriere;
double pret;
public:
Ingredient(){}
Ingredient(string denumire, double pret):
denumire(denumire),
pret(pret)
{}

void set(string denumire, double pret)


{
this->denumire=denumire;
this->pret=pret;
}

string get_denumire()
{
return denumire;
}

double get_pret()
{
return pret;
}
};

//o functie pentru popularea listei de ingrediente posibile.


//doar declaratia. definitia e dupa functia main
void populeaza_lista_de_preturi_ingrediente();
//o functie pentru afisarea listei de ingrediente posibile.
//doar declaratia. definitia e dupa functia main
void afiseaza_lista_de_preturi_ingrediente();

//initializam numarul de ingrediente din lista de ingrediente posibile


int nr_ingrediente_disponibile=61;
//declaram lista de ingrediente posibile si ii alocam memorie
Ingredient * lista_de_preturi_ingrediente=new Ingredient[nr_ingrediente_disponibile];

121
//o clasa pentru pizza
class Pizza
{
string denumire;
string marime;
string blat;
Ingredient * lista_de_ingrediente;
int nr_ingrediente;
double pret;
string stare;
public:
//un constructor implicit
Pizza()
{
//starea initiala
stare="fara ingrediente";
//pretul initial
pret=0;
}

void avanseaza_stare()
{
if(stare=="pregatita de livrare")
stare="livrata";
if(stare=="coapta")
stare="pregatita de livrare";
if(stare=="in curs de coacere")
stare="coapta";
if(stare=="asamblata")
stare="in curs de coacere";
if(stare=="comandata")
stare="asamblata";
if(stare=="pre comanda")
stare="comandata";
if(stare=="fara ingrediente")
stare="pre comanda";
}

string get_stare()
{
return stare;
}

//o functie pentru stabilirea retetei si a pretului


void reteta(string denumire, string marime, string blat, int nr_ingrediente, int
ingrediente[])
{
//trecem din starea "fara ingrediente" in starea "pre comanda"
avanseaza_stare();
//setare denumire
this->denumire=denumire;
//setare marime
this->marime=marime;
//setare tip blat
this->blat=blat;
//setare nr_ingrediente

122
this->nr_ingrediente=nr_ingrediente;
//setare compozitie
//alocam memorie pentru lista de ingrediente
lista_de_ingrediente=new Ingredient[nr_ingrediente];
//preiau ingredientele, mai putin blatul,
//din vectorul de ingrediente primit
for(int i=0;i<nr_ingrediente-1;i++)
lista_de_ingrediente[i]=lista_de_preturi_ingrediente[ingrediente[i]];
//selectie dimensiune si tip de blat
if(marime=="mic")
{
if(blat=="normal")
//blatul va fi intotdeauna ultimul ingredient
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[0];
if(blat=="classic italian")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[1];
}
if(marime=="mediu")
{
if(blat=="normal")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[2];
if(blat=="classic italian")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[3];
if(blat=="president fresh")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[4];
}
if(marime=="mare")
{
if(blat=="normal")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[5];
if(blat=="classic italian")
lista_de_ingrediente[nr_ingrediente-1]=lista_de_preturi_ingrediente[6];
}
//calculatie pret
for(int i=0;i<nr_ingrediente;i++)
pret+=lista_de_ingrediente[i].get_pret();
}

//afisare. doar denumire si pret


void afisare_simpla()
{
cout<<denumire<<", "<<"blat "<<marime<<" "<<blat<<": "<<pret<<" ron"<<endl;
}

//afisare. denumire, pret si compozitie


void afisare_completa()
{
cout<<denumire<<" - "<<pret<<" ron"<<endl;
cout<<"\tCompozitie:"<<endl;
for(int i=0;i<nr_ingrediente;i++)
cout<<"\t"<<lista_de_ingrediente[i].get_denumire()<<endl;
}
};

int main()
{
//umplem lista de ingrediente posibile

123
populeaza_lista_de_preturi_ingrediente();
//afisam lista de ingrediente posibile
afiseaza_lista_de_preturi_ingrediente();
//declar trei pizza
Pizza p1,p2,p3;
//doar pentru p1, testam si tranzitia starilor.
//stare initiala a obiectului:
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//o prima reteta
//va lua blatul corespunzator si ingredientele 7 si 9
//adica sos de pizza si mozzarella
int reteta1[]={7,9};
p1.reteta("Margherita","mic","normal",3,reteta1);
p1.afisare_simpla();
p1.afisare_completa();
//stare obiectului dupa completarea retetei:
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;
//stare obiectului dupa inca un pas:
p1.avanseaza_stare();
cout<<"p1 este acum in starea: "<<p1.get_stare()<<endl;

//o a doua reteta


int reteta2[]={7,9,27,45,55};
p2.reteta("Domino's classic","mic","normal",6,reteta2);
p2.afisare_simpla();
p2.afisare_completa();
//o a treia reteta
int reteta3[]={7,9,29,41,43};
p3.reteta("Carnivora","mediu","normal",6,reteta3);
p3.afisare_simpla();
p3.afisare_completa();
return 0;
}

void populeaza_lista_de_preturi_ingrediente()
{
//pur si simplu enumeram ingredientele si preturile lor
lista_de_preturi_ingrediente[0].set("blat mic normal", 15);
lista_de_preturi_ingrediente[1].set("blat mic classic italian", 15);
lista_de_preturi_ingrediente[2].set("blat mediu normal", 22);
lista_de_preturi_ingrediente[3].set("blat mediu classic italian", 22);
lista_de_preturi_ingrediente[4].set("blat mediu president fresh", 27.5);

124
lista_de_preturi_ingrediente[5].set("blat mare normal", 30);
lista_de_preturi_ingrediente[6].set("blat mare classic italian", 30);
lista_de_preturi_ingrediente[7].set("sos pizza", 0);
lista_de_preturi_ingrediente[8].set("extra sos pizza", 2);
lista_de_preturi_ingrediente[9].set("mozzarella", 0);
lista_de_preturi_ingrediente[10].set("extra mozzarella", 2);
lista_de_preturi_ingrediente[11].set("smantana vegetala", 2);
lista_de_preturi_ingrediente[12].set("extra smantana vegetala", 2);
lista_de_preturi_ingrediente[13].set("sos barbeque", 2);
lista_de_preturi_ingrediente[14].set("extra sos barbeque", 2);
lista_de_preturi_ingrediente[15].set("pesto", 2);
lista_de_preturi_ingrediente[16].set("extra pesto", 2);
lista_de_preturi_ingrediente[17].set("busuioc", 2);
lista_de_preturi_ingrediente[18].set("extra busuioc", 2);
lista_de_preturi_ingrediente[19].set("parmezan", 2);
lista_de_preturi_ingrediente[20].set("extra parmezan", 2);
lista_de_preturi_ingrediente[21].set("branza cu mucegai", 2);
lista_de_preturi_ingrediente[22].set("extra branza cu mucegai", 2);
lista_de_preturi_ingrediente[23].set("branza vegetala", 3);
lista_de_preturi_ingrediente[24].set("extra branza vegetala", 3);
lista_de_preturi_ingrediente[25].set("cheddar", 2);
lista_de_preturi_ingrediente[26].set("extra cheddar", 2);
lista_de_preturi_ingrediente[27].set("sunca", 2);
lista_de_preturi_ingrediente[28].set("extra sunca", 2);
lista_de_preturi_ingrediente[29].set("pepperoni", 2);
lista_de_preturi_ingrediente[30].set("extra pepperoni", 2);
lista_de_preturi_ingrediente[31].set("bacon", 2);
lista_de_preturi_ingrediente[32].set("extra bacon", 2);
lista_de_preturi_ingrediente[33].set("kikers", 1);
lista_de_preturi_ingrediente[34].set("extra kikers", 1);
lista_de_preturi_ingrediente[35].set("chorizo", 2);
lista_de_preturi_ingrediente[36].set("extra chorizo", 2);
lista_de_preturi_ingrediente[37].set("pui", 2);
lista_de_preturi_ingrediente[38].set("extra pui", 2);
lista_de_preturi_ingrediente[39].set("ton", 2);
lista_de_preturi_ingrediente[40].set("extra ton", 2);
lista_de_preturi_ingrediente[41].set("carnati", 2);
lista_de_preturi_ingrediente[42].set("extra carnati", 2);
lista_de_preturi_ingrediente[43].set("vita", 2);
lista_de_preturi_ingrediente[44].set("extra vita", 2);
lista_de_preturi_ingrediente[45].set("masline", 2);
lista_de_preturi_ingrediente[46].set("extra masline", 2);
lista_de_preturi_ingrediente[47].set("ceapa", 2);
lista_de_preturi_ingrediente[48].set("extra ceapa", 2);
lista_de_preturi_ingrediente[49].set("jalapeno", 2);
lista_de_preturi_ingrediente[50].set("extra jalapeno", 2);
lista_de_preturi_ingrediente[51].set("ardei gras verde", 2);
lista_de_preturi_ingrediente[52].set("extra ardei gras verde", 2);
lista_de_preturi_ingrediente[53].set("ananas", 2);
lista_de_preturi_ingrediente[54].set("extra ananas", 2);
lista_de_preturi_ingrediente[55].set("ciuperci", 2);
lista_de_preturi_ingrediente[56].set("extra ciuperci", 2);
lista_de_preturi_ingrediente[57].set("porumb", 2);
lista_de_preturi_ingrediente[58].set("extra porumb", 2);
lista_de_preturi_ingrediente[59].set("rosii", 2);
lista_de_preturi_ingrediente[60].set("extra rosii", 2);
}

125
void afiseaza_lista_de_preturi_ingrediente()
{
//o afisare a tuturor ingredientelor disponibile
cout<<"Ingredientele disponibile sunt:"<<endl;
for(int i=0;i<nr_ingrediente_disponibile;i++)
cout<<"\t"<<lista_de_preturi_ingrediente[i].get_denumire()<<"\t"<<
lista_de_preturi_ingrediente[i].get_pret()<<endl;
}

126
Elemente avansate de programare orientată pe obiecte -
tipare de proiectare
Note ale autorului:
Deși această a doua parte face parte conceptual din același domeniu al programării, cu nuanța
de programare orientată pe obiecte, nu recomand parcurgerea sa imediat după prima parte. O
experiență rezonabilă (1-3 ani) de lucru în programarea orientată pe obiecte este necesară
pentru a putea asimila aceste concepte. Nu este vorba atât de un interval de timp necesar pentru
a căpăta informații suplimentare de nivel mai înalt, cât de timp necesar pentru a vedea și a scrie
un număr mare de exemple de cod orientat pe obiecte, lucru fără de care explicațiile următoare
nu vor avea mare sens.
Este imposibilă tratarea exhaustivă a conceptelor următoare într-o lucrare de dimensiunile celei
actuale. Am recurs la furnizarea de referințe online ori de câte ori un anume concept sau subiect
ar fi putut fi / ar fi trebuit dezvoltat mai departe și acest lucru a avut ca efect introducerea unui
număr foarte mare de referințe. Este adevărat că această situație afectează estetica și lizibilitatea
lucrării, dar este singura soluție găsită pentru a acoperi cât mai multe concepte, care ar altfel ar
fi făcut obiectul a câteva cărți, în locul uneia singure.

Secțiunea 12 – Principii avansate ale POO - S.O.L.I.D. (Single


responsibility / Open-closed / Liskov / Interface segregation /
Dependecy inversion principles)
În programarea orientată pe obiecte, S.O.L.I.D. este un acronim mnemonic pentru cinci
principii de proiectare menite să facă proiectele software mai inteligibile, flexibile și ușor de
întreținut. Cele cinci principii fac parte dintr-un serie de unsprezece principii propuse de Robert
C. Martin în (Martin, Principles of OOD, 2003), (Martin, Getting a SOLID start, 2003)11. Deși
se aplică oricărui proiect orientat pe obiecte, principiile S.O.L.I.D. pot constitui, de asemenea,
o filozofie de bază pentru metodologii precum dezvoltarea agilă sau dezvoltarea de software
adaptiv. Teoria celor cinci principii a fost introdusă de Martin în (Martin, Design Principles
and Design Patterns, 2000), deși acronimul a fost introdus mai târziu de Michael Feathers
(Fenton, 2017).
Seria de unsprezece principii cuprinde:
• cinci principii de proiectare a claselor (cele cinci cuprinse în acronimul S.O.L.I.D.):
o Principiul responsabilității unice (The Single Responsibility Principle - SRP) -
O clasă ar trebui să aibă un motiv de schimbare / responsabilitate și numai unul
/ una.

11
Principiile S.O.L.I.D. sunt expuse pe larg și în seria de tutoriale video Tim Corey - Design Patterns & Principles:
https://www.youtube.com/watch?v=5RwhyZnVRS8&list=PLLWMQd6PeGY3ob0Ga6vn1czFZfW6e-FLr

127
o Principiul deschis-închis (The Open Closed Principle - OCP) - Comportamentul
unei clase ar trebui să poată fi extins fără să fie nevoie de modificarea clasei.
o Principiul Liskov al substituției (The Liskov Substitution Principle - LSP) -
Clasele derivate trebuie să fie substituibile pentru clasele lor de bază.
o Principiul de separării interfețelor (The Interface Segregation Principle - ISP) -
Interfețele ar trebui să aibă o granulație fină, în sensul de a fi realizate interfețe
separate, specifice fiecărui client.
o Principiul inversiunii dependențelor (The Dependency Inversion Principle -
DIP) - Dependențele trebuie să fie construite în funcție de abstracții, nu de
implementări concrete.
• șase principii de proiectare a pachetelor, primele trei referindu-se la coeziunea
pachetelor, iar celelalte trei la cuplarea între pachete:
o Principiul echivalenței între implementarea inițială și reutilizare (The Release
Reuse Equivalency Principle - REP) – Granularitatea unui pachet la
implementarea inițială trebuie să fie aceeași cu cea de la reutilizarea codului.
o Principiul ambalajului comun (The Common Closure Principle - CCP) – Clasele
care se schimbă în același timp ar trebui să se afle în același pachet.
o Principiul utilizării comune (The Common Reuse Principle – CRP) – Clasele
care sunt utilizate împreună ar trebui să se afle în același pachet.
o Principiul dependențelor aciclice (The Acyclic Dependencies Principle – ADP)
– Graful dependențelor între pachete nu trebuie să aibă cicluri.
o Principiul dependențelor stabile (The Stable Dependencies Principle – SDP) –
Dependențele trebuie realizate în direcția creșterii stabilității.
o Principiul abstracțiilor stabile (The Stable Abstractions Principle – SAP) –
Nivelul de abstractizare crește cu stabilitatea.
Obs. În diverse limbaje de programare, o interfață / un protocol este un mijloc comun pentru
ca obiectele discrete să comunice între ele. Interfața / protocolul conține definiții ale metodelor
și valorilor asupra cărora obiectele sunt de acord, pentru a coopera, ca parte a unei API
(Application Programming Interface)12.
Interfața / protocolul este o descriere a:
• Mesajelor care sunt înțelese de obiect.
• Argumentelor cu care pot fi furnizate acestor mesaje.
• Tipurilor de rezultate pe care le returnează aceste mesaje.
• Elementele invariabile care sunt conservate în ciuda modificărilor aduse la starea unui
obiect.
• Situații excepționale care va fi necesar a fi gestionate de către clienții obiectului (ex.
alte obiecte care primesc mesaje de la obiect).
Dacă obiectele sunt complet încapsulate, atunci interfața / protocolul va descrie singurul mod
în care obiectele pot fi accesate de alte obiecte. De exemplu, în interfețele Java, interfața
Comparable specifică o metodă compareTo() pe care trebuie să o implementeze clasele de

12
https://en.wikipedia.org/wiki/Application_programming_interface

128
implementare. Aceasta înseamnă că, de exemplu, o metodă de sortare oarecare poate sorta
obiecte din corice clasă care implementează interfața Comparable, fără a fi nevoie să știe nimic
despre structura clasei, cu excepția faptului că oricare două obiecte instanțiate din clasa
respectivă pot fi comparate utilizând compareTo().
Unele limbaje de programare oferă suport explicit pentru interfețe / protocoale (ex. Ada, C#,
D, Dart, Delphi, Go, Java, Logtalk, Object Pascal, Objective-C, PHP, Python, Racket, Seed7,
Swift). În C++ interfețele sunt cunoscute sub numele de clase de bază abstracte și implementate
folosind funcții virtuale pure (a se revedea Tutorialul 26). Facilitățile orientate pe obiecte din
Perl acceptă, de asemenea, interfețe. În Clojure, Elixir, Java 8, Kotlin, Logtalk, Objective-C,
Swift și Python se utilizează denumirea de protocol.
Obs. În programare, conceptul de coeziune13 se referă la gradul în care elementele din
interiorul unui modul aparțin (Yourdon & Constantine, 1975). Coeziunea este o măsură a cât
de strânsă este relația dintre metodele și datele unei clase și un anumit scop sau concept
unificator deservit de acea clasă. Este de asemenea și o măsură a cât de strânsă este relația
dintre metodele clasei și datele în sine.
Coeziunea este măsurată ordinal și este de obicei descrisă drept „coeziune ridicată” sau
„coeziune scăzută”. Modulele având coeziune ridicată sunt preferabile, deoarece coeziunea
ridicată este asociată cu mai multe trăsături dezirabile ale software-ului, incluzând robustețea,
fiabilitatea, reutilizabilitatea și inteligibilitatea. În schimb, coeziunea scăzută este asociată cu
trăsături nedorite, cum ar fi cod dificil de întreținut, testat, reutilizat sau chiar de înțeles.
Coeziunea și cuplarea sunt de obicei opuse. Coeziunea ridicată se corelează adesea cu o
cuplare scăzută și invers (Ingeno, 2018). Conceptele de cuplare și coeziune au fost inventate
de Larry Constantine la sfârșitul anilor 1960 ca parte a proiectării structurate14 și sunt bazate
pe bune practici de programare care reduceau costurile de întreținere și modificare. Conceptele
de design structurat, coeziune și cuplare au fost publicate în (Stevens, Myers, & Constantine,
1974) și în (Yourdon & Constantine, 1975). Ultimele două concepte au devenit ulterior termeni
standard în ingineria software.
În programarea orientată pe obiecte , dacă metodele care servesc o clasă tind să fie similare din
multe privințe, atunci se spune că acea clasă are o coeziune ridicată (Marsic, 2012). Într-un
sistem extrem de coeziv, lizibilitatea codului și reutilizarea acestora sunt crescute, în timp ce
complexitatea este menținută gestionabilă.
Coeziunea este crescută dacă:

Funcționalitățile încorporate într-o clasă, accesate prin metodele sale, au multe lucruri
în comun.
• Metodele desfășoară un număr mic de activități conexe, evitând seturi de date cu
granulație grosieră sau fără legătură.
Avantajele coeziunii ridicate (sau „coeziunii puternice”) sunt:

13
https://en.wikipedia.org/wiki/Cohesion_(computer_science)
14
https://en.wikipedia.org/wiki/Structured_analysis#Structured_Design

129
• Complexitatea redusă a modulului (modulele sunt mai simple, având mai puține
operații).
• Creșterea mentenabilității sistemului - schimbările logice în domeniul afectează mai
puține module, și pentru că schimbările într-un singur modul necesită mai puține
schimbări în alte module.
• Reutilizabilitate crescută a modulului - dezvoltatorii de aplicații vor găsi componenta
de care au nevoie mai ușor în setul coeziv de operații furnizate de modul.
Obs. În ingineria software, cuplarea15 este gradul de interdependență între modulele software;
o măsură a legăturii strânse dintre două rutine sau module (ISO/IEC/IEEE, 2010); forța
relațiilor dintre module (ISO/IEC, 2005).
Cuplarea evoluează de obicei în sens opus coeziunii. Cuplarea scăzută se corelează adesea cu
o coeziune ridicată și invers. Cuplarea scăzută este adesea un semn al unui sistem informatic
bine structurat și al unui design bun, iar atunci când este combinat cu o coeziune ridicată,
susține obiectivele generale de lizibilitate bună și întreținere ușoară.
Cuplarea poate fi ”scăzută” / ”slăbită” / ”slabă”, sau ”mare” / „strânsă” / „puternică”.
Obs. Nu toate cele cinci principii au fost efectiv propuse inițial de Robert C. Martin. Principiul
deschis-închis a fost propus de Bertrand Meyer în (Meyer, 1988) și Principiul Liskov al
substituției a fost propus de Barbara Liskov în (Liskov & Wing, 1994).
Secvențele de cod următoare ilustrează cele cinci principii16.

Tutorialul 27 - Principiul responsabilității unice (Single responsibility principle)


Programul următor demonstrează modul de aplicare a principiului responsabilității unice:
//Single Responsibility Principle
//O clasa ar trebui sa aiba un motiv de schimbare /
//responsabilitate si numai unul / una.

#include <fstream>
#include <string>
#include <vector>

using namespace std;

struct Jurnal
{
string titlu;
vector<string> intrari;

explicit Jurnal(const string& titlu): titlu(titlu) {}

void adaugare(const string& intrare)


{
intrari.push_back(intrare);
}
};

//Metoda de salvare pe disc a continutului jurnalului nu trebuie pusa in clasa Jurnal

15
https://en.wikipedia.org/wiki/Coupling_(computer_programming)
16
Cod preluat și adaptat de la https://cpp-design-patterns.readthedocs.io/en/latest/principles.html

130
//Clasa Jurnal are deja o responsabilitate, nu trebuie sa ii mai adaugam inca una
struct ManagerSalvare
{
static void salvare(const Jurnal& j, const string& numefisier)
{
ofstream iesire(numefisier);
for (auto& s : j.intrari)
iesire << s << endl;
}
};

int main()
{
Jurnal jurnal("Jurnalul meu");
jurnal.adaugare("Prima insemnare");
jurnal.adaugare("A doua insemnare");
jurnal.adaugare("A treia insemnare");

//O clasa separata este utilizata pentru salvarea datelor.


//Salvarea jurnalelor nu este responsabilitatea clasei Jurnal.
//Responsabilitatea clasei Jurnal este doar adaugarea de intrari in jurnal.
ManagerSalvare().salvare(jurnal, "jurnal.txt");

return 0;
}

Tutorialul 28 - Principiul deschis-închis (Open-closed principle)


Programul următor demonstrează modul de aplicare a principiului deschis-închis:
//Open-closed Principle
//Clasele trebuie sa poata fi extinse fara a schimba codul existent
#include <iostream>
#include <string>
#include <vector>

using namespace std;

enum class Color {Red, Green, Blue};


enum class Size {Small, Medium, Large};

struct Product
{
string name;
Color color;
Size size;
};

typedef vector<Product> ProductList;

//Acesta este un contraexemplu.


//Pentru a adauga un nou criteriu, clasa trebuie modificata
//(si evident, modificarile trebuie testate).
//Acest lucru incalca principiul dechis-inchis.
struct ProductFilter
{
static ProductList by_color(ProductList items, Color color)
{
ProductList result;
for (auto& item : items)
if (item.color == color) result.push_back(item);
return result;
}

131
static ProductList by_size(ProductList items, Size Size)
{
ProductList result;
for (auto& i : items)
if (i.size == Size) result.push_back(i);
return result;
}

//Abordarea nu este deloc scalabila.


//Trebuie scrise metode pentru toate combinatiile de filtre.
static ProductList by_color_and_size(ProductList items, Color color, Size size)
{
ProductList result;
for (auto& item : items)
if (item.color == color && item.size == size) result.push_back(item);
return result;
}
};

//Asa ar trebui lucrat de fapt - o abordare mai buna: generalizarea cu interfete


//Folosim un template pentru a descrie generic orice posibila specificatie
template <typename T>
struct ISpecification
{
virtual bool is_satisfied(T item) = 0;
};

//Acum specificatiile pot fi combinate


template <typename T>
struct AndSpecification : ISpecification<T>
{
ISpecification<T>& first;
ISpecification<T>& second;

AndSpecification(ISpecification<T>& first, ISpecification<T>& second): first(first),


second(second) {}

bool is_satisfied(T item) override


{
return first.is_satisfied(item) && second.is_satisfied(item);
}
};

//Specificatiile concrete vor fi clase care mostenesc clasa generica anterioara


struct ColorSpecification: ISpecification<Product>
{
Color color;
explicit ColorSpecification(const Color color): color(color) {}
bool is_satisfied(Product item) override
{
return item.color == color;
}
};

struct SizeSpecification : ISpecification<Product>


{
Size size;
explicit SizeSpecification(const Size size) : size(size) {}
bool is_satisfied(Product item) override
{
return item.size == size;
}

132
};

//Filtrele vor fi si ele descrise de un template


template <typename T>
struct IFilter
{
virtual ProductList filter(ProductList& items, ISpecification<T>& spec) = 0;
};

//Un filtru concret mosteneste clasa generica filtru.


//Se observa ca este o implementare mult mai simpla decat cea de la contraexemplu
//si nu este nevoie de niciun fel de cod suplimentar pentru a lucra cu noi filtre.
struct BetterFilter : IFilter<Product>
{
ProductList filter(ProductList& items, ISpecification<Product>& spec) override
{
ProductList result;
for (auto& productItem : items)
if (spec.is_satisfied(productItem)) result.push_back(productItem);
return result;
}
};

int main()
{
Product apple{"Apple", Color::Green, Size::Small};
Product tree{"Tree", Color::Green, Size::Large};
Product house{"House", Color::Blue, Size::Large};
//testam contraexemplul:
ProductList all{apple, tree, house};

//testam exemplul bun:


//cream filtrul:
BetterFilter bf;

//sa afisam tot ce e verde:


ColorSpecification green(Color::Green);
auto green_things = bf.filter(all, green);
for (auto& product : green_things)
cout << product.name << " is green" << endl;

//sa afisam tot ce e mare si verde:


SizeSpecification big(Size::Large);
AndSpecification<Product> green_and_big{big, green};
auto green_big_things = bf.filter(all, green_and_big);
for (auto& product : green_big_things)
cout << product.name << " is green and big" << endl;
return 0;
}

Tutorialul 29 - Principiul Liskov al substituției (Liskov substitution principle)


Programul următor demonstrează modul de aplicare a principiului Liskov al substituției:
//Liskov Substitution Principle
//Obiectele dintr-un program care apartin de o anumita clasa
//Ar trebui sa poate fi inlocuite cu obiecte dintr-o clasa derivata
//fara a modifica corectitudinea programului
#include <iostream>

using namespace std;

//Un contraexemplu:

133
class Rectangle
{
protected:
int width, height;
public:
Rectangle(const int width, const int height) : width(width), height(height) {}
virtual int GetWidth() const
{
return width;
}
virtual void SetWidth(const int width)
{
this->width = width;
}
virtual int GetHeight() const
{
return height;
}
virtual void SetHeight(const int height)
{
this->height = height;
}
int Area() const
{
return width * height;
}
};

class Square : public Rectangle


{
public:
explicit Square(int size) : Rectangle{size, size} {}
void SetWidth(const int width) override
{
this->width = height = width;
}
void SetHeight(const int height) override
{
this->height = width = height;
}
};

void process(Rectangle& r)
{
int w = r.GetWidth();
r.SetHeight(10);
cout << "expect area = " << (w * 10) << ", got " << r.Area() << endl;
}

//Ce ar trebui facut de fapt:


//Utilizarea tiparelor factory si decoration (a se vedea sectiunea 14)
//si utilizarea unei metode SetSizen loc de SetHeight and SetWidth.
//In acest mod clasa Square nu mai este necesara

struct RectangleFactory
{
static Rectangle CreateRectangle(int w, int h);
static Rectangle CreateSquare(int size);
};

int main()
{
Rectangle r{5, 5};
process(r);

134
//Obiectul s de tip Square (clasa derivata din Rectangle) violeaza principiul Liskov
//si rezultatul este calcularea incorecta a ariei
Square s{5};
process(s);

return 0;
}

Tutorialul 30 - Principiul separării interfețelor (Interface segregation principle)


Programul următor demonstrează modul de aplicare a principiului separării interfețelor:
//Interface Segregation Principle
//Mai multe interfete specifice clientilor sunt mai bune decat o singura interfata generala
//Niciun client nu ar trebui sa depinda de metode pe care nu le utilizeaza
#include <iostream>
#include <string>
#include <vector>

using namespace std;

//Sa zicem ca avem de scris software pentru echipamente de birou de diverse


//tipuri (imprimanta, scaner, multifunctional)

//Sa definim un tip "document" cu care sa lucreze echipamentul nostru:


struct Document
{
string content;
explicit Document(string content) : content(content){};
};

//Contraexemplu
//O singura interfata care indeplineste toate functiile
struct Echipament
{
virtual void print(vector<Document> docs) = 0;
virtual void scan(vector<Document> docs) = 0;
virtual void fax(vector<Document> docs) = 0;
};

//O clasa care o mosteneste pe prima


struct Multifunctional: Echipament
{
void print(vector<Document> docs) override;
void scan(vector<Document> docs) override;
void fax(vector<Document> docs) override;
};

//Exemplu corect
//Mai multe interfete, cate una pentru fiecare functie
struct IPrinter
{
virtual void print(vector<Document> docs) = 0;
};

struct IScanner
{
virtual void scan(vector<Document> docs) = 0;
};

//Si clasele client care utilizeaza aceste interfete

135
struct Printer: IPrinter
{
void print(vector<Document> docs) override
{
for (auto& doc : docs)
{
cout << "Print:\t" << doc.content << endl;
}
};
};

struct Scanner: IScanner


{
void scan(vector<Document> docs) override
{
for (auto& doc : docs)
{
cout << "Scan:\t" << doc.content << endl;
}
};
};

//Acum avem la dispozitie mai multe interfete mici pe care le putem asambala
//prin mostenire multipla pentru a forma orice interfata combinata ne dorim.
struct IMachine: IPrinter, IScanner {};

//La randul ei, interfata combinata este utilizata mai departe.


struct Machine: IMachine
{
IPrinter& printer;
IScanner& scanner;

Machine(IPrinter& printer, IScanner& scanner) : printer(printer), scanner(scanner) {}

void print(vector<Document> docs) override


{
printer.print(docs);
}
void scan(vector<Document> docs) override
{
scanner.scan(docs);
}
};

int main()
{
//o "imprimanta"
Printer printer;
//un "scaner"
Scanner scanner;
//o "multifunctionala"
Machine machine(printer, scanner);
vector<Document> documents {Document(string("Document 1")),Document(string("Document
2"))};
//acum multifunctionala "tipareste" cele doua documente
machine.print(documents);
//si respectiv le "scaneaza"
machine.scan(documents);

return 0;
}

136
Tutorialul 31 - Principiul inversării dependețelor (Dependecy inversion principle)
Programul următor demonstrează modul de aplicare a principiului inversării dependențelor.
Obs. Inversarea dependențelor, realizată de la zero, necesită scrierea unei cantități
semnificative de cod suport. Din acest motiv se preferă utilizarea unei biblioteci sau a unui
framework care se îndeplinească această funcție17. În cazul de față a fost utilizată biblioteca
Boost DI18. Pentru rulare este necesară descărcarea bibliotecii de la adresa indicată și copierea
ei în folderul proiectului.
//Dependecy inversion principle
//Dependentele ar trebui sa fie mai degraba abstracte decat concrete
//Vom utiliza interfete in locul unor clase concrete.
#include <iostream>
#include <memory>
#include "di.hpp"

using namespace std;

struct ILogger
{
virtual void Log(const string& s) = 0;
};

struct ConsoleLogger : ILogger


{
void Log(const string& s) override
{
cout << "LOG: " << s.c_str() << endl;
}
};

struct Engine
{
float volume = 5;
int horse_power = 400;

Engine(){};

friend ostream& operator<<(ostream& os, const Engine& obj)


{
return os << "volume: " << obj.volume << " horse_power: " << obj.horse_power;
}
};

struct Car
{
shared_ptr<Engine> engine;
shared_ptr<ILogger> logger;

Car(const shared_ptr<Engine>& engine, const shared_ptr<ILogger>& i_logger):


engine(engine), logger(i_logger)
{
logger->Log("Created a car");
}

friend ostream& operator<<(ostream& os, const Car& obj)

17
https://boost-experimental.github.io/di/index.html
18
https://boost-experimental.github.io/di/overview.html

137
{
return os << "car with engine: " << *obj.engine;
}
};

int main()
{
//fara dependenta inversata
cout << "without DI\n";
auto e1 = make_shared<Engine>();
auto logger1 = make_shared<ConsoleLogger>();
auto c1 = make_shared<Car>(e1, logger1);
cout << *c1 << endl;

//cu dependenta inversata


cout << "with DI\n";
using namespace boost;
//de cate ori este nevoie de un ILogger se creeaza o instanta a lui ConsoleLogger
auto injector = di::make_injector(di::bind<ILogger>().to<ConsoleLogger>());
auto c = injector.create<shared_ptr<Car>>();

cout << *c << endl;

return 0;
}

138
Secțiunea 13 – Introducere în tipare de proiectare
Un tipar de proiectare (design pattern) este formularea într-un mod reutilizabil a unei soluții
la o problemă de proiectare. Ideea a fost introdusă de arhitectul Christopher Alexander
(Alexander, 1977) și a fost adaptată pentru diverse alte discipline, în special inginerie software.
O colecție organizată de tipare de proiectare care se referă la un anumit domeniu de studii, se
numește limbaj de tipare (pattern language). Un astfel de limbaj oferă o terminologie
comună pentru a descrie situațiile cu care se confruntă inginerii / proiectanții din domeniul
respectiv.
Urmând definiția din paragraful anterior, există o multitudine de categorii de tipare de
proiectare, pentru diverse domenii de studii. Din punctul de vedere al producției de software și
implicit, al programării orientate pe obiecte, sunt mai mult sau mai puțin importante
următoarele categorii de tipare de proiectare (unele vor fi detaliate în secțiunile următoare,
altele vor fi doar menționate):
• Tiparele de proiectare software, utilizate în software design.
• Tiparele de arhitectură software, utilizate în studiul arhitecturii software.
• Tiparele de interacțiune, utilizate în proiectarea mecanismelor de interacțiune între om
și calculator.
Obs. Alte categorii de tipare pot fi găsite pe pagina http://wiki.c2.com/?PatternIndex
Obs. Listele de tipare furnizate în secțiunile următoare, deși conțin un număr mare de tipare,
nu sunt în niciun caz niște liste exhaustive. Un număr mare de alte tipare, de diverse tipuri, pot
fi găsite pe site-urile următoare:
• Portland Pattern Repository: http://c2.com/ppr/index.html,
http://wiki.c2.com/?CategoryPattern și http://wiki.c2.com/?PatternIndex
• The Hillside Group – Patterns catalog: https://hillside.net/patterns/patterns-catalog
• Brian Foote Pattern Labyrinth: http://www.laputan.org/
• DASCo Project: http://web.ist.utl.pt/rito.silva/dasco/
• ARCUS: http://www.objectarchitects.de/arcus/cookbook/
Beneficiile utilizării tiparelor includ:
• Este posibilă instruirea începătorilor folosind bune practici și abordări comune.
• Este captată ”înțelepciunea colectivă” a proiectanților în mai multe utilizări și scenarii.
• Oferă echipelor un limbaj comun, reducând neînțelegerile care apar din cauza
vocabularului diferit.
• Se reduc timpul și costurile în ciclul de viață al proiectării și dezvoltării software.
• Fac ca proiectele utilizabile să fie „calea minimei rezistențe”.
• Se elimină timpul pierdut cu reinventarea mecanismelor deja descoperite de alții
anterior.
• Li se garantează utilizatorilor o experiență de utilizare consistentă și previzibilă în
cadrul unei aplicații sau serviciu.

139
Tipare de proiectare software
În ingineria software, un tipar de proiectare software este o soluție generală, reutilizabilă
pentru o problemă care apare frecvent într-un context dat în proiectarea software-ului. Nu este
vorba de un proiect final care poate fi transformat direct în cod sursă sau cod mașină. Mai
degrabă, este o descriere sau un șablon utilizabil pentru a rezolva o problemă, șablon care poate
fi folosit în multe situații diferite. Tiparele de proiectare sunt bune practici pe care
programatorul le poate folosi pentru a rezolva probleme comune, atunci când proiectează o
aplicație sau un sistem (Gamma, Helm, Johnson, & Vlissides, 1994).
Tiparele de design folosite în programarea orientată pe obiecte descriu de obicei relații și
interacțiuni între clase sau obiecte, fără a specifica clasele aplicației finale sau obiectele
implicate.
Obs. Tiparele care implică o stare mutabilă pot fi inadecvate pentru limbajele de programare
funcționale, unele tipare pot fi inutile în limbaje care conțin suport integrat pentru rezolvarea
problemei pe care încearcă să o rezolve tiparul respectiv, iar tipare orientate pe obiecte nu sunt
neapărat potrivite pentru limbajele neorientate pe obiecte.
Tiparele de proiectare software pot fi privite ca o abordare structurată a programării, un nivel
intermediar între o paradigmă / filosofie de programare19 și un algoritm concret.
Tiparele de proiectare software pot accelera procesul de dezvoltare prin furnizarea de
paradigme de dezvoltare testate și dovedite (Bishop, 2012). Proiectarea eficientă a software-
ului necesită luarea în considerare a problemelor care nu vor deveni vizibile decât mai târziu,
în timpul implementării. Codul proaspăt scris poate avea adesea probleme subtile ascunse,
probleme care necesită timp pentru a fi detectate și care pot cauza uneori probleme majore pe
parcurs. Reutilizarea tiparelor de proiectare software ajută la prevenirea unor astfel de
probleme subtile și îmbunătățește, de asemenea, lizibilitatea codului, atât pentru programatorii
cât și pentru inginerii software care sunt familiarizați cu tiparele de proiectare software.
Obs. Pentru a obține flexibilitate, tiparele de proiectare introduc de obicei niveluri suplimentare
de indirectare, ceea ce, în unele cazuri, poate complica proiectele rezultate și poate afecta
performanța aplicației.
Prin definiție, un tipar de proiectare software reprezintă o schemă care trebuie implementată
de la zero în fiecare aplicație care îl folosește. Unii autori consideră acest lucru ca un pas înapoi
în ceea ce privește reutilizabilitatea software-ului.
Spre deosebire de tehnicile de proiectare software care sunt dificil de aplicat la o gamă mai
largă de probleme, tiparele de proiectare oferă soluții generale, documentate într-un format care
nu sunt în mod specific legate de un anumit tip de problemă.
Tiparele de proiectare software pot fi clasificate în tipare de uz general și tipare specifice pentru
un anumit domeniu.

19
https://en.wikipedia.org/wiki/List_of_software_development_philosophies

140
Tipare de proiectare software de uz general
Tiparele de proiectare software de uz general, așa cum au fost descrise în (Gamma, Helm,
Johnson, & Vlissides, 1994) (a se vedea și http://wiki.c2.com/?DesignPatternsBook), au fost
inițial grupate în trei categorii, cuprinzând 23 de tipare: tipare de creație (Creational patterns;
5 tipare), tipare structurale (Structural patterns; 7 tipare) și tipare comportamentale (Behavioral
patterns; 11 tipare). Ulterior, la acestea a mai fost adăugată încă o categorie, cea a tiparelor de
concurență (Concurrency patterns) și un număr de alte tipare în cele trei categorii deja existente.
Obs. Cele 23 de tipare apărute inițiale sunt exemplificate într-o aplicație demonstrativă scrisă
de Antonio Gulli în C++ și disponibilă gratuit pe această pagină:
http://codingplayground.blogspot.com/2009/01/design-patterns-c-full-collection-of.html

Tipare de creație
Tipare de creație specificate în (Gamma, Helm, Johnson, & Vlissides, 1994):
• Abstract factory20 - Furnizează o interfață pentru crearea de obiecte înrudite sau
dependente, fără a specifica în mod concret clasele din care sunt instațiate obiectele
respective.
• Builder21 - Separă construcția unui obiect complex de reprezentarea sa, permițând ca
același proces de construcție să fie folosit pentru diferite reprezentări.
• Factory method22 - Definește o interfață pentru crearea unui obiect, dar lasă subclasele
să decidă ce clasă să instanțieze. Factory Method permite unei clase să lase subclasele
sale să se ocupe de instanțiere.
• Prototype23 - Specifică tipuri de obiecte care vor fi create prin utilizarea unei instanțe
prototip și creează obiecte noi dintr-un ”schelet” al unui obiect existent, crescând
performanța și reducând consumul de memorie.
• Singleton24 - Garantează faptul că o clasă are o singură instanță și furnizează un punct
global de acces la ea.
Tipare de creație introduse ulterior:
• Dependency Injection25 - O clasă primește obiectele de care are nevoie (dependințele
sale) de la un injector, în loc să fie create direct obiectele.
• Lazy initialization / virtual proxy26 - O tactică de întârziere a creării unui obiect, a
calculării unei valori, sau a unui alt proces (de obicei ”scump”, din punct de vedere al
resurselor consumate), până în ultimul moment - exact atunci când este necesară. Acest
tipar este de fapt o strategie de implementare pentru tiparul Proxy.
• Multiton27 - Garantează faptul că o clasă are numai instanțe denumite și furnizează un
punct global de acces la ele.

20
https://en.wikipedia.org/wiki/Abstract_factory_pattern
21
https://en.wikipedia.org/wiki/Builder_pattern
22
https://en.wikipedia.org/wiki/Factory_method_pattern
23
https://en.wikipedia.org/wiki/Prototype_pattern
24
https://en.wikipedia.org/wiki/Singleton_pattern
25
https://en.wikipedia.org/wiki/Dependency_injection
26
https://en.wikipedia.org/wiki/Lazy_initialization
27
https://en.wikipedia.org/wiki/Multiton_pattern

141
• Object pool28 - Evită ”costurile” (din punct de vedere al resurselor consumate)
corespunzătoare obținerii și eliberării resurselor prin reciclarea obiectelor care nu mai
sunt utilizate. Poate fi considerat o generalizare a tiparelor Connection pool și Thread
pool.
• Resource acquisition is initialization (RAII)29 - Garantează eliberarea
corespunzătoare a resurselor, legându-le de durata de viață a obiectelor
corespunzătoare.

Tipare structurale
Tipare structurale specificate în (Gamma, Helm, Johnson, & Vlissides, 1994):
• Adapter / Wrapper / Translator30 - Convertește interfața unei clase într-o altă
interfață solicitată de către client. Un tipar Adapter permite conlucrarea unor clase care
altfel nu ar putea interacționa din cauza interfețelor incompatibile.
• Bridge31 - Decuplează o abstracție de implementarea sa, permițând celor două să
evolueze independent.
• Composite32 - Asamblează obiectele în arbori de obiecte, de așa natură încât să
reprezinte ierarhii întreg-componente. Tiparul Composite permite clienților să trateze
obiectele individuale și compozițiile de obiecte în mod uniform.
• Decorator33 - Atașează responsabilități adiționale unui obiect, menținând în mod
dinamic aceeași interfață. Tiparul Decorator furnizează o alternativă la crearea de
subclase în scopul extinderii funcționalității.
• Facade34 - Furnizează o interfață unificată pentru un set de interfețe dintr-un subsistem.
Tiparul Facade definește o interfață de nivel mai înalt care face ca subsistemul să fie
mai ușor de utilizat.
• Flyweight35 - Utilizarea distribuirii pentru a suporta un număr mare de obiecte în mod
eficient.
• Proxy36 - Furnizează un surogat / înlocuitor pentru un alt obiect, pentru a controla
accesul la acesta.
Tipare structurale introduse ulterior:
• Extension object – Un obiect care permite adăugarea de funcționalitate la o ierarhie,
fără a schimba ierarhia.
• Front controller37 - Acest tipar este legat de proiectarea aplicațiilor Web. Furnizează
un punct centralizat de manipulare a cererilor.

28
https://en.wikipedia.org/wiki/Object_pool_pattern
29
https://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initialization
30
https://en.wikipedia.org/wiki/Adapter_pattern
31
https://en.wikipedia.org/wiki/Bridge_pattern
32
https://en.wikipedia.org/wiki/Composite_pattern
33
https://en.wikipedia.org/wiki/Decorator_pattern
34
https://en.wikipedia.org/wiki/Facade_pattern
35
https://en.wikipedia.org/wiki/Flyweight_pattern
36
https://en.wikipedia.org/wiki/Proxy_pattern
37
https://en.wikipedia.org/wiki/Front_controller

142
• Marker38 - O interfață goală utilizată pentru asocierea metadatelor39 cu o clasă.
• Module40 - Grupează câteva elemente înrudite, cum ar fi clase, singletoane, metode,
care sunt utilizate global, într-o singură entitate conceptuală.
• Twin41 - Tiparul Twin permite simularea moștenirilor multiple în limbajele de
programare care nu permit acest lucru.

Tipare comportamentale
Tipare comportamentale specificate în (Gamma, Helm, Johnson, & Vlissides, 1994):
• Chain of responsibility42 - Evitarea cuplării expeditorului unei cereri cu destinatarul
prin oferirea posibilității de rezolvare a cererii către mai multe obiecte. Obiectele
destinatar sunt apelate în lanț și cererea este pasată de-a lungul lanțului până când un
obiect o rezolvă.
• Command43 - Încapsularea unei cereri sub formă de obiect, permițând astfel
parametrizarea clienților cu diferite cereri și programarea sau jurnalizarea cererilor.
Permite de asemenea efectuarea de acțiuni ireversibile.
• Interpreter44 - Pentru un anumit limbaj, se permite reprezentarea gramaticii sale și se
furnizează un interpretor care utilizează reprezentarea respectivă pentru a interpreta
”propozițiile” din limbajul respectiv.
• Iterator45 - Furnizează o modalitate de accesare a elementelor unui obiect agregat în
mod secvențial fără a fi nevoie de acces direct la structura sa internă.
• Mediator46 - Definește un obiect care încapsulează modul de interacțiune al unui set
de alte obiecte. Tiparul Mediator promovează cuplarea slabă prin a împiedica obiectele
să face referințe în mod explicit unul la celălalt și permite ca interacțiunea lor să se
modifice independent.
• Memento47 - Fără a viola principiul încapsulării, capturează și externalizează starea
internă a unui obiect la un moment dat, permițând ca obiectul să fie readus la această
stare ulterior.
• Observer48 / Publish/subscribe49 - Definește o dependință unul-la-mai-mulți între
obiecte astfel încât toate obiectele incluse să fie notificate și actualizate automat.
• State50 - Permite unui obiect să își modifice comportamentul atunci când starea sa
internă se schimbă. Este echivalent cu schimbarea aparentă a clasei obiectului.

38
https://en.wikipedia.org/wiki/Marker_interface_pattern
39
Metadate = date utilizate pentru descrierea altor date. https://en.wikipedia.org/wiki/Metadata
40
https://en.wikipedia.org/wiki/Module_pattern
41
https://en.wikipedia.org/wiki/Twin_pattern
42
https://en.wikipedia.org/wiki/Chain_of_responsibility_pattern
43
https://en.wikipedia.org/wiki/Command_pattern
44
https://en.wikipedia.org/wiki/Interpreter_pattern
45
https://en.wikipedia.org/wiki/Iterator_pattern
46
https://en.wikipedia.org/wiki/Mediator_pattern
47
https://en.wikipedia.org/wiki/Memento_pattern
48
https://en.wikipedia.org/wiki/Observer_pattern
49
https://en.wikipedia.org/wiki/Publish/subscribe
50
https://en.wikipedia.org/wiki/State_pattern

143
• Strategy51 - Definește o familie de algoritmi, îl încapsulează pe fiecare dintre ei și îi
face interschimbabili. Tiparul Strategy permite schimbarea algoritmului folosit
independent de clienții care îl utilizează.
• Template method52 - Definește ”scheletul” unui algoritm în funcțiune și trimite spre
execuție anumiți pași către subclase. Metode template permit subclaselor să
redefinească anumiți pași ai algoritmului fără a schimba structura internă a acestuia.
• Visitor53 - Reprezintă o operație care trebuie executată pe elementele unei structuri
obiect. Tiparul Visitor permite definirea de noi operații fără schimbarea clasei
elementelor pe care operează.
Obs. Se numește obiect agregat un obiect având o structură complexă, conținând o cantitate
oarecare de date similare din punct de vedere al tipului și / sau utilizării 54. Obiecte agregate
tipice sunt listele, vectorii, arborii și generatorii. Structura obiectelor agregate permite în cele
mai multe cazuri parcurgerea secvențială, direct sau indirect. În acest caz, structura agregată se
numește iterabilă (obiect iterabil), iar variabila folosită ca auxiliar în timpul parcurgerii se
numește iterator, ca și tiparul.
Obs. Agregarea55, ca și compoziția, este o operație care permite combinarea unor obiecte sau
tipuri de date mai simple, formând unele noi, mai complexe. Agregarea diferă de compoziția
obișnuită prin faptul că nu implică proprietatea obiectului agregat asupra obiectelor ”din
subordine”. În cazul compoziției, când obiectul proprietar este distrus, la fel se întâmplă și cu
obiectele conținute. În agregare, acest lucru nu este neapărat adevărat. De exemplu, o
universitate conține diverse departamente și fiecare departament are un număr de profesori.
Dacă universitatea se va închide, departamentele nu vor mai exista, dar profesorii din aceste
departamente vor continua să existe. Prin urmare, o universitate poate fi privită ca o compoziție
a departamentelor, în timp ce departamentele sunt agregări de profesori. În plus, un profesor ar
putea lucra în mai multe departamente, dar un departament nu poate face parte din mai multe
universități.
Compoziția este de obicei implementată astfel încât un obiect compozit conține direct obiectele
din subordine. La agregare, obiectul agregat conține doar referințe / pointeri la obiect din
subordine (și nu are responsabilitatea administrării acestora de-a lungul ciclului lor de viață).
Uneori, agregarea este denumită și compoziție atunci când distincția dintre compoziția
obișnuită și agregare nu are importanță. Spre exemplu, tipurile de obiecte agregate enumerate
mai sus (liste, vectori, arbori, generatori) sunt de fapt obiecte compozite.
Tipare comportamentale introduse ulterior:
• Blackboard56 - Tipar specific inteligenței artificiale care permite combinarea unor
surse disparate de date.
• Null object57 - Evită apariția de referințe nule prin furnizarea unui obiect implicit.

51
https://en.wikipedia.org/wiki/Strategy_pattern
52
https://en.wikipedia.org/wiki/Template_method_pattern
53
https://en.wikipedia.org/wiki/Visitor_pattern
54
https://en.wikipedia.org/wiki/Aggregate_pattern
55
https://en.wikipedia.org/wiki/Object_composition#Aggregation
56
https://en.wikipedia.org/wiki/Blackboard_(design_pattern)
57
https://en.wikipedia.org/wiki/Null_Object_pattern

144
• Servant58 - Definește o funcționalitate comună pentru un grup de clase. Tiparul Servant
mai este, de asemenea, denumit frecvent și Helper class sau Utility class pentru un set
definit de clase. Clasele helper nu au în general obiecte instanțiate pentru că, în cele
mai multe cazuri, nu conțin decât metode implementate static pentru diverse tipuri de
obiecte ale unor clase pe care le deservesc.
• Specification59 - Logică de afaceri recombinabilă în mod oarecum asemănător cu
operațiile booleene.

Tipare de concurență
Tiparele de concurență sunt utilizate în programarea aplicațiilor concurente / distribuite, fie că
sunt orientate pe obiecte, fie că nu:
• Active Object60 - Decuplează executarea metodei de apelarea metodei prin amplasarea
lor în fire de execuție diferite. Scopul este obținerea concurenței prin utilizarea
apelurilor ascincrone la metode61 și a unui programator62 pentru tratarea cererilor.
• Balking63 - Se execută acțiuni pe un obiect numai atunci când obiectul se află într-o
anumită stare.
• Binding properties64 - Combină mai multe tipare Observer pentru a sincroniza sau a
coordona în mod forțat într-un mod oarecare proprietățile diverselor obiecte65.
• Compute kernel66 - Executarea aceleiași prelucrări matematice de mai multe ori în
paralel, cu parametri întregi diferiți, în algoritmi care nu presupun ramificări logice,
precum înmulțirea matricilor sau rețele neuronale convoluționale optimizate pentru
execuția pe GPU.
• Double-checked locking67 - Reduce costurile de resurse implicate de obținerea unui
lock, prin testarea în avans, într-o manieră superficială, a posibilității obținerii lock-
ului; doar dacă această testare inițială are succes se trece la obținerea propriu-zisă a
lock-ului. Poate fi o manieră nesigură de lucru pentru anumite combinații de limbaj /
hardware și este considerată un anti-tipar68 în astfel de condiții.
• Event-based asynchronous69 - Încearcă să rezolve unele probleme care apar în
programe cu mai multe fire de execuție atunci când se utilizează tiparul asincron
(Nagel, Evjen, Glynn, Watson, & Skinner, 2008).
• Guarded suspension70 - Administrează operațiile care necesită atât obținerea unui lock
cât și satisfacerea unei precondiții înainte ca o anumită operație să poată fi executată.

58
https://en.wikipedia.org/wiki/Design_pattern_Servant
59
https://en.wikipedia.org/wiki/Specification_pattern
60
https://en.wikipedia.org/wiki/Active_object
61
https://en.wikipedia.org/wiki/Asynchronous_method_invocation
62
https://en.wikipedia.org/wiki/Scheduling_(computing)
63
https://en.wikipedia.org/wiki/Balking_pattern
64
https://en.wikipedia.org/wiki/Binding_properties_pattern
65
http://wiki.c2.com/?BindingProperties
66
https://en.wikipedia.org/wiki/Compute_kernel
67
https://en.wikipedia.org/wiki/Double_checked_locking_pattern
68
Mai multe despre anti-tipare de proiectare software într-o secțiune următoare.
69
https://en.wikipedia.org/wiki/Event-Based_Asynchronous_Pattern
70
https://en.wikipedia.org/wiki/Guarded_suspension

145
• Join71 - Furnizează o cale de scriere a programelor concurente, paralele și distribuite
prin transmiterea de mesaje. Prin comparație cu utilizarea firelor de execuție și a lock-
urilor, acest tipar este un tipar de programare de nivel înalt.
• Lock72 - Un fir de execuție pune un ”lock” (lacăt) pe o resursă, împiedicând alte fire de
execuție să acceseze sau modifice resursa respectivă73.
• Messaging design pattern (MDP)74 - Permite interschimbarea de informație (spre
exemplu prin mesaje) între diverse componente și aplicații.
• Monitor object75 - Un obiect ale cărui metode sunt implicate în excludere mutuală76,
împiedicând astfel utilizarea sa de către mai multe obiecte simultan.
• Reactor77 - Un obiect Reactor furnizează o interfață asincronă către resurse care sunt
manipulate sincron în mod normal.
• Read-write lock78 - Permite accesul concurent pentru citire la un obiect, dar necesită
acces exclusiv pentru operații de scriere.
• Scheduler79 - Controlează în mod explicit intervalele de timp în care firele de execuție
pod executa cod single-threaded.
• Thread pool80 - Un număr de fire de execuție sunt create pentru a efectua un număr de
sarcini, sarcini organizate uzual într-o coadă. În mod tipic numărul de sarcini îl
depășește pe cel de fire de execuție. Poate fi considerat un caz special al tiparului Object
pool.
• Thread-specific storage81 - Memorie alocată static (”global”) la nivelul unui fir de
execuție.

Tipare specifice pentru un anumit domeniu


Există o multitudine de tipuri de tipare de proiectare software specifice pentru un anumit
domeniu. Exemple de astfel de categorii sunt:
• Tipare de vizualizare a informațiilor (Heer & Agrawala, 2006).
• Tipare de proiectarea securizată (Dougherty, Sayre, Seacord, Svoboda, & Togashi,
2009).
• Tipare de securitatea utilizării (Garfinkel, 2005).
• Tipare de proiectare a modelului de afaceri (How to design your Business Model as a
Lean Startup?, 2010).
Obs. S-a observat că necesitatea utilizării unora dintre tiparele de proiectare poate fi doar un
simptom că unele caracteristici lipsesc din limbajul de programare folosit (ex. Java, C++).

71
https://en.wikipedia.org/wiki/Join-pattern
72
https://en.wikipedia.org/wiki/Lock_(computer_science)
73
http://www.castle-cadenza.demon.co.uk/lock.htm
74
https://en.wikipedia.org/wiki/Messaging_pattern
75
https://en.wikipedia.org/wiki/Monitor_(synchronization)
76
https://en.wikipedia.org/wiki/Mutual_exclusion
77
https://en.wikipedia.org/wiki/Reactor_pattern
78
https://en.wikipedia.org/wiki/Read/write_lock_pattern
79
https://en.wikipedia.org/wiki/Scheduler_pattern
80
https://en.wikipedia.org/wiki/Thread_pool_pattern
81
https://en.wikipedia.org/wiki/Thread-Specific_Storage

146
Practic, se poate susține faptul că tiparele de proiectare software sale sunt pur și simplu soluții
de rezolvare a caracteristicilor lipsă din limbajul respectiv (ex. C++), înlocuind elementele
abstracte elegante care ar fi fost necesare cu tipare de concrete de mari dimensiuni. Peter Norvig
demonstrează că 16 din cele 23 de tipare propuse inițial (pentru C++) sunt simplificate sau
eliminate, prin intermediul suportului direct al limbajului, în Lisp sau Dylan (Norvig, 1998).
Observații asemănătoare au fost făcute de Hannemann și Kiczales care au implementat mai
multe dintre cele 23 de tipare de proiectare folosind un limbaj de programare orientat pe aspect
(AspectJ) și au arătat că dependențele la nivel de cod au fost eliminate din implementările a 17
din cele 23 de tipare de proiectare și că programarea orientată pe aspect ar putea simplifica
implementarea tiparelor de proiectare (Hannemann & Kiczales, 2002).
Obs. Utilizarea necorespunzătoare a tiparelor poate crește inutil complexitatea (McConnell,
2004).

Tipare de arhitectură software


Un tipar de arhitectură software este o soluție generală, reutilizabilă pentru problemă care
apar frecvent în arhitectura software într-un context dat (Taylor, Medvidović, & Dashofy,
2009). Tiparele de arhitectură software sunt similare cu tiparele de proiectare software, dar au
un domeniu de aplicare mai larg. Tiparele arhitecturale abordează diverse probleme în ingineria
software, cum ar fi limitările de performanță hardware, înalta disponibilitate și minimizarea
riscului de afaceri. Unele tipare arhitecturale au fost implementate în diverse framework-uri
software. Chiar dacă un tipar de arhitectură conține o imagine a unui sistem, el nu este nu este
o arhitectură. Un tipar de arhitectură este doar un concept care rezolvă și delimitează unele
elemente esențiale de coeziune ale unei arhitecturi software. Nenumărate arhitecturi diferite
pot implementa același tipar și pot împărtăși caracteristicile aferente.
Exemple de tipare de arhitecturi software, grupate pe domenii:
• Integrarea datelor82 / SOA (Service Oriented Architecture)83:
o ETL (data Extraction Transformation and Loading)84
o MFT (Managed File Transfer)85
o EAI (Enterprise Application Integration)86 / ESB (Enterprise Service Bus)87
• Arhitecturi de date88:
o TDS (Transaction data stores) / OLTP (Online Transaction Processing)89
o Master data store90
o Operational data store91

82
https://en.wikipedia.org/wiki/Data_integration
83
https://en.wikipedia.org/wiki/Service-oriented_architecture
84
https://en.wikipedia.org/wiki/Extract,_transform,_load
85
https://en.wikipedia.org/wiki/Managed_file_transfer
86
https://en.wikipedia.org/wiki/Enterprise_application_integration
87
https://en.wikipedia.org/wiki/Enterprise_service_bus
88
https://en.wikipedia.org/wiki/Data_architecture
89
https://en.wikipedia.org/wiki/Online_transaction_processing
90
https://en.wikipedia.org/wiki/Master_data_management
91
https://en.wikipedia.org/wiki/Operational_data_store

147
o Data mart92
o Data warehouse93
• Analytics și inteligența afacerii94:
o Transactional reporting
o Operational analytics
o Business analytics
o Predictive analytics
o Prescriptive analytics
o Streaming analytics
o Data science și advanced analytics
• Master data management95
o Master data hub
• Modelarea datelor96
o Modelarea dimensională a datelor97
o Modelarea Entitate-Relație98
• Inteligență Artificială99
o Managementul deciziilor100
o Recunoașterea vorbirii101
o Text analytics și NLP (Natural Language Processing)102
o NLG (Natural Language Generation)103
o ML (Machine Learning)104 și Deep Learning105
o Automatizarea robotizată a proceselor106
o Analiza imaginilor107 și a secvențelor video108
• Alte tipuri de tipare de arhitecturi:
o Broker pattern109
o Arhitectură bazată pe evenimente110
o Invocare implicită111

92
https://en.wikipedia.org/wiki/Data_mart
93
https://en.wikipedia.org/wiki/Data_warehouse
94
https://en.wikipedia.org/wiki/Business_intelligence
95
https://en.wikipedia.org/wiki/Master_data_management
96
https://en.wikipedia.org/wiki/Data_modeling
97
https://en.wikipedia.org/wiki/Dimensional_modeling
98
https://en.wikipedia.org/wiki/Entity%E2%80%93relationship_model
99
https://en.wikipedia.org/wiki/Artificial_intelligence
100
https://en.wikipedia.org/wiki/Decision_management
101
https://en.wikipedia.org/wiki/Speech_recognition
102
https://en.wikipedia.org/wiki/Natural_language_processing
103
https://en.wikipedia.org/wiki/Natural-language_generation
104
https://en.wikipedia.org/wiki/Machine_learning
105
https://en.wikipedia.org/wiki/Deep_learning
106
https://en.wikipedia.org/wiki/Robotic_process_automation
107
https://en.wikipedia.org/wiki/Image_analysis
108
https://en.wikipedia.org/wiki/Video_content_analysis
109
https://en.wikipedia.org/wiki/Broker_pattern
110
https://en.wikipedia.org/wiki/Event-driven_architecture
111
https://en.wikipedia.org/wiki/Implicit_invocation

148
o Arhitectură stratificată112
o Arhitectură hexagonală113
o Microservicii114
o Action-domain-responder115
o Model–view–controller116
o Presentation-abstraction-control117
o Model-view-presenter118
o Model-view-viewmodel119
o Entity–component–system120
o Entity-control-boundary121
o Arhitectură multinivel (arhitecturi pe 1, 2, 3, 4, n straturi)122
o Naked objects123
o Peer-to-peer124
o Pipe and filter architecture125
o Space-based architecture126

Tipare de interacțiune
Nota autorului: Tiparele de interacțiune nu au legătură directă cu domeniul programării
orientate pe obiecte, decât în măsura în care și aplicațiile realizate în această filosofie au nevoie
să interacționeze cu utilizatorul. Le menționez totuși aici pentru a lua în considerare toate
categoriile importante de tipare, întregind astfel această secțiune.
Tiparele de interacțiune sunt tipare de proiectare aplicate în contextul interacțiunii om-
calculator, care descriu soluții comune pentru problemele ridicate de realizarea interfețelor
grafice cu utilizatorul.
Tiparele de proiectare a interacțiunii sunt o modalitate de a descrie soluții la probleme comune
de utilizare sau accesibilitate într-un context specific. Tiparele de interacțiune documentează
moduri de interacțiune care facilitează utilizatorilor înțelegerea interfeței și îndeplinirea în bune
condiții a sarcinilor (Tidwell J. , 1999).
Obs. Spre deosebire de tiparele de proiectare și cele de arhitectură prezentate anterior, care
sunt foarte stabile, în sensul că, exceptând faptul că li s-au adăugat unele noi, au rămas la fel în

112
https://en.wikipedia.org/wiki/Layer_(object-oriented_design)
113
https://en.wikipedia.org/wiki/Hexagonal_architecture_(software)
114
https://en.wikipedia.org/wiki/Microservices
115
https://en.wikipedia.org/wiki/Action-domain-responder
116
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
117
https://en.wikipedia.org/wiki/Presentation-abstraction-control
118
https://en.wikipedia.org/wiki/Model-view-presenter
119
https://en.wikipedia.org/wiki/Model-view-viewmodel
120
https://en.wikipedia.org/wiki/Entity%E2%80%93component%E2%80%93system
121
https://en.wikipedia.org/wiki/Entity-control-boundary
122
https://en.wikipedia.org/wiki/Multitier_architecture
123
https://en.wikipedia.org/wiki/Naked_objects
124
https://en.wikipedia.org/wiki/Peer-to-peer
125
https://en.wikipedia.org/wiki/Pipe_and_filter_architecture
126
https://en.wikipedia.org/wiki/Space-based_architecture

149
ultimii 25 de ani, domeniul tiparelor de interacțiune este un domeniu fluid, în continuă evoluție,
evoluție stimulată de cea a hardware-ului de afișare și control.
Obs. Diverse surse clasifică tiparele de interacțiune în categoriile UI Pattern (User Interface
Design Pattern) și respectiv UX (User eXperience Design Pattern). Deși denumirile ar indica
acest lucru, în marea majoritate a cazurilor nu există o distincție clară între cei doi termeni și
sunt folosiți interschimbabil. Câteva comentarii pe această temă pot fi găsite în aceste două
articole: https://careerfoundry.com/en/blog/ui-design/user-interface-patterns/ și respectiv
https://careerfoundry.com/en/blog/ux-design/ux-patterns-why-use-them/.
O scurtă introducere în problematica tiparelor de interacțiune poate fi găsită la adresa:
https://www.mockplus.com/blog/post/ux-design-patterns.
O lucrare destul de complexă pe acest subiect poate fi consultată la adresa:
https://www.interaction-design.org/literature
Un element specific tiparelor de interacțiune este faptul că ele variază de la un mod de realizare
a interfeței la altul (interfețe native pentru sistemul de operare gazdă, versus interfețe web) și
de la o categorie de platforme hardware la alta (PC, având ca modalitate de control dominantă
mouse + tastatură, versus echipamente mobile, care au ca modalitate de control dominantă
touch + gesturi).
În urma acestei separări pot fi distinse următoarele categorii principale de tipare de interacțiune:
• Tipare de interacțiune pentru aplicații native într-un anumit sistem de operare.
• Tipare de interacțiune pentru aplicații web.
• Tipare de interacțiune pentru aplicații mobile și/sau multiplatformă.

Tipare de interacțiune pentru aplicații native într-un anumit sistem de operare.


Sistemele de operare cel mai utilizate în acest moment sunt cele din familia Microsoft
Windows, sistemele de operare MacOS și diversele distribuții Linux.
O introducere în utilizarea tiparelor de interacțiune pentru aplicațiile native în diverse sisteme
de operare poate fi găsită la adresa: https://uxplanet.org/designing-for-pc-apps-4554d8a0aa85.
Pentru sistemele de operare din familia Microsoft Windows, tiparele de interacțiune de bază
sunt furnizate de către producătorul sistemului de operare sub forma unei referințe complete
pentru interfața grafică a sistemului de operare Windows 10. Interfața a fost denumită inițial
Metro și a fost redenumită ulterior Microsoft Design Language, iar referința este denumită
Fluent Design System: https://www.microsoft.com/design/fluent/#/. Este important de
menționat faptul că această referință nu este, de fapt, prevăzută să fie limitată doar la aplicațiile
native Windows, ci poate fi aplicată și pentru aplicații web, iOS sau Android. De asemenea,
trebuie menționat că referința respectivă nu conține doar tiparele de interacțiuni ci este o
referință completă, conținând și elemente de design și funcționalitate. O introducere în Fluent
Design poate fi găsită la adresa: https://docs.microsoft.com/en-us/windows/apps/fluent-
design-system.
Pentru sistemul de operare MacOS, tiparele de interacțiune sunt furnizate de către producătorul
sistemului de operare, sub forma unui ghid, care conține nu numai tipare de interacțiune ci și

150
un îndrumar complet de design și funcționalitate, disponibil la adresa:
https://developer.apple.com/design/human-interface-guidelines/macos/overview/themes/.
Pentru diversele distribuții Linux, lucrurile stau ceva mai complicat, pe de o parte pentru că
sunt disponibile nu una, ci mai mult interfețe grafice, având caracteristici destul de diferite și
pe de altă parte pentru că sistemele de operare din această familie sunt dezvoltate în sistem
open-source, astfel încât nu există o abordare unitară singulară asupra modului în care ar trebui
dezvoltate interfețele cu utilizatorii. În ciuda aceste stări de fapt, sunt disponibile diverse
referințe principale conținând tipare de interacțiune, cum ar fi:
• GNOME Human Interface Guidelines - https://developer.gnome.org/hig/stable/
• KDE Human Interface Guidelines - https://hig.kde.org/
• Ubuntu Design - https://design.ubuntu.com/

Tipare de interacțiune pentru aplicații web.


În zona aplicațiilor web nu există o autoritate centrală care să producă tipare de interacțiune
care să fie ulterior acceptate de către o majoritate a dezvoltatorilor. Există în schimb un număr
de companii și organizații de profil mai cunoscute, din care voi aminti câteva (unele dintre ele
se ocupă și de design pentru celelalte categorii de aplicații):
• IBM Carbon Design System - https://www.carbondesignsystem.com/
• PatternFly - https://www.patternfly.org/v4/get-started/about
• Welie - http://www.welie.com/patterns/
• Styleguides - http://styleguides.io/examples.html#patterns
• Design Systems Repo - https://designsystemsrepo.com/design-systems/
• UCPin Adele - https://adele.uxpin.com/
• UI Patterns - http://ui-patterns.com/patterns
• Atomic UI Pattern Library Sketch Resource - https://www.sketchappsources.com/free-
source/2979-atomic-design-template-sketch-freebie-resource.html
• UI Scraps - https://uiscraps-blog.tumblr.com/
• UX Planet - https://uxplanet.org/

Tipare de interacțiune pentru aplicații mobile și / sau multiplatformă.


O introducere în utilizarea tiparelor de interacțiune pentru aplicațiile mobile poate fi găsită la
adresa: https://recro.io/blog/how-to-design-best-ui-for-mobile-apps.
Specificația Google Material Design este ceea ce se poate numi un depozit central de tipare de
interacțiune și elemente de design pentru aplicații mobile Android și multiplatformă:
https://material.io/design/introduction/#.
Pentru telefoanele Apple, echivalentul este: https://developer.apple.com/design/human-
interface-guidelines/ios/overview/themes/.
Alte câteva surse de tipare de interacțiune pentru aplicații mobile și multiplatformă sunt:
• Mobile-patterns - https://www.mobile-patterns.com/
• Pttrns - https://pttrns.com/
• Cocoa Controls - https://www.cocoacontrols.com/
• Inspired UI - https://inspired-ui.com/

151
• Capptivate - https://github.com/capptivateco/capptivate
• Android niceties - https://androidniceties.tumblr.com/
• MailChimp - https://ux.mailchimp.com/patterns/color

Anti-tipare
Nota autorului: Dacă este important să știi ce să faci că să construiești o aplicație de bună
calitate și de asta se ocupă, printre altele, tiparele enumerate mai sus, este la fel de important și
să știi și ce să nu faci. De acest lucru se ocupă anti-tiparele.
Un anti-tipar (anti-pattern) este un răspuns comun la o problemă recurentă, răspuns care este
de obicei ineficient și riscă să fie extrem de contraproductiv (Budgen, 2003) (Ambler, 1998).
Termenul, creat în 1995 de Andrew Koenig (Koenig, 1995), a fost inspirat de lucrarea (Gamma,
Helm, Johnson, & Vlissides, 1994), lucrare care enumeră și descrie o serie de tipare de
proiectare în dezvoltarea de software, tipare pe care autorii săi le-au considerat extrem de
fiabile și eficiente.
Termenul a fost popularizat trei ani mai târziu de cartea AntiPatterns (Brown, Malveau,
McCormick, & Mowbray, 1998) (se pot vedea de asemenea http://wiki.c2.com/?AntiPattern și
http://wiki.c2.com/?AntiPatternsBook), care a extins utilizarea termenului de anti-tipar dincolo
de domeniul proiectării software-ului pentru a se referi în mod informal la orice soluție comun
întâlnită, dar care este necorespunzătoare pentru o anumită problemă.
O listă extinsă de anti-tipare, grupate pe categorii:
• Anti-tipare ale programării orientate pe obiecte
o Anemic domain model - https://en.wikipedia.org/wiki/Anemic_domain_model
o Call super - https://en.wikipedia.org/wiki/Call_super
o Circle–ellipse problem -
https://en.wikipedia.org/wiki/Circle%E2%80%93ellipse_problem
o Circular dependency - https://en.wikipedia.org/wiki/Circular_dependency
o Constant interface - https://en.wikipedia.org/wiki/Constant_interface
o God object sau God class - https://en.wikipedia.org/wiki/God_object /
http://wiki.c2.com/?GodClass
o Object cesspool - https://en.wikipedia.org/wiki/Object_cesspool
o Object orgy - https://en.wikipedia.org/wiki/Object_orgy
o Poltergeists - https://en.wikipedia.org/wiki/Poltergeist_(computer_science)
o Sequential coupling - https://en.wikipedia.org/wiki/Sequential_coupling
o Yo-yo problem - https://en.wikipedia.org/wiki/Yo-yo_problem
• Anti-tipare de arhitectură software - http://wiki.c2.com/?ArchitectureAntiPattern
o Analogy Breakdown - http://wiki.c2.com/?AnalogyBreakdownAntiPattern
o Architecture as Requirements -
http://wiki.c2.com/?ArchitectureAsRequirements
o Architecture by Implication - http://wiki.c2.com/?ArchitectureByImplication
o Autogenerated Stovepipe -
http://wiki.c2.com/?AutogeneratedStovepipeAntiPattern

152
o Cover Your Assets - http://wiki.c2.com/?CoverYourAssets
o Design by Committee - http://wiki.c2.com/?DesignByCommittee
o Doer and Knower - http://wiki.c2.com/?DoerAndKnower
o Jumble - http://wiki.c2.com/?JumbleAntipattern
o Kill Two Birds with One Stone -
http://wiki.c2.com/?KillTwoBirdsWithOneStone
o Not the Appropriate Protocol -
http://wiki.c2.com/?NotTheAppropriateProtocol
o Politics Oriented Architecture -
http://wiki.c2.com/?PoliticsOrientedArchitecture
o Requirements as Architecture -
http://wiki.c2.com/?RequirementsAsArchitecture
o Roll Your Own Database - http://wiki.c2.com/?RollYourOwnDatabase
o Standing on the Shoulders of Midgets -
http://wiki.c2.com/?StandingOnTheShouldersOfMidgets
o Stovepipe - http://wiki.c2.com/?StovepipeAntiPattern
o Stovepipe System - http://wiki.c2.com/?StovepipeSystem
o Sumo Marriage - http://wiki.c2.com/?SumoMarriage
o Swiss Army Knife - http://wiki.c2.com/?SwissArmyKnife
• Anti-tipare de dezvoltare software - http://wiki.c2.com/?DevelopmentAntiPattern
o Abstraction inversion - https://en.wikipedia.org/wiki/Abstraction_inversion
o Accidental Complexity - http://wiki.c2.com/?AccidentalComplexity
o Accidental Inclusion - http://wiki.c2.com/?AccidentalInclusion
o Action at a distance -
https://en.wikipedia.org/wiki/Action_at_a_distance_(computer_science)
o Adding Epicycles - http://wiki.c2.com/?AddingEpicycles
o Alcohol Fueled Development -
http://wiki.c2.com/?AlcoholFueledDevelopment
o Ambiguous Viewpoint - http://wiki.c2.com/?AmbiguousViewpoint
o Asynchronous Unit Testing - http://wiki.c2.com/?AsynchronousUnitTesting
o Bear Trap
o Big Ball of Mud - http://wiki.c2.com/?BigBallOfMud
o Boat Anchor - http://wiki.c2.com/?BoatAnchor
o Busy waiting - https://en.wikipedia.org/wiki/Busy_waiting
o Caching failure - https://en.wikipedia.org/wiki/Caching_failure
o Cascading Dialog Boxes -
http://wiki.c2.com/?CascadingDialogBoxesAntiPattern
o Coding by exception - https://en.wikipedia.org/wiki/Coding_by_exception
o Continuous Obsolescence - http://wiki.c2.com/?ContinuousObsolescence
o Control Freak - http://wiki.c2.com/?ControlFreak
o Copy and Paste Programming -
http://wiki.c2.com/?CopyAndPasteProgramming
o Crci Cards - http://wiki.c2.com/?CrciCards
o Creeping Featuritis - http://wiki.c2.com/?CreepingFeaturitis

153
o Database-as-IPC - https://en.wikipedia.org/wiki/Database-as-IPC
o DbClass - http://wiki.c2.com/?DbClass
o Dead End - http://wiki.c2.com/?DeadEnd
o Dependency hell - https://en.wikipedia.org/wiki/Dependency_hell
o Design for the Sake of Design -
http://wiki.c2.com/?DesignForTheSakeOfDesign
o DLL hell - https://en.wikipedia.org/wiki/DLL_hell
o Error hiding - https://en.wikipedia.org/wiki/Error_hiding
o Every Fool Their Own Tool -
https://en.wikipedia.org/wiki/Every_Fool_His_Own_Tool
o Exception Funnel - http://wiki.c2.com/?ExceptionFunnel
o Extension conflict - https://en.wikipedia.org/wiki/Extension_conflict
o Floating Point Currency - http://wiki.c2.com/?FloatingPointCurrency
o Floating Point Fractions - http://wiki.c2.com/?FloatingPointFractions
o Fool Trap - http://wiki.c2.com/?FoolTrap
o Functional Decomposition - http://wiki.c2.com/?FunctionalDecomposition
o Gold plating - https://en.wikipedia.org/wiki/Gold_plating_(analogy)
o Golden Hammer - http://wiki.c2.com/?GoldenHammer / Silver bullet -
https://en.wikipedia.org/wiki/No_Silver_Bullet
o Grenade Message - http://wiki.c2.com/?GrenadeMessage
o Hard code - https://en.wikipedia.org/wiki/Hard_code
o Hidden Requirements - http://wiki.c2.com/?HiddenRequirements
o If it is Working Don’t Change - http://wiki.c2.com/?IfItIsWorkingDontChange
o Implementation Inheritance - http://wiki.c2.com/?ImplementationInheritance
o Improbability factor - https://en.wikipedia.org/wiki/Improbability_factor
o Inner-platform effect - https://en.wikipedia.org/wiki/Inner-platform_effect
o Input Kludge - http://wiki.c2.com/?InputKludge
o Interface bloat - https://en.wikipedia.org/wiki/Interface_bloat
o Invented here - https://en.wikipedia.org/wiki/Invented_here
o It’s An Operator Problem - http://wiki.c2.com/?ItsAnOperatorProblem
o JAR hell - https://en.wikipedia.org/wiki/JAR_hell
o Job Keeper - http://wiki.c2.com/?JobKeeper
o Junkyard Coding - http://wiki.c2.com/?JunkyardCoding
o Kitchen Sink Design - http://wiki.c2.com/?KitchenSinkDesign
o Lasagna code - https://en.wikipedia.org/wiki/Spaghetti_code#Lasagna_code
o Lava Flow - http://wiki.c2.com/?LavaFlow
o Loop-switch sequence - https://en.wikipedia.org/wiki/Loop-switch_sequence
o Magic Container - http://wiki.c2.com/?MagicContainer
o Magic numbers -
https://en.wikipedia.org/wiki/Magic_number_(programming)#Unnamed_num
erical_constants
o Magic pushbutton - https://en.wikipedia.org/wiki/Magic_pushbutton

154
o Magic strings -
https://en.wikipedia.org/wiki/Magic_string_(programming)#Magic_strings_in
_code
o Not Invented Here - http://wiki.c2.com/?NotInventedHere
o Null Flag - http://wiki.c2.com/?NullFlag
o Over Generalization of Business Logic -
http://wiki.c2.com/?OverGeneralizationOfBusinessLogic
o OverUse of Patterns - http://wiki.c2.com/?OverUseOfPatterns
o Parsing HTML With Regex - http://wiki.c2.com/?ParsingHtmlWithRegex
o Passing Nulls to Constructors -
http://wiki.c2.com/?PassingNullsToConstructors
o Path of Least Resistance - http://wiki.c2.com/?PathOfLeastResistance
o PhatWare - http://wiki.c2.com/?PhatWareAntiPattern
o Premature optimization -
https://en.wikipedia.org/wiki/Optimization_(computer_science)#When_to_opt
imize
o Programming by permutation -
https://en.wikipedia.org/wiki/Programming_by_permutation
o Race hazard - https://en.wikipedia.org/wiki/Race_hazard
o Reinventing the square wheel -
https://en.wikipedia.org/wiki/Reinventing_the_square_wheel
o Repeating yourself - https://en.wikipedia.org/wiki/Don%27t_Repeat_Yourself
o Requirements Tossed Over The Wall -
http://wiki.c2.com/?RequirementsTossedOverTheWall
o Rube Goldberg Machine - http://wiki.c2.com/?RubeGoldbergMachine
o Secret Society - http://wiki.c2.com/?SecretSociety
o Shotgun surgery - https://en.wikipedia.org/wiki/Shotgun_surgery
o Single Function Exit Point - http://wiki.c2.com/?SingleFunctionExitPoint
o Soft code - https://en.wikipedia.org/wiki/Softcoding
o Spaghetti Code - http://wiki.c2.com/?SpaghettiCode
o Specify Nothing - http://wiki.c2.com/?SpecifyNothing
o String Without Length - http://wiki.c2.com/?StringWithoutLength
o Sweep it Under the Rug -
http://wiki.c2.com/?SweepItUnderTheRugAntiPattern
o Tester Driven Development -
https://en.wikipedia.org/wiki/Tester_Driven_Development
o Thats Not Really an Issue - http://wiki.c2.com/?ThatsNotReallyAnIssue
o The Blob - http://wiki.c2.com/?TheBlob
o The Grand Old Duke of York - http://wiki.c2.com/?TheGrandOldDukeOfYork
o Tower of Voodoo - http://wiki.c2.com/?TowerOfVoodoo
o Vendor Lock In - http://wiki.c2.com/?VendorLockIn
o Voodoo Chicken Coding - http://wiki.c2.com/?VoodooChickenCoding
o Walking Through a Mine Field -
http://wiki.c2.com/?WalkingThroughaMineField

155
o Wolf Ticket - http://wiki.c2.com/?WolfTicket
o Zero Means Null - http://wiki.c2.com/?ZeroMeansNull
• Anti-tipare de management - http://wiki.c2.com/?ManagementAntiPattern
o An Athena - http://wiki.c2.com/?AnAthena
o Analysis Paralysis - http://wiki.c2.com/?AnalysisParalysis
o Appointed Team - http://wiki.c2.com/?AppointedTeam
o Architects Don’t Code - http://wiki.c2.com/?ArchitectsDontCode
o Band Aid - http://wiki.c2.com/?BandAid
o Blame Storming - http://wiki.c2.com/?BlameStorming
o Blow hard Jamboree - http://wiki.c2.com/?BlowhardJamboree
o Brooks's law - https://en.wikipedia.org/wiki/Brooks%27s_law
o Car Park Syndrome - http://wiki.c2.com/?CarParkSyndrome
o Carbon Copy His Manager - http://wiki.c2.com/?CarbonCopyHisManager
o Cart before the horse - https://en.wikipedia.org/wiki/Cart_before_the_horse
o Confusion of Objectives - http://wiki.c2.com/?ConfusionOfObjectives
o Corn Cob - http://wiki.c2.com/?CornCob
o Death by Planning - http://wiki.c2.com/?DeathByPlanning
o Death march -
https://en.wikipedia.org/wiki/Death_march_(software_development)
o Decision by Arithmetic - http://wiki.c2.com/?DecisionByArithmetic
o Discordant Reward Mechanisms -
http://wiki.c2.com/?DiscordantRewardMechanisms
o Dry Waterhole - http://wiki.c2.com/?DryWaterhole
o Egalitarian Compensation - http://wiki.c2.com/?EgalitarianCompensation
o Email is Dangerous - http://wiki.c2.com/?EmailIsDangerous
o Emperor’s New Clothes - http://wiki.c2.com/?EmperorsNewClothes
o Empire Building - http://wiki.c2.com/?EmpireBuilding
o False Surrogate Endpoint - http://wiki.c2.com/?FalseSurrogateEndpoint
o Fire Drill - http://wiki.c2.com/?FireDrill
o Fungible Project Manager - http://wiki.c2.com/?FungibleProjectManager
o Fungible Teams - http://wiki.c2.com/?FungibleTeams
o Give Me Estimates Now - http://wiki.c2.com/?GiveMeEstimatesNow
o Glass Wall - http://wiki.c2.com/?GlassWall
o Half Done Is Enough - http://wiki.c2.com/?HalfDoneIsEnough
o Heir Apparent - http://wiki.c2.com/?HeirApparent
o Hero Culture - http://wiki.c2.com/?HeroCulture
o Hidden Requirements - http://wiki.c2.com/?HiddenRequirements
o Idiot Proof Process - http://wiki.c2.com/?IdiotProofProcess
o Inappropriate Technical Objective -
http://wiki.c2.com/?InappropriateTechnicalObjective
o It’s Not Rocket Science - http://wiki.c2.com/?ItsNotRocketScience
o Leading Request - http://wiki.c2.com/?LeadingRequest
o Manager Controls Process - http://wiki.c2.com/?ManagerControlsProcess
o Mushroom Management - http://wiki.c2.com/?MushroomManagement

156
o Net Negative Producing Programmer -
http://wiki.c2.com/?NetNegativeProducingProgrammer
o Ninety-ninety rule - https://en.wikipedia.org/wiki/Ninety-ninety_rule
o Overengineering - https://en.wikipedia.org/wiki/Overengineering
o Plug Compatible Interchangeable Engineers -
http://wiki.c2.com/?PlugCompatibleInterchangeableEngineers
o ScapeGoat - http://wiki.c2.com/?ScapeGoat
o Scope creep - https://en.wikipedia.org/wiki/Scope_creep / feature creep -
https://en.wikipedia.org/wiki/Feature_creep
o Seagull Management - http://wiki.c2.com/?SeagullManagement
o Selling a Product You Can’t Realize -
http://wiki.c2.com/?SellingaProductYouCantRealize
o Shoot the Messenger - http://wiki.c2.com/?ShootTheMessenger
o Smoke and Mirrors - http://wiki.c2.com/?SmokeAndMirrors
o Specify Nothing - http://wiki.c2.com/?SpecifyNothing
o The Customers are Idiots - http://wiki.c2.com/?TheCustomersAreIdiots
o The Process Is the Deliverable -
http://wiki.c2.com/?TheProcessIsTheDeliverable
o They Understood Me - http://wiki.c2.com/?TheyUnderstoodMe
o Thrown Over The Wall - http://wiki.c2.com/?ThrownOverTheWall
o Train the Trainer - http://wiki.c2.com/?TrainTheTrainer
o Vietnam War - http://wiki.c2.com/?VietnamWarAntiPattern
o Viewgraph Engineering - http://wiki.c2.com/?ViewgraphEngineering
o Warm Bodies - http://wiki.c2.com/?WarmBodies
o We Are Idiots - http://wiki.c2.com/?WeAreIdiots
o Yet Another Meeting Will Solve It -
http://wiki.c2.com/?YetAnotherMeetingWillSolveIt
o Yet Another Programmer - http://wiki.c2.com/?YetAnotherProgrammer
o Yet Another Thread Will Solve It -
http://wiki.c2.com/?YetAnotherThreadWillSolveIt
• Anti-tipare de interacțiune - http://wiki.c2.com/?UserInterface
o Cascading Dialog Boxes -
http://wiki.c2.com/?CascadingDialogBoxesAntiPattern
• Anti-tipare organizaționale: http://wiki.c2.com/?OrganizationalAntiPattern
o Architects Don’t Code - http://wiki.c2.com/?ArchitectsDontCode
o Architects Play Golf - http://wiki.c2.com/?ArchitectsPlayGolf
o Bicycle shed - https://en.wikipedia.org/wiki/Parkinson%27s_law_of_triviality
o Bleeding edge - https://en.wikipedia.org/wiki/Bleeding_edge
o Bystander apathy - https://en.wikipedia.org/wiki/Bystander_apathy
o Cargo Cult - http://wiki.c2.com/?CargoCult
o Cash cow - https://en.wikipedia.org/wiki/Cash_cow
o CryptoCracy - http://wiki.c2.com/?CryptoCracy
o Escalation of commitment -
https://en.wikipedia.org/wiki/Escalation_of_commitment

157
o False Economy - http://wiki.c2.com/?FalseEconomy
o Fear of Success - http://wiki.c2.com/?FearOfSuccess
o Geographically Distributed Development -
http://wiki.c2.com/?GeographicallyDistributedDevelopment
o Groupthink - https://en.wikipedia.org/wiki/Groupthink
o Management by objectives -
https://en.wikipedia.org/wiki/Management_by_objectives
o Micromanagement - https://en.wikipedia.org/wiki/Micromanagement
o Moral hazard - https://en.wikipedia.org/wiki/Moral_hazard
o Peter principle - https://en.wikipedia.org/wiki/Peter_principle
o Software Merger - http://wiki.c2.com/?SoftwareMerger
o Typecasting - https://en.wikipedia.org/wiki/Typecasting_(acting)
o Untested but Finished - http://wiki.c2.com/?UntestedButFinished
• Anti-tipare de proces - http://wiki.c2.com/?ProcessAntiPatterns
o Appointed Team - http://wiki.c2.com/?AppointedTeam
o Discordant Reward Mechanisms -
http://wiki.c2.com/?DiscordantRewardMechanisms

Code smell & design smell


Dacă tot am vorbit mai sus despre ce nu ar trebui făcut în programarea orientată pe obiecte, în
particular și în programare, în general, ar trebui menționată și problema ”mirosului”. Mai
precis este vorba de termenii code smell și design smell, ambii desemnând maniere nedorite și
dăunătoare de a face lucrurile în programare.

Code smell
În programare, termenul code smell reprezintă orice caracteristică din codul sursă al unui
program care indică posibil o problemă mai profundă (Tufano, și alții, 2015) (Fowler,
CodeSmell, 2006). Termenul code smell este un termen subiectiv și variază în funcție de
limbaj, dezvoltator și metodologie de dezvoltare.
Termenul a fost popularizat de Kent Beck pe WardsWiki la sfârșitul anilor '90 și a început să
fie folosit mai intens după ce a fost popularizat în (Fowler, Refactoring - Improving the Design
of Existing Code, 1999) Este, de asemenea, un termen folosit în abordarea agile a dezvoltării
software.
Câteva exemple generale de code smell sunt indicate de (Wikipedia.Org, 2019):
• Code smell la nivel de aplicație:
o Cod duplicat (duplicated code): cod identic sau foarte similar în mai multe
locații.
o Complexitate forțată (contrived complexity): utilizarea forțată a modelelor de
design supra-complicate, acolo unde ar fi suficient un design mai simplu.
o Shotgun surgery: o schimbare care trebuie aplicată în mai multe clase în același
timp (a se vedea și anti-tiparul cu același nume).
• Code smell la nivel de clasă:

158
o Clasă prea mare (large class): o clasă care a crescut prea mult (a se vedea și God
object, la anti-tipare).
o Invidia pe facilități (feature envy): o clasă care folosește excesiv metodele altor
clase.
o Intimitate inadecvată (inappropriate intimacy): o clasă care are dependențe
legate de detaliile implementării unei alte clase (a se vedea și Object orgy, la
anti-tipare).
o Refused bequest: o clasă care are prioritate față de o metodă a unei clase de
bază, astfel încât contractul de clasa de bază nu este onorat de clasa derivata. (a
se vedea și principiul Liskov al substituției, în secțiunea 12).
o Lazy class / freeloader: o clasă care face prea puțin.
o Utilizarea excesivă a literalilor (excessive use of literals): literalii ar trebui puși
în cod sub formă de constante numite, pentru a îmbunătăți lizibilitatea și pentru
a evita erorile de programare. În plus, literalii pot și ar trebui să fie externalizați
în fișiere / scripturi de resurse sau în alte modalități de stocare a datelor, cum ar
fi bazele de date, pentru a facilita localizarea software-ului, dacă se
intenționează utilizarea aplicației în diferite limbi.
o Complexitatea ciclomatică (cyclomatic complexity)127: prea multe ramuri
(decizii) sau bucle (iterații); acest lucru poate indica faptul că o funcție ar trebui
împărțită în funcții mai mici sau că poate fi simplificată.
o Downcasting128: un mod de conversie forțată a tipului de date care strică
modelul de abstractizare; după un downcasting abstracția trebuie refăcută sau
eliminată.
o Variabilă orfană sau clasă constantă (orphan variable or constant class): o clasă
care conține o colecție de constante care ar aparține logic unei alte clase.
o Conglomerat de date (data clump): apare atunci când un grup de variabile sunt
transmise ”lipite” unele de altele în diferite părți ale programului. În general,
acest lucru sugerează că ar fi mai potrivit ca respectivele variabile să fie grupate
într-un singur obiect, care obiect să fie transmis.
• Code smell la nivel de metode:
o Prea mulți parametri (too many parameters): o funcție cu o listă lungă de
parametri este greu de citit și complică apelarea și testarea funcției. Poate indica
faptul că scopul funcției este prost conceput și că funcția ar trebui rescrisă astfel
încât responsabilitatea sa fie atribuita într-un mod mai curat.
o Metodă lungă (long method): o metodă, o funcție sau o procedură care a crescut
prea mult.
o Identificatori excesiv de lungi (excessively long identifiers): sunt în special
problematici dacă sunt rezultatul utilizării unei convenții de denumire în scopul
de evita ambiguități, lucru care ar trebui asigurat automat de către arhitectura
software.

127
https://en.wikipedia.org/wiki/Cyclomatic_complexity
128
https://en.wikipedia.org/wiki/Downcasting

159
o Identificatori excesiv de scurți (excessively short identifiers): numele unei
variabile ar trebui să reflecte funcția acesteia, exceptând cazul în care aceasta
este evidentă.
o Returnarea a excesiv de multe date (excessive return of data): o funcție sau o
metodă care returnează mai mult decât ceea ce are nevoie oricare dintre apelanții
săi.
o Linii de cod excesiv de lungi (excessively long line of code sau God Line): o
linie de cod care este prea lungă, ceea ce face codul dificil de citit, înțeles,
depanat, refactorizat sau chiar împiedică identificarea posibilităților de
reutilizare a software-ului.

Design smell
În programare, termenul design smell se referă la „structuri în proiectul software care indică
încălcarea principiilor fundamentale de proiectare și au impact negativ asupra calității
designului” (Suryanarayana, SG, & Sharma, 2014). Originea termenului design smell este
termenul code smell, care a fost prezentat în (Fowler, Refactoring - Improving the Design of
Existing Code, 1999) (a se vedea mai sus).
Existența unui design smell indică acumularea unor datorii de proiectare (un tip particular de
datorie tehnică – technical debt129). Erorile sau funcțiile neimplementate nu sunt contabilizate
ca design smell. Elementele de design smell apar din deciziile proaste de proiectare care fac ca
designul rezultat să fie fragil și dificil de întreținut. Identificarea aparițiilor design smell-ului
într-un sistem software și refactorizarea corespunzătoare pentru eliminarea acestora este o bună
practică pentru a evita acumularea de datorii tehnice.
Contextul proiectului respectiv (caracterizat prin diverși factori, cum ar fi problema de rezolvat,
ecosistemul de proiectare și platforma) joacă un rol important pentru a decide dacă o anumită
structură sau decizie ar trebui considerată design smell. De multe ori, este indicată acceptarea
unor apariții ale design smell-ului pentru a respecta constrângerile impuse de context.
Câteva exemple generale de design smell:
• Abstracție absentă (missing abstraction) - se folosesc conglomerate de date sau string-
uri codate în loc să se creeze o abstracție (Suryanarayana, SG, & Sharma, 2014).
Cunoscută și sub denumirea de „obsesie primitivă” sau „conglomerate de date”
(Fowler, Refactoring - Improving the Design of Existing Code, 1999) (a se vedea și
conglomerat de date la Code smell).
• Abstracție multifațetată (multifaceted abstraction) - unei abstracții îi sunt atribuite mai
multe responsabilități (Suryanarayana, SG, & Sharma, 2014). Cunoscută și sub
denumirea de „abuz de conceptualizare” (Trifu, 2005).
• Abstracție duplicată (duplicate abstraction) - două sau mai multe abstracții au nume
identice și / sau implementare identică (Suryanarayana, SG, & Sharma, 2014).
Cunoscută și sub denumirea de „clase alternative cu interfețe diferite” (Fowler,

129
https://en.wikipedia.org/wiki/Technical_debt

160
Refactoring - Improving the Design of Existing Code, 1999) și „artefacte de design
duplicat” (Stal, 2007).
• Încapsulare deficitară (deficient encapsulation) - nivelul de acces declarat al unuia sau
mai multor membri ai unei abstracții este mai permisiv decât este necesar
(Suryanarayana, SG, & Sharma, 2014).
• Încapsulare neexploatată (unexploited encapsulation) - codul client utilizează verificări
de tip explicite (folosind instrucțiuni if-else sau switch care verifică tipul obiectului), în
loc să exploateze variația tipurilor deja încapsulate într-o ierarhie (Suryanarayana, SG,
& Sharma, 2014).
• Modularizare defectă (broken modularization) - datele și / sau metodele care ar fi trebuit
să fie localizate într-o singură abstracție sunt separate și răspândite în mai multe
abstracții (Suryanarayana, SG, & Sharma, 2014).
• Modularizare insuficientă (insufficient modularization) - există o abstracție care nu a
fost complet descompusă în module și o descompunere suplimentară ar putea reduce
dimensionalitatea și / sau complexitatea implementării (Suryanarayana, SG, & Sharma,
2014).
• Dependența circulară (circular dependency130) - o modularizare iterativă, dependentă,
rezultată atunci când două sau mai multe abstracții depind direct sau indirect una de
cealaltă (creând o cuplare strânsă între abstracții) (Suryanarayana, SG, & Sharma,
2014). Cunoscută și sub denumirea de „dependențe ciclice” (Page-Jones, The practical
guide to structured systems design, 2nd edition, 1988) sau ”ierarhie ciclică”
(Suryanarayana, SG, & Sharma, 2014), atunci când un supertip într-o ierarhie depinde
de oricare dintre subtipurile sale (Suryanarayana, SG, & Sharma, 2014). Cunoscută și
sub denumirea de „cicluri de moștenire / referință” (Sefika, Sane, & Campbell, 1996).
• Ierarhie neluată în considerare (unfactored hierarchy) - există o duplicare inutilă între
tipurile dintr-o ierarhie (Suryanarayana, SG, & Sharma, 2014).
• Ierarhie defectă (Broken hierarchy) - un supertip și subtipul său conceptual nu se află
într-o relație de tipul „IS-A”, rezultând o substituibilitate defectă (Suryanarayana, SG,
& Sharma, 2014). Este cunoscută și sub denumirea de „utilizarea necorespunzătoare a
moștenirii” (Budd, 2001) și „aplicarea greșită a IS-A” (Page-Jones, Fundamentals of
object-oriented design in UML, 1999).

130
https://en.wikipedia.org/wiki/Circular_dependency

161
Secțiunea 14 – Seturi de tipare de proiectare - G.R.A.S.P. (General
Responsibility Assignment Software Patterns)131
Tiparele generale de atribuire a responsabilității (General Responsibility Assignment Software
Patterns, abreviat G.R.A.S.P.), sunt un set de nouă linii directoare pentru atribuirea
responsabilității către clase și obiecte în proiectarea aplicațiilor orientate pe obiecte (Umair,
2017).
Tiparele și principiile utilizate în G.R.A.S.P sunt: controler (controller), creator, indirectare
(indirection), expertul în informație (information expert), coeziunea ridicată (high cohesion),
cuplarea redusă (low coupling), polimorfismul (polymorphism), variațiile protejate (protected
variations) și fabricarea pură (pure fabrication). Ele nouă tipare răspund la probleme întâlnite
în dezvoltarea software-ului la aproape toate proiectele. Scopul lor nu este să pună la dispoziție
noi modalități de lucru, ci doar să le documenteze mai bine și să le standardizeze pe cele deja
existente în programarea orientată pe obiecte.
Obs. Cele nouă elemente ale G.R.A.S.P. se regăsesc într-un fel sau altul în principiile și tiparele
văzute deja până acum în această lucrare. Elementul de noutate este asamblarea lor împreună
într-un set coerent care are ca scop sistematizarea fluxului de lucru din aplicații.
Craig Larman a afirmat că "Unealta critică de proiectare pentru dezvoltarea de software este o
minte bine educată în principiile proiectării. Nu este UML sau orice altă tehnologie." (Larman,
2004). Din acest punct de vedere G.R.A.S.P. nu sunt decât un set de unelte mentale, un ajutor
în proiectarea aplicațiilor orientate pe obiecte.

Controler (Controller)
Tiparul Controler asigură atribuirea responsabilității, în cazul prelucrărilor necesitate de
evenimentele de sistem, pentru clasele care nu aparțin de interfața cu utilizatorul și care sunt
reprezentative pentru sistem sau pentru un scenariu de caz de utilizare. În acest context, se
numește obiect controler (controller object) un obiect care nu aparține de interfața cu
utilizatorul și care este responsabil pentru recepționarea sau manipularea unui eveniment de
sistem.
Un controler de caz de utilizare ar trebui utilizat pentru a avea grijă de toate evenimentele de
sistem presupuse de cazul de utilizare respectiv și poate fi utilizat pentru unul sau mai multe
cazuri de utilizare.
Putem defini controlerul ca fiind primul obiect din spatele interfeței cu utilizatorul care
recepționează și coordonează (”controlează”) o operație a sistemului. Este important de
menționat că asta ar trebui să fie singura sa funcție: să coordoneze sau să controleze activitatea,
delegând sarcinile de lucru efective către alte obiecte. Nu ar trebui să facă nimic sau aproape
nimic altceva. Din acest punct de vedere, controlerul poate fi gândit ca făcând parte din nivelul
aplicație / serviciu (dacă în aplicație există o distincție între acest nivel și nivelul de domeniu)

131
Tiparele G.R.A.S.P. sunt expuse pe larg și în tutorialul video Derek Banas - Object Oriented Design 10:
https://www.youtube.com/watch?v=9Y2mZger8kE

162
Creator
Este același lucru cu tiparul Factory din secțiunea 13. Crearea obiectelor este una dintre cele
mai comune activități dintr-un sistem orientat pe obiecte. Stabilirea clasei care creează obiecte
este un aspect fundamental al relației dintre obiectele unei anumite clase.
Ca regulă generală, o clasă B ar trebui să fie responsabilă pentru crearea de instanțe ale unei
clase A dacă se aplică una sau mai multe dintre regulile următoare:
• Instanțe ale lui B conțin sau agregă instanțe ale lui A
• Instanțe ale lui B înregistrează instanțe ale lui A
• Instanțe ale lui B utilizează îndeaproape instanțe ale lui A
• Instanțe ale lui B dețin informațiile de inițializare pentru instanțe ale lui A, informații
transmise la crearea acestor instanțe.

Indirectare (Indirection)
Tiparul Indirectare suportă cuplarea redusă între două elemente (obiecte, sisteme, subsisteme)
și creează potențial de reutilizare prin atribuirea responsabilității între cele două elemente unui
obiect intermediar. Un exemplu este introducerea componentei controler pentru medierea
contactului între date (model) și reprezentarea lor (view) în tiparul model-view-control (MVC).
Introducerea elementului intermediar asigură cuplarea redusă între cele două elemente inițiale.

Expertul în informație (Information expert)


Principiul expertului în informație (denumit și principiul expertului) este un principiu utilizat
pentru determinarea clasei către care să fie delegate responsabilități (termenul generic
responsabilități se referă la acțiuni care trebuie întreprinse, cum ar fi metode, câmpuri calculate
etc. și atribuirea responsabilităților constă în stabilirea clasei care trebuie să conțină acțiunile
respective).
Mergând pe acest principiu, se evaluează responsabilitatea care trebuie atribuită, se determină
care este informația necesară pentru îndeplinirea responsabilității respective și se caută clasa /
clasele în care este stocată aceasta informație. Odată identificată locația informației respective,
clasa care conține cea mai mare parte din această informație ar trebui să fie clasa căreia să îi
fie atribuită responsabilitatea (Larman, 2004).

Coeziune ridicată (High cohesion)


Conceptul de coeziune a fost expus anterior, în secțiunea 12.
Coeziunea ridicată132 este un tipar folosit pentru evaluare, care încearcă să mențină obiectele
la un nivel adecvat de concentrate, ușor de gestionat și de înțeles. Coeziunea ridicată este
utilizată în general pentru a sprijini cuplarea redusă. Coeziunea ridicată indică faptul că
responsabilitățile unui element dat (obiect, sistem, subsistem) sunt puternic legate între ele și
puternic concentrate. Împărțirea programelor în clase și subsisteme este un exemplu de
activități care măresc proprietățile coezive ale unui sistem. Prin opoziție, coeziunea scăzută
este o situație în care un element dat are prea multe responsabilități fără legătură. Elementele

132
https://en.wikipedia.org/wiki/Cohesion_(computer_science)

163
având o coeziune scăzută sunt adesea de greu de înțeles, de reutilizat, de întreținut și de
modificat (Larman, 2004).

Cuplare redusă (Low coupling)


Conceptul de cuplare a fost expus anterior, în secțiunea 12.
Cuplarea redusă133 este un tipar folosit pentru evaluare, care dictează modul de atribuire a
responsabilităților pentru a obține:
• dependență mai mică între clase.
• reducerea impactului schimbărilor efectuate într-o clasă asupra celorlalte clase.
• potențial mai mare de reutilizare.

Polimorfism (Polymorphism)
În conformitate cu principiul polimorfismului, responsabilitatea pentru definire unei modificări
de comportament a unui tip trebuie atribuită tipului pentru care se petrece modificarea. Acest
lucru se obține prin utilizarea operațiilor polimorfe și nu prin utilizarea de decizii bazate pe tip.

Variații protejate (Protected variations)


Tiparul numi variații protejate se referă la protejarea elementelor componente ale unei aplicații
(obiecte, sisteme, subsisteme) de efectele modificării altor elemente prin izolarea zonei
modificate cu ajutorul unei interfețe și prin utilizarea polimorfismului pentru a crea diverse
implementări ale acestei interfețe.

Fabricație pură (Pure fabrication)


O fabricație pură este o clasă care nu reprezintă echivalentul abstract al unui concept din
domeniul problemei de rezolvat, și care este special creată pentru a obține cuplarea redusă,
coeziunea ridicată și capacitatea potențială de reutilizare a codului derivată din cele două
(atunci când o soluție prezentată de expertul în informații nu rezolvă problema direct). Acest
tip de clasă se numește „serviciu” în proiectarea bazată pe domeniu.
Aplicarea celor nouă principii / tipare în realizarea aplicațiilor duce la obținerea de aplicații
mai consistente, mai ușor de înțeles, de modificat și de depanat.

133
https://en.wikipedia.org/wiki/Loose_coupling

164
Încheiere

Lucrarea de față a pornit de la ideea simplă a unei colecții de aplicații practice pentru disciplina
Programare Orientată pe Obiecte. Pe parcursul scrierii sale, s-a transformat încet, încet, într-o
colecție de explicații și exemple legate de acest domeniu.
Regretul meu legat de această carte este faptul că domeniul programării orientate pe obiecte
este un domeniu atât de vast încât este practic imposibilă scrierea unei singure cărți care să îl
acopere în întregime. Pe măsură ce am lucrat mi-am dorit să includ din ce în ce mai multe
informații și în același timp, am realizat că trebuie să dau la o parte din ce în ce mai multe
informații, punând în locul lor doar simple link-uri sau referințe.
Lăsând la o parte regretul menționat, ofer cu plăcere această carte în speranța că undeva,
cândva, îi va folosi cuiva pentru a asimila, sau pentru a înțelege mai bine domeniul programării
orientate pe obiecte.
Oricine ai fi, dacă ai ajuns cu cititul (și poate și învățatul) aici, îți urez spor mai departe!

165
Referințe bibliografice
Alexander, C. (1977). A Pattern Language: Towns, Buildings, Construction. Oxford: Oxford
University Press.
Ambler, S. W. (1998). Process patterns: building large-scale systems using object technology.
Cambridge: Cambridge University Press.
Bishop, J. (2012). C# 3.0 Design Patterns: Use the Power of C# 3.0 to Solve Real-World
Problems. O'Reilly Media.
Brown, W. J., Malveau, R. C., McCormick, H. W., & Mowbray, T. J. (1998). AntiPatterns:
Refactoring Software, Architectures, and Projects in Crisis. John Wiley & Sons.
Budd, T. (2001). An introduction to object-oriented programming, 3rd edition. Addison
Wesley.
Budgen, D. (2003). Software design. Harlow: Addison-Wesley.
CPlusPlus.Com. (2019). C++ Language. Preluat de pe CPlusPlus:
http://www.cplusplus.com/doc/tutorial/
Dougherty, C., Sayre, K., Seacord, R. C., Svoboda, D., & Togashi, K. (2009). Secure Design
Patterns. Software Engineering Institute.
Fenton, S. (2017). Pro TypeScript: Application-Scale JavaScript Development. Apress.
Fowler, M. (1999). Refactoring - Improving the Design of Existing Code. Addison-Wesley.
Fowler, M. (2006). CodeSmell. Preluat de pe MartinFowler.com:
https://martinfowler.com/bliki/CodeSmell.html
Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of
Reusable Object-Oriented Software. Pearson Education.
Garfinkel, S. L. (2005). Design Principles and Patterns for Computer Systems That Are
Simultaneously Secure and Usable. teză de doctorat.
Geeks-for-geeks.Org. (2019). C++ Programming Language. Preluat de pe Geeks for geeks:
https://www.geeksforgeeks.org/c-plus-plus/
Hannemann, J., & Kiczales, G. (2002). Design Pattern Implementation in Java and AspectJ.
Preluat de pe CWI.Nl:
https://homepages.cwi.nl/~storm/teaching/reader/HannemannKiczales02.pdf
Heer, J., & Agrawala, M. (2006). Software Design Patterns for Information Visualization.
IEEE Transactions on Visualization and Computer Graphics, 853-860.
How to design your Business Model as a Lean Startup? (2010). Preluat de pe The
Methodologist: https://torgronsund.wordpress.com/2010/01/06/lean-startup-business-
model-pattern/
Ingeno, J. (2018). Software Architect's Handbook. Packt Publishing.
ISO/IEC. (2005). ISO/IEC EN 19759:2005, Software Engineering — Guide to the Software
Engineering Body of Knowledge (SWEBOK). ISO/IEC.
ISO/IEC/IEEE. (2010). ISO/IEC/IEEE 24765:2010 Systems and software engineering —
Vocabulary. ISO/IEC/IEEE.
Koenig, A. (1995). Patterns and Antipatterns. Journal of Object-Oriented Programming, 46-
48.

166
Laakso, S. A. (2003). Collection of User Interface Design Patterns. University of Helsinki,
Dept. of Computer Science.
Larman, C. (2004). Applying UML and Patterns – An Introduction to Object-Oriented Analysis
and Design and Iterative Development, 3rd edition. Prentice Hall.
Liskov, B. H., & Wing, J. M. (1994). A behavioral notion of subtyping. ACM Transactions on
Programming Languages and Systems, 1811-1841.
Marsic, I. (2012). Software Engineering. Rutgers University.
Martin, R. C. (2000). Design Principles and Design Patterns. Preluat de pe
https://docs.google.com/file/d/0BxR1naE0JfyzV2JVbkYwRE5odGM/edit
Martin, R. C. (2003). Getting a SOLID start. Preluat de pe UncleBobConsultingLLC:
https://sites.google.com/site/unclebobconsultingllc/getting-a-solid-start
Martin, R. C. (2003). Principles of OOD. Preluat de pe ButUncleBob.com:
http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
McConnell, S. (2004). Code Complete: A Practical Handbook of Software Construction, 2nd
Edition. Redmond: Microsoft Press.
Meyer, B. (1988). Object-Oriented Software Construction. Prentice Hall.
Microsoft. (2009). Programarea Orientată pe Obiecte și Programarea Vizuală. București:
Microsoft Press.
Nagel, C., Evjen, B., Glynn, J., Watson, K., & Skinner, M. (2008). Event-based Asynchronous
Pattern. Professional C# 2008. Wiley.
Norvig, P. (1998). Design Patterns in Dynamic Languages. Preluat de pe Norvig.Com:
http://www.norvig.com/design-patterns/
Page-Jones, M. (1988). The practical guide to structured systems design, 2nd edition. Prentice
Hall.
Page-Jones, M. (1999). Fundamentals of object-oriented design in UML. Addison-Wesley
Professional.
Sefika, M., Sane, A., & Campbell, R. (1996). Monitoring compliance of a software system with
its high-level design models. Proceedings of the 18th international conference on
software engineering, ICSE ‘96, (pg. 387-396). Washington DC.
Stal, M. (2007). Software architecture refactoring. The international conference on object
oriented programming, systems, languages and applications (OOPSLA).
Stevens, W. P., Myers, G. J., & Constantine, L. L. (1974). Structured design. IBM Systems
Journal, 115-139.
Stroustrup, B. (2013). A Tour of C++. Addison-Wesley, Pearson Education.
Stroustrup, B. (2013). The C++ Programming Language, Fourth edition. Addison-Wesley,
Pearson Education.
Stroustrup, B. (2014). Programming: Principles and Practice Using C++, Second edition.
Addison-Wesley, Pearson Education.
Suryanarayana, G., SG, G., & Sharma, T. (2014). Refactoring for software design smells:
Managing technical debt. Morgan Kaufmann.
Taylor, R. N., Medvidović, N., & Dashofy, E. M. (2009). Software architecture: Foundations,
Theory and Practice. Wiley.

167
Tidwell, J. (1999). Common Ground: A Pattern Language for Human-Computer Interface
Design. Preluat de pe MIT.Edu:
https://www.mit.edu/~jtidwell/interaction_patterns.html
Tidwell, J. (2011). Designing Interfaces: Patterns for Effective Interaction Design, Second
Edition. Amazon.
Trifu, A. (2005). Automated strategy based restructuring of object oriented code. Proceedings
of the 7th German workshop on software-reengineering (WSR).
Tufano, M., Palomba, F., Bavota, G., Oliveto, R., Di Penta, M., De Lucia, A., & Poshyvanyk,
D. (2015). When and Why Your Code Starts to Smell Bad. IEEE/ACM 37th IEEE
International Conference on Software Engineering (pg. 403-414). IEEE.
TutorialsPoint.Com. (2019). C++ Object Oriented. Preluat de pe Tutorials Point:
https://www.tutorialspoint.com/cplusplus/cpp_object_oriented.htm
Umair, M. (2017). SOLID, GRASP, and Other Basic Principles of Object-Oriented Design.
Preluat de pe DZone: https://dzone.com/articles/solid-grasp-and-other-basic-
principles-of-object-o
Wikipedia.Org. (2019). Code smell. Preluat de pe Wikipedia:
https://en.wikipedia.org/wiki/Code_smell
Wikipedia.Org. (2019). Design smell. Preluat de pe Wikipedia:
https://en.wikipedia.org/wiki/Design_smell
Yahoo. (2008). Yahoo! Design Pattern Library. Preluat de pe Yahoo:
https://web.archive.org/web/20080229011119/http://developer.yahoo.com/ypatterns/
Yourdon, E., & Constantine, L. L. (1975). Structured Design: Fundamentals of a Discipline of
Computer Program and Systems Design. Yourdon Press.

168

S-ar putea să vă placă și