POO Cauta

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.

Obiective
  • 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)

TimpActivitate
0-10Proiectare fara cod: stare, comportament, stari imposibile
10-20Discutie: ce e private, ce e public, ce atribute nu primesc setter
20-30Programul de pornire: cum se strica un struct public
30-60Lucru individual: clasa Produs
60-75Cerinta noua: capacitatea depozitului
75-85Verificare individuala si intrebari scurte
85-90De 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:

  1. Care este starea obiectului?
  2. Ce comportament apartine obiectului?
  3. Ce stari ar trebui sa fie imposibile?
  4. Ce ar trebui sa fie private?
  5. Ce ar trebui sa fie public?
  6. Ce proprietati nu ar trebui sa aiba setter?
IndiciuLa 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.
IndiciuLa 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
  1. Starea: id, nume, pret, stoc.
  2. Comportamentul: schimbarea pretului, vanzarea, reaprovizionarea, calculul valorii.
  3. Imposibile: stoc negativ, pret <= 0, nume gol, un id care se schimba dupa creare, vanzarea mai multor bucati decat sunt in stoc.
  4. Private: toate cele patru atribute.
  5. Public: schimbaPret, vinde, reaprovizioneaza, getValoare si getter-e doar pentru citire, acolo unde cineva are nevoie de valoare.
  6. id nu se schimba niciodata. stoc se schimba doar prin vinde si reaprovizioneaza. Deci niciunul nu primeste setter. pret se schimba prin schimbaPret, 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

  1. Care este starea obiectului?
  2. Ce comportament apartine obiectului?
  3. Ce stari ar trebui sa fie imposibile?
  4. Ce ar trebui sa fie private? Ce ar trebui sa fie public?
  5. Ce proprietati nu ar trebui sa aiba setter?
  6. 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.

  • initializeaza accepta doar id > 0, un nume nevid, pret > 0, stoc >= 0 si functioneaza o singura data (id-ul nu are voie sa se schimbe ulterior).
  • schimbaPret refuza un pret <= 0.
  • vinde refuza o cantitate <= 0 sau mai mare decat stocul.
  • reaprovizioneaza refuza o cantitate <= 0.
  • getValoare intoarce pret * 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)

Deschide in Godbolt

IndiciuUn produs care nu a fost initializat are id == 0. Foloseste asta ca sa stii daca initializeaza a fost deja apelata.
IndiciuScrie 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.

Codul din repository: S03

Varianta in engleza