Saptamana 03 / 14 Programare Orientata Obiect
Seminar 3 - De la cerinte la clase si incapsulare
Transformam o lista scurta de cerinte intr-o clasa Produs. Intai o proiectam pe hartie, apoi ii ascundem starea si scriem metode care nu o lasa niciodata sa devina invalida.
- Identifici obiectele, starea si comportamentul pornind de la cerinte
- Definesti o clasa cu atribute private si metode publice
- Protejezi invariantii clasei in interiorul metodelor
- Marchezi cu const metodele care doar citesc obiectul
- Eviti sa scrii un get si un set pentru fiecare atribut
- Raspunzi la intrebarea "De ce este responsabila aceasta clasa?"
Plan (90 min)
| Timp | Activitate |
|---|---|
| 0-10 | Proiectare fara cod: stare, comportament, stari imposibile |
| 10-20 | Discutie: ce e private, ce e public, ce atribute nu primesc setter |
| 20-30 | Programul de pornire: cum se strica un struct public |
| 30-60 | Lucru individual: clasa Produs |
| 60-75 | Cerinta noua: capacitatea depozitului |
| 75-85 | Verificare individuala si intrebari scurte |
| 85-90 | De ce este responsabila aceasta clasa? |
Cerintele
Un magazin are produse. Fiecare produs are un id, un nume, un pret si un stoc (cate bucati au mai ramas). Magazinul poate schimba pretul, poate vinde bucati, poate reaproviziona si poate cere valoarea stocului (pret inmultit cu stoc).
Intai proiectarea, fara cod
Zece minute pe hartie
Inchide editorul. Pe hartie, raspunde:
- Care este starea obiectului?
- Ce comportament apartine obiectului?
- Ce stari ar trebui sa fie imposibile?
- Ce ar trebui sa fie private?
- Ce ar trebui sa fie public?
- Ce proprietati nu ar trebui sa aiba setter?
Indiciu
La intrebarea 3, gandeste-te la valori care nu au sens intr-un magazin real: un stoc de -5, un pret de 0 sau mai mic, un nume gol.Indiciu
La intrebarea 6, intreaba-te cum se schimba fiecare atribut in realitate. Stocul se schimba pentru ca cineva il “seteaza” sau pentru ca s-a vandut ori s-a livrat ceva?Solutie
- Starea:
id,nume,pret,stoc. - Comportamentul: schimbarea pretului, vanzarea, reaprovizionarea, calculul valorii.
- Imposibile: stoc negativ, pret
<= 0, nume gol, un id care se schimba dupa creare, vanzarea mai multor bucati decat sunt in stoc. - Private: toate cele patru atribute.
- Public:
schimbaPret,vinde,reaprovizioneaza,getValoaresi getter-e doar pentru citire, acolo unde cineva are nevoie de valoare. idnu se schimba niciodata.stocse schimba doar prinvindesireaprovizioneaza. Deci niciunul nu primeste setter.pretse schimba prinschimbaPret, care il verifica.
Programul de pornire
Asa arata datele fara clasa. Citeste programul si prezice ultima linie inainte sa il rulezi.
#include <iostream>
#include <string>
using namespace std;
struct Produs {
int id;
string nume;
double pret;
int stoc;
};
double valoare(Produs p) {
return p.pret * p.stoc;
}
int main() {
Produs p;
p.id = 1;
p.nume = "Pix";
p.pret = 2.5;
p.stoc = 10;
p.stoc = p.stoc - 15; // am vandut 15 pixuri pe care nu le aveam
p.pret = -1; // greseala de tastare la schimbarea pretului
p.id = 7; // cineva a "corectat" id-ul
cout << p.id << " " << p.nume << " " << p.stoc << " " << valoare(p) << endl;
}
Afiseaza 7 Pix -5 5. Valoarea stocului e pozitiva si pare corecta, pentru ca doua numere gresite inmultite dau unul plauzibil. Fiecare linie din main a compilat. Nimic din program nu cunoaste regulile unui magazin.
Intrebari obligatorii
- Care este starea obiectului?
- Ce comportament apartine obiectului?
- Ce stari ar trebui sa fie imposibile?
- Ce ar trebui sa fie private? Ce ar trebui sa fie public?
- Ce proprietati nu ar trebui sa aiba setter?
- De ce este responsabila aceasta clasa?
Prezice, apoi verifica:
#include <iostream>
using namespace std;
class Produs {
double pret = 0;
int stoc = 0;
public:
void initializeaza(double p, int s) { pret = p; stoc = s; }
bool vinde(int cantitate) {
if (cantitate <= 0 || cantitate > stoc) return false;
stoc -= cantitate;
return true;
}
int getStoc() const { return stoc; }
double getValoare() const { return pret * stoc; }
};
int main() {
Produs p;
p.initializeaza(2.5, 10);
p.vinde(3);
p.vinde(20);
cout << p.getStoc() << " " << p.getValoare();
} Sarcina minima
Clasa Produs
Completeaza clasa. Fiecare metoda care modifica obiectul isi verifica datele primite si intoarce false daca refuza. Metodele care doar citesc sunt const.
initializeazaaccepta doarid > 0, un nume nevid,pret > 0,stoc >= 0si functioneaza o singura data (id-ul nu are voie sa se schimbe ulterior).schimbaPretrefuza un pret<= 0.vinderefuza o cantitate<= 0sau mai mare decat stocul.reaprovizioneazarefuza o cantitate<= 0.getValoareintoarcepret * stoc.
Testeaza din main fiecare refuz, nu doar cazurile care merg.
#include <iostream>
#include <string>
using namespace std;
class Produs {
int id = 0;
string nume;
double pret = 0;
int stoc = 0;
public:
bool initializeaza(int idNou, const string& numeNou, double pretNou, int stocNou) {
// TODO: refuza datele invalide, accepta doar primul apel
id = idNou;
nume = numeNou;
pret = pretNou;
stoc = stocNou;
return true;
}
bool schimbaPret(double pretNou) {
// TODO
return false;
}
bool vinde(int cantitate) {
// TODO
return false;
}
bool reaprovizioneaza(int cantitate) {
// TODO
return false;
}
double getValoare() const {
// TODO
return 0;
}
int getStoc() const { return stoc; }
};
int main() {
Produs p;
p.initializeaza(1, "Pix", 2.5, 10);
cout << p.vinde(3) << " " << p.getStoc() << endl; // asteptat: 1 7
cout << p.vinde(20) << " " << p.getStoc() << endl; // asteptat: 0 7
cout << p.getValoare() << endl; // asteptat: 17.5
}Intrare (stdin)
Indiciu
Un produs care nu a fost initializat areid == 0. Foloseste asta ca sa stii daca initializeaza a fost deja apelata.Indiciu
Scrie intai verificarea si abia apoi modificarea:if (cantitate <= 0 || cantitate > stoc) return false; si doar dupa aceea stoc -= cantitate;.Solutie
bool initializeaza(int idNou, const string& numeNou, double pretNou, int stocNou) {
if (id != 0) return false; // deja initializat
if (idNou <= 0 || numeNou.empty() || pretNou <= 0 || stocNou < 0) return false;
id = idNou;
nume = numeNou;
pret = pretNou;
stoc = stocNou;
return true;
}
bool schimbaPret(double pretNou) {
if (pretNou <= 0) return false;
pret = pretNou;
return true;
}
bool vinde(int cantitate) {
if (cantitate <= 0 || cantitate > stoc) return false;
stoc -= cantitate;
return true;
}
bool reaprovizioneaza(int cantitate) {
if (cantitate <= 0) return false;
stoc += cantitate;
return true;
}
double getValoare() const { return pret * stoc; }Invariantul “stocul nu este niciodata negativ” se verifica in exact doua locuri, vinde si reaprovizioneaza, pentru ca doar ele pot modifica stoc.
Cerinta noua
Depozitul tine cel mult 100 de bucati dintr-un produs. Un apel reaprovizioneaza sau initializeaza care ar depasi 100 este refuzat.
- In cate locuri din cod a trebuit sa modifici? De ce atat de putine?
- Ce ar fi trebuit sa cauti in programul de pornire, cu struct-ul public?
- Scrie, in afara clasei,
void afiseaza(const Produs& p). Ce metode poate apela? Ce spune compilatorul dacagetValoarenu esteconst?