La elaborarea modelului obiectual al unei aplicatii se disting urmatoarele doua etape:
a) identificarea claselor - corespunzatoare conceptelor aplicatiei (substantivele),
b) stabilirea relatiilor dintre clase - corespunzatoare specificatiilor aplicatiei (verbele).
In cele ce urmeaza ne vom referi la urmatoarele tipuri de relatii intre clase:
Asociatie relatie de cooperare intre clase - corespunzatoare unui verb oarecare din specificatie, diferita de relatia parte-intreg (de exemplu relatia Student - Facultate). Relatia corespunzatoare dintre obiecte apartinand unor clase asociate se numeste legatura. O asociatie poate fi simpla sau multipla (daca un obiect asociant este pus in legatura, de exemplu prin pointeri, cu mai multe obiecte asociate, de acelasi tip). Aceasta relatie se reprezinta grafic (dupa metodologia OMT) in plan orizontal astfel:
![]() |
Agregare -
relatie de asociatie
prin care obiectul agregat este inclus in obiectul agregant (verbul caracteristic este a avea),
si evident ca poate fi simpla sau multipla:
Specializare (generalizare, mostenire) - relatie prin care sunt pastrate (mostenite) caracteristicile unei clase (de baza) si sunt adaugate diferentele specifice, formand o noua clasa (derivata). Mostenirea permite reutilizarea de cod definit in clasa de baza (superclasa) si in noua clasa (sublasa). Deci, o subclasa este o specializare a unei superclase (verbul caracteristic fiind a fi), iar o superclasa este o generalizare a unei subclase. Aceasta relatie este tranzitiva, iar structura formata de multimea clas 848g69i elor aflate in aceasta relatie (construita pe verticala) se numeste ierarhie de clase. Atat specializarea cat si mostenirea poate fi simpla sau multipla, reprezentarea facandu-se in plan vertical astfel:
![]() |
Se pot obtine in felul acesta si ierarhii spatiale de clase, avand relatii atat pe orizontala cat si pe verticala.
Prin aceasta relatie dintre clase se modeleaza o legatura intre obiectele instantiate (care
depind unul de celalalt). Traversarea unei asociatii se face
printr-un rol (nume dat extremitatatii
unei asociatii). Implementarea unei
asociatii se poate realiza astfel:
a) printr-un pointer - clasa asocianta contine un atribut de tip pointer spre clasa asociata,
b) printr-o clasa - cu atribute si comportare proprie, fiecare legatura fiind o instanta a acestei clase.
Asociatiile pot fi unidirectionale sau bidirectionale, iar relatiile de asociatie pot fi binare, ternare sau n-are. Pe de alta parte, asociatiile pot fi simple sau multiplicative (acestea din urma putand fi eventual si cu restrictii de ordonare sau de calificare).
Asociatiile multiplicative sunt caracterizate prin numarul de instante ale claselor care se afla in asociatie, iar pentru a reprezenta directia de asociatie se pot utiliza sageti.
O asociatie multiplicativa
poate sa fie neordonata (instantele formeaza
o multime),
sau poate sa fie ordonata (instantele formeaza o
lista ordonata).
O relatie multiplicativa cu
restrictie de calificare pune in relatie
doua clase printr-un calificant (atribut care reduce multiplicitatea
asociatiei printr-un criteriu
pe care trebuie sa-l indeplineasca obiectele asociate pentru a intra in
relatie).
La implementarea relatiilor de asociatie putem aplica cele doua metode amintite anterior:
a) prin pointeri spre clasa asociata incuibariti in clasa asocianta in functie de tipul relatiei de asociere astfel:
pentru relatia de asociatie simpla se adauga clasei asociante inca un atribut de tip pointer spre clasa asociata, impreuna cu relaxarea incapsularii pentru traversarea asociatiei alegand o metoda convenabila dintre urmatoarele variante:
modificarea protectiei datelor membru implicate in public,
utilizarea de metode sau clase friend,
extinderea interfetei prin metode de acces la componente.
pentru relatia de asociatie multipla se adauga clasei asociante mai multe atribute de tip pointer in functie de tipul asociatiei si ordinul de multiplicitate astfel:
daca este relativ mic, se vor adauga pointeri distincti,
iar in caz contrar se poate utiliza un sir de pointeri;
daca relatia este supusa unor restrictii de ordonare, se pot utiliza liste ordonate.
b)
prin clase
distincte care realizeaza abstractizarea asociatiilor daca legaturile au
proprietati si operatii proprii (nemaifiind necesara adaugarea de atribute
claselor care se asociaza), aceasta metoda utilizandu-se in special in cazul
asociatiilor bidirectionale de tip m-n),
sau o legatura poate conecta obiecte de clase diferite.
Exemplu:
Clase asociate
#include <conio.h>
#include <iostream.h>
#include 'VectCaut.h'
void main (void)
while (a);
cout << ' Dati m : '; cin >> n;
VectSort* Y=new VectSort(n); Y->Sort();
VectCaut* y=new VectCaut(Y); Y->Print();
do
while (a); getche();
// Clasa asocianta << VectCaut.h >>
#include 'VectSort.h'
class VectCaut ;
// Clasa asociata << VectSort.h >>
class VectSort ;
// Clasa asocianta << VectCaut.Cpp >>
# include 'VectCaut.h'
VectCaut::VectCaut (VectSort *p)
int VectCaut::CautSec(int a)
int VectCaut::CautBin(int a)
return 0;
// Clasa asociata << VectSort.Cpp >>
# include <iostream.h>
# include 'VectSort.h'
VectSort::VectSort (int k)
void VectSort::Sort( )
Sortat;
do k++; }
while (!Sortat);
void VectSort::Print ( )
int* VectSort::Adr ( )
int VectSort::Dim ( )
VectSort::~VectSort ( )
Relatia de agregare este cea mai puternica relatie de asociatie, in care o clasa este o
componenta a altei clase, deci relatia este binara, unidirectionala, iar
relatiile multiplicative sunt de tip 1-n. Prin aceasta relatie dintre clase, un obiect
al clasei agregate este parte
constituenta, atribut, al clasei agregante, deci verbul caracteristic
este a avea (contine). Obiectele constituente pot fi
independente sau doar componente ale obiectului care le include.
Aceasta relatie (notata cu simbolul avand semnificatia contine) are urmatoarele doua proprietati de baza:
a) tanzitivitate : Daca Cx Cy si Cy Cz, atunci Cx Cz
b) antisimetrie : Daca Cx Cy atunci Cy Cx .
Relatia de agregare poate fi:
a) fixa - numarul si tipul componentelor sunt fixe,
b) variabila - permite un numar variabil de obiecte de acelasi tip,
c) recursiva - accepta acelasi tip ca tip agregat si agregant.
Exemplu:
Relatia de agregare
#include <conio.h>
#include <iostream.h>
#include 'Vect_Agr.h'
void main (void)
while (a);
cout << ' Dati m : '; cin >> n;
VectCaut* Y=new VectCaut(n); Y->Sort(); Y->Print();
do
while (a); getche();
// Clasa Agreganta << Vect_Agr.h >>
#include 'VectSort.h'
class VectCaut ;
// Clasa Agreganta << Vect_Agr.Cpp >>
# include 'Vect_Agr.h'
VectCaut::VectCaut (int n) : V(n)
void VectCaut::Sort ()
void VectCaut::Print ()
int VectCaut::CautSec(int a)
int VectCaut::CautBin(int a)
return 0;
// Clasa Agregata << VectSort.h >>
class VectSort ;
// Clasa Agregata << VectSort.Cpp >>
# include <iostream.h>
# include 'VectSort.h'
// A fost deja descrisa la clase asociate.
In urmatorul exemplu se va folosi o clasa Iterator pentru lista simplu inlantuita care utilizeaza clasa Elem pentru un nod al listei si clasa Lista:
Iterator Lista
#include <conio.h>
#include <iostream.h>
# define Tel int
class Elem
friend class Lista;
friend class Iterator;
class Lista
void Ad (Tel);
friend class Iterator;
void Lista::Ad (Tel elem)
else Cap=new Elem(elem,0);
class Iterator
Tel operator () ( ) // elementul curent
void operator ++ ( ) // avanseaza in lista
int operator ! ( ) // exista element
void main (void)
getche();
Exista posibilitatea definirii unei clase (incuibate) in interiorul altei clase (ca si atribut al cesteia). Aceasta posibilitate exista de fapt si la structuri (struct) si uniuni (union).
Declararea obiectelor din clasa incuibata se poate realiza utilizand operatorul de scop (::) asa cum se poate vedea in exemplul urmator:
Clase Imbricate
class Cerc ; /*
*/
};
void main ()
Utilizarea obiectelor din clasa incuibata se poate realiza utilizand operatorul de apartenenta (.) dupa cum se vede in urmatorul exemplul:
Clase Incuibate
#include <conio.h>
#include <iostream.h>
class Cerc
void Print ()
};
Punct Centru;
public: Cerc (Punct P, float raza)
void Print()
};
void main ()
Evident ca din clasa incuibata (Punct) nu avem acces la elementele clasei din care face parte (Cerc). Daca se doreste acest lucru, atunci se poate proceda ca si in urmatorul exemplu:
Clase Incuibate
#include <conio.h>
#include <iostream.h>
class Cerc
class Punct;
friend Punct;
class Punct
void Print (Cerc c)
};
// void Cerc::Punct::Print (Cerc c)
void main ()
Se poate observa in exemplul dat ca referirea atributului r al clasei Cerc nu este permisa din interiorul clasei incuibate (vezi functia Print descrisa in ambele variante), motiv pentru care clasa Punct a fost declarata prietena.
In urmatorul exemplu sunt imbricate clasele R R2 R3
Clase Incuibate
#include <conio.h>
#include <iostream.h>
class R
class R2; friend R2;
class R2
class R3; friend R3;
class R3
void Print (R,R2);
};
void Print (R);
}; };
void R::R2::Print (R a)
void R::R2::R3::Print (R a, R2 b)
void main ()
Prin aceasta relatie putem modela similitudinile dintre clase doua sau mai multe clase. Pornind de la o clasa de baza (generala) se pot deriva noi clase (prin diferentele specifice). Obiectele clasei derivate mostenesc atributele si metodele clasei de baza la care se vor adauga noile elemente caracteristice (vor fi umflate), ceea ce permite reutilizarea resurselor deja pregatite in clasele de baza (pentru obiectele similare). Verbul caracteristic al acestei relatii de specializare este a fi ( . este un fel de . <a kind of>).
Mostenirea permite pastrarea elementelor (date si
functii
ale) unei clase de baza
(superclasa),
cu definirea de noi elemente construind o noua clasa
derivata (subclasa),
formand in felul acesta ierarhii de
clase. Mostenirea
poate fi si multipla
daca o clasa
mosteneste
mai multe clase. Deoarece aceasta
relatie este tranzitiva se utilizeaza si
termenii de stramos
si descendent.
![]() ![]() ![]() |
Relatia de derivare se poate descrie prin constructii speciale fara a mai fi nevoie de o relaxare a incapsularii, asa cum a fost necesara la relatiile prezentate anterior. Daca intr-o aplicatie se poate utiliza relatia de derivare, este de preferat in locul asociatiei sau agregarii pentru ca avem instrumente specializate in limbajul de programare.
O clasa derivata se declara astfel:
class Clasa_Derivata Lista_clase_de_baza
Lista claselor de baza poate sa contina si modificatorii de protectie (Mod_Pr) public, protected sau private, deci o derivare poate sa fie publica, protejata sau privata, accesul rezultat fiind redat in urmatoarele tabele.
De exemplu, daca cele doua clase
sunt descrise astfel:
class Cl_Baza
class Cl_Derivata : Mod_Pr Cl_Baza
atunci protectia membrilor din clasa derivata este redata in schema alaturata.
Ordinea de executare a constructorilor la instantierea obiectelor dintr-o clasa derivata:
prima data se executa constructorul clasei de baza, apoi constructorul clasei derivate (se construiste cadrul, apoi se adauga diferentele specifice).
Ordinea de executare a destructorilor la distrugerea obiectelor dintr-o clasa derivata:
prima data se executa destructorul clasei derivate, apoi destructorul clasei de baza
Constructorul clasei derivate transmite parametrii necesari constructorului clasei de baza prin apelul direct al acestuia astfel:
Clasa_Derivata Clasa_de_baza ( . ) ; // inline
sau
Clasa_Derivata :: Clasa_Derivata ( . ) Clasa_de_baza ( . ) ; //inline
Deoarece relatia de derivare este poate cea mai importanta relatie dintre clase, sunt oferite facilitati de implementare, care permit urmatoarele facilitati:
economia de cod - reutilizarea codului scris o singura data dupa care se mosteneste,
extensibilitate - re-specializare prin derivarea de noi ramuri dintr-o ierarhie,
polimorfism - intr-o ierarhie de clase se poate implementa o comportare polimorfica,
incapsulare-relaxare - relatia de derivare ofera posibilitatea inchiderii resurselor simultan cu deschiderea spre modificare si extensie.
Relatiile de derivare (de fapt si celelalte relatii dintre clase) sunt stabilite la compilare, deci nu se mai pot modifica pe parcursul executiei. Mai trebuie cunoscut faptul ca prin derivare nu se pot mosteni constructorii, destructorii, elementele prietene (functii, clase sau metode friend) si nici operatorii redefiniti.
Exemplu:
Relatia de derivare
#include <conio.h>
#include <string.h>
#include <iostream.h>
class N
~N
char* Modul ( )
};
void Print (N n)
class Z : public N
~Z
char Semnul( )
};
void Print (Z n)
void main (void)
Conversia unui obiect dintr-o clasa derivata intr-un obiect apartinand clasei de baza este permisa, invers insa nu (sursa trebuie sa acopere destinatia):
Contra_Exemplu:
Conversii la derivare
void main (void)
getche();
Pentru o functie care are ca parametru formal un obiect al clasei de baza este permis apelul avand ca parametru un obiect al clasei derivate, invers nu (o functie care are ca parametru formal un obiect al clasei derivate, nu poate fi apelata avand ca parametru actual un obiect al clasei de baza).
Exemplu:
Conversie la derivare
#include <conio.h>
#include <string.h>
#include <iostream.h>
class N
N (char* s)
N (N& n)
~N ( )
char* Modul ( )
void Print (N n)
class Z : public N
Z (char* s) : N(s+1)
~Z ( )
char Semnul( )
};
void Print (Z n)
void WriteAbs (N n)
void WriteAbs_(N*n)
void main (void)
In exemplul urmator se porneste de la clasa de baza VectBaza si se construieste clasa derivata Vect_Der :
Relatia de derivare
#include <conio.h>
#include <iostream.h>
#include 'Vect_Der.h'
void main (void)
while (a);
cout << ' Dati m : '; cin >> n;
Vect_Der* Y=new Vect_Der(n); Y->Sort(); Y->Print();
do
while (a); getche();
// Clasa de baza << VectBaza.h >>
class VectBaza ;
// Clasa de baza << VectBaza.Cpp >>
# include <iostream.h>
# include 'VectBaza.h'
VectBaza::VectBaza (int k)
void VectBaza::Sort( )
Sortat;
do k++; }
while (!Sortat);
void VectBaza::Print ( )
VectBaza::~VectBaza ( )
Clasa Derivata << Vect_Der.h >>
#include 'VectBaza.h'
class Vect_Der : public VectBaza ;
// Clasa Derivata << Vect_Der.Cpp >>
# include 'Vect_Der.h'
Vect_Der::Vect_Der (int n) : VectBaza(n)
int Vect_Der::CautSec(int a)
int Vect_Der::CautBin(int a)
return 0;
In exemplul care urmeaza, datele x,y (din clasa de baza Punct) au fost declarate protected deoarece clasa derivata (Cerc) le refera. La clasa derivata am utilizat modificatorul public pentru a putea utiliza si pentru Cerc operatia Contine (PIC).
// Program Fct_NeVirtuala;
#include <stdio.h>; #include <conio.h>; #include <iostream.h>; #include <math.h>;
float Sqr (float x)
class Punct ;
Punct::Punct (float x0, float y0)
float Punct::Dist (Punct P)
int Punct::Contine(Punct P)
class Cerc : public Punct
float Dist (Punct P)
};
void main (void)
Rezultate:
Cercul C nu contine punctul P.
Se observa in exemplul de mai sus ca rezultatul nu este cel dorit. Acest neajuns il vom rezolva mai tarziu declarand Distanta ca functie virtuala (pentru legare dinamica
Fata de mostenirea simpla, in care dintr-o singura clasa de baza se deriveaza una sau mai multe clase derivate (specializate), mostenirea multipla presupune existenta mai multor clase de baza din care un sau mai multe clase mostenesc diverse caracteristici.
Declararea
unei clase derivate din mai multe clase de baza se face astfel:
class Clasa_de baza ;
class Clasa_de baza ;
class Clasa__derivata : Mod_Pr Clasa_de baza Mod_Pr Clasa_de baza
unde Mod_Pr I
|
Datorita mostenirii multiple o clasa de baza poate fi prezenta in mai multe exemplare intr-o clasa derivata, asa cum se poate vedea in exemplul alaturat, unde datele membru ale clasei Animal vor fi mostenite in doua exemplare de catre clasa Caine (unul prin clasa Domestic, altul prin clasa Mamifer) si pot fi referite prin operatorul de rezolutie ( :: ) aplicat clasei prin care se face mostenirea (Nume).
|
Aceasta mostenire repetitiva a unei clase de baza este corecta si se poate utiliza astfel:
class Cl_Baza ;
class Cl_Der1 : public Cl_Baza ;
class Cl_Der2 : public Cl_Baza ;
class Clasa_Derivata : public Cl_Der1, Cl_Der2
|
Daca dorim realizarea unei singure copii a atributelor mostenite, vom folosi mostenirea multipla virtuala:
class Cl_Baza ;
class Cl_Der1 : virtual public Cl_Baza ;
class Cl_Der2 : virtual public Cl_Baza ;
class Clasa_Derivata : public Cl_Der1, Cl_Der2
;
Asa cum se poate vedea in exemplul urmator, vom avea un nume de mamifer si un nume domestic.
// Program Clase NeVirtuale;
#include <iostream.h>
#include <conio.h>
#include <string.h>
class Animal
};
class Mamifer : public Animal
};
class Domestic: public Animal
};
class Caine : public Mamifer, public Domestic
void Tip()
};
void main (void)
// Rezultate:
Nume Mamifer : Cane
Nume Domestic: Nero
Greutate : 13
Pret : 1300
Lant : 8
Daca dorim ca datele membru sa fie prezente intr-un singur exemplar in clasele derivate, atunci vom utiliza clase virtuale. O clasa de baza devine virtuala prin mostenire daca se declara aceasta prin cuvantul virtual plasat inaintea clasei (devenind astfel clasa virtuala fata de clasa derivata
Programul anterior modificat astfel incat numele sa fie memorat intr-un singur exemplar este urmatorul:
// Program Clase Virtuale;
#include <iostream.h>
#include <conio.h>
#include <string.h>
class Animal
};
class Mamifer : virtual public Animal
};
class Domestic: virtual public Animal
};
class Caine : public Mamifer, public Domestic
void Tip()
};
void main (void)
// Rezultate:
Nume Animal : Lup
Greutate : 13
Pret : 1300
Lant : 8
|
Pentru o ierarhie ca cea din figura alaturata, in care avem clase virtuale ( ) si nevirtuale ( ), se executa mai intai constructorii claselor de baza virtuale apoi cei ai claselor nevirtuale, iar constructorul clasei de baza se va executa pentru o singura data toate exemplarele virtuale si cate o data pentru fiecare exemplar nevirtual.
Exemplul:
// Program Ierarhie Clase Virtuale/Nevirtuale;
#include <iostream.h>
#include <conio.h>
class A
class B: virtual public A
class C: virtual public A
class D: public A
class E
class F
class G: public B, public C, public D, public E, virtual public F
void main (void)
// Rezultate:
A : Ob.
F : Ob.
B : Ob.
C : Ob.
A : Ob.
D : Ob.
E : Ob.
G : Ob.
|