Interfete si Polimorfism
 

Interfete. Programarea Bazata pe Interfete.

Interfete. Tipuri. Ierarhia de Interfete. [Gamma95]
Fiecare declaratie a unei functii membru pentru o clasa de obiecte specifica numele functiei, obiectele pe care le primeste ca parametri, si tipului valorii returnate de functie. Acestea formeaza impreuna signatura functiei. Multimea tuturor signaturilor functiilor ce pot fi apelate de catre clientii acelei clase de obiecte formeaza interfata clasei. Interfata unui obiect descrie setul complet de cereri (mesaje) care pot fi trimise catre un obiect al acelei clase.

Un tip este un nume pentru o anumita interfata. Spunem ca un obiect are tipul "List" daca poate executa toate operatiile prevazute in interfata numita "List". Un obiect poate avea mai multe tipuri; iar mai multe obiecte total diferite pot apartine aceluiasi tip! O parte a interfetei unui obiect poate fi caracterizat printr-un anumit tip, in timp ce o alta parte a interfetei sale poate fi caracterizata printr-un alt tip. Doua obiecte care apartin  acluiasi tip trebuie sa aiba in comun doar o  parte a interfetei lor, si anume acea parte care caracterizeaza tipul caruia ii apartin.

Spunem ca un tip este subtipul altui tip daca interfata sa contine integral interfata supertipului sau. Astfel, putem spune ca un subtip mosteneste intefata supertipului sau, putandu-se defini prin urmare o ierarhizarea intre tipuri.

Mostenirea Claselor vs. Mostenirea Interfetelor
Este important sa facem distinctia intre clasa unui obiect si tipul sau.  Clasa unui obiect defineste modul in care un obiect este implementat. Clasa defineste starea interna si implementarea operatiilor sale. In contrast cu aceasta, tipul unui obiect se refera doar la interfata sa, adica setul de solicitari la care poate raspunde.

Desigur, exista o relatie stransa intre clasa si tip. Intrucat clasa defineste operatiile pe care un obiect le poate efectua, ea defineste automat si tipul obiectului. Atunci cand spunem ca un obiect este instanta unei clase, spunem implicit si ca obiectul are interfata definita de clasa a carui instanta este.

In acest context, este deasemenea important sa intelegem diferenta intre mostenirea claselor si mostenirea interfetelor. Prin relatia de mostenire intre clase implementarea unui obiect este definita in termenii implementarii altui obiect. Asadar mostenirea intre clase este un mecanism care asigura reutilizarea codului si a reprezentarii. In contrast cu mostenirea claselor, mostenirea interfetelor descrie cand poate fi folosit un obiect in locul altui obiect. Aceste doua concepte pot fi usor confundate deoarece multe limbaje, printre care si C++, nu fac o distinctie explicita intre ele.  In C++, modalitatea standard de a mosteni o interfata este aceea de a mosteni public de la o clasa de baza virtuala , in timp ce mostenirea claselor se poate exprima cel mai bine prin mostenirea privata.

Legarea Dinamica a Operatiilor. Polimorfism.
Interfetele sunt fundamentale in sistemele orientate pe obiecte. Obiectele sunt cunoscute in sistem doar prin interfetele lor. Nu exista nici o alta cale de a afla ceva despre un obiect sau de a-i cere sa faca ceva decat prin intermediul intefetei sale. Interfata unui obiect nu spune nimic despre implementarea sa - diferitele obiecte pot astfel sa implementeze o aceeasi interfata in mod diferit. Ceea ce inseamna ca doua obiecte cu interfete identice, pot avea implementari total diferite!

Atunci cand se solicita o anumita operatie, modul in care aceasta va fi indeplinita depinde nu doar de operatia solicitata, ci si de obiectul care va primi solicitarea si o va executa apeland una din functiile sale membru. Acest lucru se datoreaza faptului ca pot exista mai multe obiecte care sa poata raspunde la acea solicitare. Cu alte cuvinte, prin operatia solicitata se specifica serviciul  dorit, iar prin obiectul concret  se alege o anumita implementare a respectivului serviciu. Asocierea dintre o operatie solicitata si obiectul care va pune la dispozitie implementare concreta a  operatiei printr-una din functiile sale membru se numeste legare. Functie de momentul in care se face aceasta legare, exista doua tipuri de legare:

Astfel, la legarea dinamica, prin solicitarea unei operatii nu se produce la momentul compilarii "lipirea" operatiei de o anumita implementarea particulara a sa, acest lucru realizandu-se doar la momentul executiei. Principalul avantaj al legarii dinamice il reprezinta posibilitatea de a substitui la executie obiecte avand interfete identice. Posibilitatea de a folosi un obiect in locul altui obiect avand aceeasi interfata se numeste polimorfism. Polimorfismul este unul din conceptele fundamentale ale programarii orientate pe obiecte.
 

Programarea Bazata pe Interfete
Mostenirea claselor este in esenta doar un mecanism de extindere a functionalitatii unei aplicatii prin reutilizarea functionalitatii din clasele de baza. Aceasta permite definirea facila si rapida a unei noi clase  de obiecte in functie de o clasa existenta, obtinand o implementare extrem de ieftina, prin faptul ca se majoritatea lucrurilor necesare se mostenesc de la clase existente.

Pe langa reutilizarea implementarii mostenirea este si mecanismul prin care se pot defini familii de obiecte cu interfete identice (de obicei prin mostenirea de la o clasa abstracta). Atunci cand mostenirea este utilizata corespunzator toate clasele derivate dintr-o clasa abstracta vor avea o interfata comuna. Astfel, in mod normal, o subclasa va redefini sau va adauga operatii la interfata mostenita si nu va ascunde operatii definite in clasa parinte. In felul acesta, obiectele tuturor subclaselor vor putea raspunde atunci cand este solicitata o operatie pentru un obiect al clasei de baza (abstracta). Prin urmare, relatia de mostenire este unul din mecanismele fundamentale care contribuie la realizare polimorfismul.

Exista doua avantaje major ale manipularii obiectelor in termenii interfetei definite de o clasa abstracta:

Principu: Programati orientat spre interfata, nu spre implementare!

Corolar: Nu declarati variabilele ca instante ale unor clase concrete particulare, ci operati mereu prin intermediul interfetei definite de clasa abstracta!
 

Functii Virtuale

Polimorfismul Partial.
In lucrarea trecuta am vazut ca un pointer la clasa de baza poate fi utilizat pentru a adresa un obiect al oricarei clase derivate din ea. Prin intermediul unui astfel de pointer se pote apela toate functiile-membru ale clasei de baza comune pentru clasele derivate. Aceasta este o forma partiala de polimorfism intrucat acelasi pointer poate fi folosit pentru a manipula obiecte de diferite forme. Astfel se poate declara un pointer care sa indice in mod valid spre orice obiect al unei clase aflata intr-o relatie de mostenire (derivare) fata de clasa utilizata pentru declararea pointerului. Utilizand acel pointer, toate aceste obiecte pot fi manipulate uniform fara a se tine cont de clasa concreta de care apartine obiectul: fiecare obiect va fi tratat in termenii tipului sau general (tipul definit de clasa de baza a ierarhiei).

Acest tip de polimorfism - bazata exclusiv pe relatia de mostenire si pe conversia implicita a pointerilor spre clasa de baza - prezinta urmatoarele dezavantaje:

class Employee { 
public: 
  double salary() { return sal;} 
  double computeRaise() { return 25;} 
  ~Employee() {...}
protected:
  Employee (double salary} { sal = salary;} //nu pot crea direct instante!! 
private:
  . . . 
  double sal; 
}; 

class Manager : public Employee { 
public: 
  double computeRaise() { return 100; } 
  Manager(double salary) : Employee(salary) {} 
  ~Manager() {...} 
}; 
 

class SecurityGuard : public Employee { 
public: 
  double computeRaise() { return 75; } 
  SecurityGuard(double salary) : Employee(salary) {} 
  ~SecurityGuard() {...} 
}; 
 

main() {
  Manager *john = new Manager(2000); 
  double johnSalary = john->salary(); // 2000 
  Employee *george = john; // conversie la clasa de baza 
  double georgeSalary = george->salary(); // 2000 

  double johnRaise = john->computeRaise();  // 100 
  double georgeRaise = george->computeRaise(); // GRESIT: 25 
  Manager *boss1 = new Manager(2000);
}

Polimorfismul Complet.
Pentru a putea vorbi de un polimorfism complet, care sa permita manipularea omogena a tuturor claselor aflate intr-o relatie de mostenire,  trebuie sa fie indeplinite urmatoarele doua conditii:

Cu alte cuvinte, cea de-a doua conditie vrea sa spuna ca se doreste ca selectia functiei sa fie determinata de clasa obiectului si nu de declaratia pointerului utilizat pentru manipularea obiectului. In termenii discutiei de mai sus, aceasta facilitatea inseamna de fapt am dori  ca in locul unei legari statice a functiilor apelate, acestea sa fie legate dinamic

Definirea Functiilor Virtuale in C++
Pentru a realiza polimorfismul complet in C++ trebuie doar sa declaram ca virtuale acele functii-membru din clasa de baza pentru care dorim sa se apeleze implementarea din clasele derivate, atunci cand invocarea se produce printr-un pointer la clasa de baza, dar care indica spre un obiect al unei clasei derivate. Declararea unei functii virtuale se face, prefixand declaratia (nu definitia!!) functiei prin cuvantul virtual.

Sa vedem cum se va modifica exemplul prezentat anterior:
 

class Employee { 
public: 
  virtual double salary() { return sal;} 
  virtual double computeRaise() { return 25;} 
  virtual ~Employee() {...}
protected:
  Employee (double salary} { sal = salary;} //nu pot crea direct instante!! 
private:
  . . . 
  double sal; 
}; 

class Manager : public Employee { 
public: 
  double computeRaise() { return 100; } 
  Manager(double salary) : Employee(salary) {} 
    ~Manager() {...} 
}; 
 
class SecurityyGuard : public Employee { 
public: 
  double computeRaise() { return 75; } 
  SecurityGuard(double salary) : Employee(salary) {} 
  ~SecurityGuard() {...} 
}; 
 

Observam ca singura modificare pe care am operat-o este aceea de a declara ca virtuale functiile-membru ale clasei Employee. Apelul functiei computeRaise()  se va executa acum corect:
 

main() {
  Employee *george = john;
  . . .

  double georgeRaise = george->computeRaise();  // corect:returneaza 100, 
  // deoarece functia este legata dinamic!  

    george = new SecurityGuard;
  georgeRaise = george->computeRaise();  // corect: 75
}

Clasa de baza in mod uzual va defini si o implementare pentru functiile virtuale care poate fi mostenita de catre o clasa derivata daca acesta din urma nu doreste sa o redefineasca. In exemplul de mai sus acesta este cazul functiei salary(). Desi nu este redefinita in nici una din clasele de baza, aceasta functie a fost declarata ca virtuala intrucat ea ar putea fi redefinita la un moment ulterior al dezovltarii aplicatiei, fie intr-o noua clasa derivata, fie chiar in una din cele doua clase derivate deja existente.

Ceea ce distinge functiile virtuale de functiile obisnuite este prioritatea pe care o are intotdeauna implementarea functiei din clasa derivata, chiar si atunci cand obiectul clasei derivate apare intr-un context in care este asteptat un obiect al clasei de baza (evident indicat prin pointer sau referinta).

Figura de mai jos ilustreaza modul de identificarea functiei invocate are loc la executie in functie de tipul obiectului indicat si nu functie de tipul pointerului care indica obiectul.

In Spatele Scenei . . .
Atunci cand declaram virtual anumite functii membru ale unei clase de baza, compilatorul va genera un cod suplimentar pentru aceste functii pe baza caruia va putea fi selectata la executie, dintre implementarile definite in clasele derivate, versiunea corecta pentru acea functie, pe baza tipului obiectului. In acest scop, oridecateori se creaza un obiect dintr-o clasa cu functii virtuale, compilatorul va retine intr-un camp special clasa din care a fost instantiat obiectul respectiv. Acest camp este utilizat doar de catre compilator si limbajul nu are nici un mecanism prin care sa faca acest camp accesibil programatorului. Pe baza acestui camp, se va putea selecta clasa concreta a carei implementarea va fi utilizata pentru executia operatiei. respective.
 
 

Destructori Virtuali

Dintre functiile declarate virtual in clasa Employee una anume este surprinzatoare la prima vedere: destructorul clasei! Acest lucru pare neobisnuit intrucat in mod normal nu ne gandim la a apela un destructor. La o privire mai atenta insa constatam ca declarea destructorului ca virtual este importanta. Sa consideram fragmentul de cod de mai jos:
 

class Employee { 
 . . . 
 ~Employee(); 
 . . . 
}; 

. . . 

Employee *boss = new Manager; 
. . . 
delete boss;

Ce se intampla in ultima linie din exemplul de mai sus? La apelul operatorului delete acesta nu are de unde sa stie ca obiectul spre care indica este de fapt o instanta a clasei Manager si in plus nu exista nimic in clasa Employee care sa determine selectarea la executie a destructorului ce trebuie apelat. Ceea ce inseamna ca daca constructorul clasei Manager a alocat reurse care se intentiona a fi eliberate prin intermediul destructorului acestei clase, acestea nu vor fi eliberate, ramand alocate inutil ("garbage").

Daca destructorul clasei de baza este declarat virtual, atunci destructorul apelat va fi selectat la executie in functie de obiectul efectiv indicat de pointer, exact ca si in cazul celorlalte functii virtuale. Astfel, in exemplul de mai sus va fi invocat destructorul Manager::~Manager() ceea ce va produce eliberarea resurselor alocate prin constructorul acestei clase derivate. Desigur, in cele din urma va fi apelat si destructorul clasei Employee in conformitate cu regulile de apel ale destructorilor  pentru clase derivate.
 
 

Functii Virtuale Pure. Clase Abstracte.

Sa revenim din nou la varianta initiala a exemplului Employee; am declarat acolo constructorul in sectiune protected ceea ce face imposibila crearea de instante a clasei Employee . Instante pot fi create numai din clasele derivate. De fapt nu putem face nimic cu  clasa abstracta ... ! De ce? Pentru ca nu putem implementa functia computeRaise() pentru anagajati in general; putem implementa aceasta functie doar pentru categorii concrete de angajati (vezi clasele Manager si SecurityGuard)

In limbajul C++ functiile dintr-o clasa de baza pentru care nu se poate defini - sau nu se doreste - nici o implementare se numesc functii virtuale pure. In primul rand remarcam ca aceste functii sunt virtuale, ceea ce inseamna ca vom putea invoca diferitele lor implementari existente in clasele derivate fara a trebui sa stim exact tipul obiectului sau implementarea functiei. In al doilea rand, aceste functii sunt "pure" in sensul in care nu se defineste pentru ele in acea clasa un corp, ci doar o signatura pura. Intrucat nu putem implementa pentru astlfe de functii un corp general, vom "lega" corpul functiei la pointerul vid :
 

class Employee {
public:
  virtual double computeRaise() = 0           // functie virtuala pura 
  double salary() { return sal;} 
  Employee (double salary} { sal = salary;}  
  virtual ~Employee() {...}
  . . .
};

Acum va fi imposibil sa mai cream o instanta a clasei Telephone pentru simplul motiv ca, neexistand o implementare a metodei ring(), compilatorul nu va stii ce sa faca in cazul in care o astfel de metoda ar fi invocata printr-un obiect  al acelei clase. O clasa care nu mai poate fi instantiata intrucat contine functii virtuale pure se numeste clasa de baza abstracta sau clasa partiala.O astfel de clasa este abstracta sau partiala in sensul in care ea defineste o anumita comportare fara a defini insa si implementarea acesteia. Ea impune claselor derivate sa implementeze aceste functii, specificand astfel care sunt metodele ce trebuie in mod obligatoriu redefinite.

O functie virtuala pura, desi nu trebuie sa aiba un corp, poate avea un corp. Acesta va putea fi invocat prin intermediul operatorului de specificare a domeniului (::).
 

Mostenirea Multipla

Uneori un singur lant de mostenire nu este suficient; In unele cazuri o clasa are nevoie sa mosteneasca proprietati de la doua sau mai multe clase parinte. Daca putem stabili la compilare care sunt aceste clase de baza putem utiliza mostenirea multipla pentru a combina comportamentele mai multor clase de baza intr-o singura clasa derivata.

Exemplu
Un exemplu uzual este utilizarea mostenirii multiple pentru crearea unui sistem de ferestre terminal. Dorim ca aceste sistem de ferestre sa fie portabil pe mai multe tipuri de terminale. De aceea din clasa Window am derivat clasele CursesWindow si XWindow ce corespund la doua tehnologii de implementare a ferestrelor (Curses si XWindow). Majoritatea membrilor clasei Window sunt virtuali ceea ce inseamna ca implementarea lor concreta se realizeaza in clasele derivate. Avantajul acestei abordari este acela ca la nivelul aplicatiei putem lucra ca si cum obiecte manipulate ar fi de tip Window, in timp ce ele vor corespunde in realitate clasei corespunzatoare tehonologiei utilizate.

Cea de-a treia clasa derivata (EditWindow) este o specializarea a unei ferestre pentru utilizarea in cadrul unui editor de texte. Dar ceea ce am dori de fapt ar fi o clasa care pe de o parte sa aiba caracteristicile unei ferestre de editare - deci sa fie derivate din EditWindow - dar in acelasi timp sa fie specifica unei anumite tehnologii, de exemplu Curses; prin urmare clasa ar trebui sa fie derivata si din clasa CursesWindow.  Acest lucru il putem realiza prin intermediul mostenirii multiple. Urmatoare secventa de cod creaza astfel de clasa, si apoi creaza o instanta a acelei clase:
 

class EditWnd_Curses : public EditWindow, public CursesWindow {  
};

EditWnd_Curses aNewWindow;

Prezentam in figura de mai jos si o schita (simplificata) a ierarhiei de clase pentru exemplul acesta:


Noua clasa EditWnd_Curses mosteneste toate proprietatile claselor din care este derivata (direct sau indirect). Aceasta insa ridica urmatoarele doua probleme:

Ambiguitati
Cu privire la prima problema, limbajul C++ spre deosebire de majoritatea limbajelor orientate pe obecte, nu ofera un mecanism prin care sa trateze implicit aceasta ambiguitate, lasand aceasta sarcina exclusiv in grija programatorului. C++ este astfel construit incat declararea unor functii ce cauzeaza ambiguitati este legala; numai utilizarea unor astfel de functii va genera eroari de compilare.

In exemplul de mai sus, clasa derivata EditWnd_Curses rezolva ambiguitatea redefinind functia scroll(). In implementarea functiei sunt apelate versiunile functiei scroll() definite in clasele de baza, intr-o ordine impusa de logica problemei. Aceeasi situatie este si in cazul functiei
clear(). Completam prin urmare definitia clasei EditWnd_Curses dupa cum urmeaza.
 

class EditWnd_Curses : public EditWindow, public CursesWindow {  
public:
  void clear() {CursesWindow::clear(); }
  void scroll() {
   EditWindow::scroll();
   CursesWindow::scroll();
   }

};

EditWnd_Curses aNewWindow;

Mai pot aparea ambiguitati datorate datelor membru mostenite. Aceste ambiguitati vor fi eliminate prin referirea respectivelor variabile/obiecte nu direct, ci cu ajutorul operatorului de specificarea a domeniului (::) exact cum am procedat mai sus cu functiile membru.

Clase de Baza Virtuale
Cea de-a doua problema mentionata mai sus este legata de existenta unei clase comune pe diferitele canale prin care mosteneste o clasa derivata prin mostenire multipla. Aceasta tip de mostenire se numeste mostenire in diamant. Se recomanda pe cat posibil evitarea definirii unor astfel de relatii de mostenire.

Problema critica in acest caz este mostenirea multipla a datelor din respectiva clasa comuna. Aceasta problema poate fi rezolvata prin declararea ca virtuale a claselor de baza:
 

class Window {
 ...
};

class EditWindow : public virtual Window {
 ...
};

class CursesWindow : public virtual Window {
 ...
};

class EditWnd_Curses : public EditWindow, public CursesWindow {
 ...
};

Prin aceasta declaratie, chiar daca clasa Window este mostenita de o clasa derivata prin mai multe canale de mostenire, datele membru vor aparea o singura data intr-o instanta a clasei EditWnd_Curses. Daca clasa Window nu ar fi mostenita virtual, datele sale s-ar regasi in clasa EditWnd_Curses de un numar de ori egal cu numarul de canale prin care este mostenita clasa.

Evitarea Apelurilor Redundante ale Metodelor din CBV
In  cazul unei mosteniri in diamant mai apare o problema, redat de secventa de mai jos:
 

class Window {
public:
 virtual void scroll() { ... }

};

class EditWindow : public virtual Window {
public:
 void scroll() {
   ....  // cod specific 
   ....  // clasei EditWindow
   Window::scroll();
 }
};

class CursesWindow : public virtual Window {
public:
 void scroll() {
   Window::scroll(); 
   ....  // cod specific 
   ....  // clasei CursesWindow 
 }
};

class EditWnd_Curses : public EditWindow, public CursesWindow {
public:
 void scroll() {
    EditWindow::scroll();
    CursesWindow::scroll();
 }
};

Clasa Window ofera o implementare utila a functiei scroll(), care este utilizata in redefinirea acestei functii din clasele EditWindow si CursesWindow, iar implementarea functiei din clasa EditWnd_Curses, apeleaza ambele functii. Aceasta face ca Window::scroll()sa se execute de doua ori. Acesta este un apel redundant al unei functii dintr-o CBV.

Atentie: In unele cazuri un astfel de apel nu este doar o problema de redundanta (ceva ce se eceuta inutil), ci poate avea efecte negative, constituind o perfida greseala  ce va afecta logica programului! Un motiv in plus sa ne ferim de mostenirea in diamant.

Solutia acestei probleme este redata mai jos:
 

class Window {
public:
 virtual void scroll() { ... }

};

class EditWindow : public virtual Window {
public:
 void scroll() {
  _scroll();
   Window::scroll();
 }
protected: 
 _scroll() {
   ....  // cod specific 
   ....  // clasei EditWindow
 }
};

class CursesWindow : public virtual Window {
public:
 void scroll() {
   Window::scroll();
  _scroll();
 }
protected: 
 _scroll() {
   ....  // cod specific 
   ....  // clasei CursesWindow
 }
};

class EditWnd_Curses : public EditWindow, public CursesWindow {
public:
 void scroll() {
    Window::scroll(); 
   EditWindow::_scroll(); 
    CursesWindow::_scroll();
 }
};


 

Probleme
1. Sa se implementeze ierarhia de clase descrisa mai jos:
 

  • Clasa Object - reprezinta un obiect abstract.

  •         Date membru: nu are
            Functii membru:
                - virtual char *nameOf(); returneaza numele clasei sub forma de sir de caractere;
                - virtual int isEqual(Object&); returneaza 1 la egalitatea cu argumentul si 0 in caz contrar; functia este private.
                - virtual void printOn(); afiseaza valoarea obiectului; afiseaza chiar numele clasei.
                - friend int operator==(Object&, Object&); operatorul de comparare la  egalitate;
                - friend int operator!=(Object&, Object&); operatorul de comparare la  diferit.
     
  • Clasa Integer - implementeaza tipul de date abstract intreg.

  •         Date membru: o variabila de tip int.
            Functii membru redefinite:  nameOf(), isEqual() si 'printOn().
            Operatori: adunare, scadere, inmultire, impartire
     
  • Clasa String - Implementeaza tipul de date abstract sir de caractere.

  •         Date membru: o variabila de tip char*.
            Functii membru redefinite:  nameOf(), isEqual() si 'printOn()'.
            Operatori: cei precizati in tema de la lucrarea 4.
     
  • Clasa Collection - se va implementa ca o lista simplu inlantuita, cu noduri apartinand unei  clase Node ai carei membri sunt:

  •          Date membru:
                -un pointer la clasa Object (campul de informatie);
                -pointeri tipici pentru o lista inlantuita.
            Functii membru redefinite:
                    - nameOf()
                    - isEqual();  Se considera ca 2 obiecte de tip Collection sunt egale daca au acelasi numar de noduri, iar continutul campurilor de
    informatie din nodurile corespondente sunt respectiv egale.
                    -'printOn()'.  Functia 'printOn' trebuie sa realizeze afisarea listei sub forma: ( element_1 ; element_2 ; . . . ; element_n ). Functia printOn va fi bazata pe functia forEach() descrisa mai jos.
             Alte functii:
                   -virtual void add(Object&); adauga un element in lista;
                   -virtual void detach(Object&); elimina un element din lista;
                   -void forEach(iterFuncType iterf, void *args); apeleaza functia 'iterf' pentru fiecare element al colectiei; eventualii parametri ai functiei 'iterf' vor fi transmisi prin intermediul lui 'args'; Tipul 'iterFuncType' se defineste astfel:  typedef void (*iterFuncType) (void*);
     
  • Clasa Stack - implementeaza tipul de date stiva. Ofera spre exterior urmatoarele functii:

  •    Functii membru redefinite:  nameOf(), isEqual().  Implementarea functieie printOn() se va mosteni de la clasa Collection().
       Alte functii membru:
             -void push(Object&); introduce un element in stiva;
             -void pop(); extrage un element din stiva;
             -Object& top(); returneaza elementul din varful stivei.
     
  • Clasa Set - implementeaza tipul de date multime.

  •        Functii membru redefinite:  nameOf(), isEqual() si 'printOn(). Un obiect de tip Set se va afisa sub forma: { element_1 ,  . . . , element_n }
           Operatori:
             -operator+ pt. reuniunea a 2 multimi;
             -operator* pt. intersectia a 2 multimi;
             -operator- pt. diferenta dintre 2 multimi;
             -operator<= pt. test de incluziune a unei multimi in alta multime.
         Alte functii membru:
             -int isin(Object&); test de apartenenta a unui element la o multime;
             -int cardinal(); returneaza numarul de elemente al multimii;

    Initial instantele clasei Set vor fi multimi vide sau multimi cu cate un singur element (dat ca parametru constructorului). Completarea unei multimi cu  elemente se poate face in 2 variante:
         -adaugand elementele pe rand, cu ajutorul functiei 'add'; aceasta functie va fi redefinita pentru clasa Set pe baza functiei 'add' de la clasa Collection (pentru Set trebuie sa se prevada un test care sa evite inserarea a 2 elemente identice);
         -utilizand operatia de reuniune in care unul din operanzi este multimea initiala, iar celalalt operand - multimea formata din elementul de adaugat. Se vor redefini functiile virtuale 'nameOf', 'printOn' si 'isEqual'.
     

    Bibliografie

    [Cop91]        J. Coplien  - "Advanced C++ Programming Styles and Idioms",  Addison-Wesley, 1991
    [Gam95]       E.Gamma, R.Helm, R.Johnson, J.Vlissides - Design Patterns,  Addison-Wesley, 1995
                         L. Negrescu - "Limbajele C/C++ pentru Incepatori vol. II", Editura Microinformatica Cluj, 1994
     

    Hosted by www.Geocities.ws

    1