Exemple de proiectare OOP
Se prezinta in acest capitol citeva exemple de proiectare a ierarhiilor de obiecte, folosind conceptele si metodele OOP. Se proiecteaza si se implementeaza mai intii o clasa tip structura de date inlantuita de tip "container" care se foloseste apoi pentru implementarea unor structuri mai particulare (stiva, coada). Se accentueaza maniera de proiectare a ierarhiei in "stil" OOP.
5.1. Proiectarea si utilizarea unor clase container
Practicile "corecte" in OOP 959f55j nu sint intotdeauna intuitive. Proiectarea claselor si metodelor pentru acestea este relativ clara si precisa, dar asamblarea mai multor astfel de clase intr-un program poate deveni greoaie si "obscura", producind un program de calitate proasta.
Unul dintre motivele ce conduc la aceasta este capacitatea limbajelor OOP actuale (Pascal, C++) de a suporta atit tehnica programarii structurate cit si tehnicile de programare specifice OOP. Aceasta "incurajeaza" multi programatori sa trateze limbajele OOP ca "extensii" ale limbajelor structurate, conducind in cele din urma la eliminarea din proiectare tocmai a conceptelor OOP.
Unul dintre aceste concepte este acela de "clasa container".
Proiectarea unei clase container "robusta" din punct de vedere al conceptelor OOP poate conduce la simplificarea enorma a codului scris ulterior.
Containerele sint "blocurile" constitutive fundamentale ale programelor OOP.
In proiectarea unei clase container trebuie avute in vedere 3 aspecte fundamentale:
a) tipurile predefinite nu sint clase ! (int, float, char,...)
b) in C++ clasele nu se deriveaza numai dintr-o singura radacina ("mama tuturor claselor"), ca de exemplu in alte limbaje (Smalltalk).
c) clasele container (si metodele lor) trebuie sa fie polimorfice; pentru aceasta trebuie utilizate functii virtuale. Functiile virtuale trebuie sa aiba ACELASI tip de parametri in intreaga ierarhie. Trebuie referite atunci obiectele din clasele container in mod generic, adica la fel pentru orice tip de obiect se foloseste containerul respectiv. Cea mai buna solutie este utilizarea pointer-ilor "void". Astfel clasele container NU vor contine efectiv obiecte (de un anumit tip) ci vor contine si vor opera asupra pointerilor "void".
Se va proiecta in continuare ca exemplu o ierarhie pentru liste simplu inlantuite.
class Container //aflare nr. elem.
void AssignHandler(void(*UserHandler)()) //setare f tratare
//exceptii si erori
void operator=(const Container& C); //operatorul de asignare
// functii generale, toate virtuale pure:
// introducerea unui element
virtual int Store(void* item)=0;
// inspectarea (vizualizarea) unui element
virtual void* Examine()=0;
// extragerea (regasirea) unui element
virtual void* Retrive()=0
// eliminarea tuturor elementelor
virtual void Empty()=0;
Implementarea metodelor pentru clasa "Container":
//functie implicita de tratare aexceptiilor si erorilor
static void DefaultHandler()
Container::Container()
Container::Container(const Container& c)
void Container::operator= (const Container& c)
Se observa ca nu se definesc decit 2 membri pentru clasa "Container":
- un contor al numarului de elemente introduse in lista
- un pointer la o functie de tratare a exceptiilor ce se va apela in momente "critice"; poate fi rescrisa specific pentru fiecare clasa derivata din "Container", in functie de necesitati.
Chiar daca nu "fac mare lucru" constructorul fara argumente si operatorul de asignare sint introdusi in aceasta clasa pentru a oferi o interfata (acum devenita obligatorie!) pentru toate celelalte clase. Este evident ca indiferent de clasa derivata, aceasta in constructorul fara argumente trebuie sa initializeze "count" la zero! In loc de a avea grija sa facem aceasta initializare in FIECARE constructor, o facem o SINGURA data, constructorul clasei de baza fiind apelat AUTOMAT din orice clasa derivata si astfel asignarea este FACUTA!
In plus orice modificare in acest constructor se va reflecta automat in TOTI constructorii claselor derivate.
Nu se defineste nici un destructor deoarece nu este nevoie de nici o "curatire" pentru aceste obiecte.
Pentru ultimele 4 metode nu se dau implementari-corpuri ale functiilor - (sint functii virtuale pure) pentru ca fiind virtuale trebuie sa se "adapteze" in clasele derivate cu tipul clasei respective. De exemplu "Store" va fi functie diferita pentru o stiva si pentru o coada !
Containerul doar defineste capabilitatile generale ale unui grup de clase, nu le implementeza !
5.2. Implementarea unei clase container pentru liste
Folosind aceasta clasa se poate deriva acum un alt CONTAINER ce implementeza capabilitatile unei liste simplu inlantuite. Este tot un container, deoarece functiile listei nu depind de TIPUL elementelor din lista ! Functiile care depind de tipul elementelor vor fi lasate "pure" (!), implementindu-se doar acelea care sint definitorii pentru o lista simplu inlantuita:
#include "Container.h" // depinde de definirea "Container"
class SinglyLinkedList : public Container ;
ListNode* Head; // capul listei
ListNode* Tail; // coada listei
void Copy(const SinglyLinkedList& sl); //metoda de duplicare
public:
SinglyLinkedList(); //constructor "void"
SinglyLinkedList(const SinglyLinkedList& sl); // copy constr.
void operator=(const SinglyLinkedList& sl); //op de asignare
~SinglyLinkedList();
// metodele virtuale pure:
virtual int Store(void* item)=0;
virtual void* Examine()=0;
virtual void* Retrieve()=0;
// stergerea tuturor elementelor dintr-o lista (nu mai este pura)
virtual void Empty();
Contributia esentiala adusa de "SinglyLinkedList" la stuctura clasei "Container" este "ListNode" - structura nodurilor dintr-o lista. Lista are suplimentar si 2 membri (pointeri) la astfel de noduri.
Se foloseste functia privata "Copy" pentru copierea a 2 liste pentru a simplifica scrierea constructorului de copiere si a operatorului de asignare. Dar NU trebuie accesat direct din exterior !!
Implementarea metodelor listei "SinglyLinkedList":
void SinglyLinkedList::Copy(const SinglyLinkedList& sl)
else
temp=temp->Next;
SinglyLinkedList::SinglyLInkedList()
: Container() //constr. container(baza)
SinglyLinkedList::SinglyLinkedlist( const SinglyLinkedList& sl)
: Container(sl)
void SinglyLinkedList::operator=(const SinglyLinkedList& sl)
SinglyLinkedList::~SingllyLinkedList()
// operatia de stergere a tuturor nodurilor dintr-un obiect
// instanta al clasei SinglyLinkedList
void SinglyLinkedList::Empty()
Chiar daca "SinglyLinkedList" nu implementeaza metodele de introducere/extragere a elementelor din lista, aceasta clasa implementeaza operatiile fundamentale (creare, distrugere, etc).
Poate fi atit o clasa abstracta de baza din care se deriva liste cu elemente de tipuri definite (si nu generice cum sint aici), cit si in continuare un container pentru alte structuri (clase) ce se pot deriva din aceasta (cum ar fi de exemplu stiva si coada).
5.3. Implementarea unui container pentru stiva si coada
O stiva este o lista simplu inlantuita care are particularizate operatiile de introducere/extragere (functiile virtuale de la lista). Definitia stivei poate fi:
#include "SinglyList.h" //se bazeaza pe SinglyLinkedList
class Stack : public SinglyLinkedList ;
Se observa ca stiva definita astfel este o stiva "generica" (tipul elementelor nu e precizat), fiind in acest fel, la rindul sau un container: sau se poate deriva o stiva particulara sau se poate folosi "mai departe". Nu are nici un membru nou in plus fata de lista, mostenind tot ceea ce are nevoie. Atunci constructorul nu va face decit sa-l apeleze pe cel al listei; la fel si operatorul de asignare.
Metodele ce prezinta interes sint cele virtuale, care se modifica (particularizeaza) aici. Implementarea completa poate fi:
#include "Stack.h"
Stack::Stack()
:SinglyLinkedlist()
Stack::Stack(const Stack& st)
:SinglyLinkedlist(st) //atentie la transmiterea parametrilor
//folosind regulile de compatibilitate!
void Stack::operator=(const Stack& st)
int Stack::Store(void* item)
// inspectarea elementului din virful stivei
void* Stack::Examinare()
//Pop - extragere element din virful stivei
void* Stack::Retrieve()
Observatii
1. Utilizarea (netriviala) a pointerului "this" in operatorul de asignare.
2. Clasa "Stack" nu are definit destructor. Se genereaza implicit unul care-l apeleaza implicit pe cel al listei. Acesta la rindul sau apeleaza functia "Empty" ce sterge nodurile unei liste (si implicit ale unei stive).
Datorita faptului ca singura diferenta intre o "stiva" si o "coada" este modalitatea de introducere (la celalalt cap) al unui element, este "aproape" evident ca din stiva se va deriva coada:
#include "Stack.h" //depinde de Stack
class Queue : public Stack
Se observa ca acum "Coada" nu mai defineste decit strict ceea ce este necesar si nu mosteneste de la clasa parinte (aici Stack): constructorii, operatorul de asignare care NU poate fi virtual (deoarece are alt TIP de argument!!) si functia de inserare care TREBUIE modificata (este singura realmente diferita fata de "parinte").
Observatii
1. Constructorii nu se mostenesc.
2. Va amintiti modalitatea "traditionala" de definire si implementare a unei cozi ?! Exista diferente ???
Implementarea metodelor cozii ar putea fi:
#include "Queue.h"
Queue::Queue()
:Stack()
Queue::Queue(const Queue& q)
:Stack(q)
void Queue::operator=(const Queue& q)
//si intr-adevar singura modificare
int Queue::Store(void* item)
else
count++;
return 0;
Observatie
GATA!!
5.4. Exemplu de utilizare a claselor container definite
Se prezinta in continuare un program de test a celor doua clase (stiva si coada). Se remarca utilizarea lor in forma in care sint definite, fara particularizarea tipului informatiei. Aceasta se va face prin operatorii de conversie de tip (type- cast) tinind cont ca pointerii din stiva si coada sint "generici" (void*).
#include "Stack.h"
#include "Queue.h"
Stack s,s2; // 2 stive
Queue q,q2; // 2 cozi
int a[10]; //el. ce se vor introduce in stiva/coada
void main(void)
s2=s; // test operatori de asignare
q2=q;
printf("\n Stack2= ");
while( (ip=(int*)(s2.Retrieve())) != NULL )
printf("%3d", *ip);
printf("\n Queue2= ");
while( (ip=(int*)(q2.Retrieve())) != NULL )
printf("%3d", *ip);
|