Ierarhii de obiecte (Composite)
Definitie
Realizeaza compunerea obiectelor intr-o structura de
arbore, pentru a reprezenta ierarhii de tipul 'intreg-parti componente'.
Da clientilor posibilitatea de a trata in mod uniform atat obiectele individuale,
cat si structurile compuse de obiecte.
Context
Aplicatiile de genul editoarelor grafice permit utilizatorilor
sa construiasca diagrame complexe pe baza unor componente simple. Componentele
pot fi grupate pentru a forma structuri compuse, care la randul lor pot
fi grupate pentru a forma structuri mai mari s.a.m.d.
Intr-o aplicatie grafica simpla putem considera ca elemente
primitive obiecte ca: Text, Linie si Dreptunghi.
Pe langa acestea, vor fi definite alte clase, cu rol de containere pentru
elementele primitive. Problema care se pune aici este aceea ca, atat obiectele
primitive, cat si containerele sa poata fi tratate uniform de catre clienti.
Sablonul Composite aplica principiul compunerii recursive
pentru a rezolva aceasta problema.
Elementul cheie al sablonului Composite
il constituie definirea unei clase abstracte care sa reprezinte atat primitivele,
cat si containerele. Pentru exemplul nostru, o asemenea clasa este Grafic,
a carei interfata include:
Clasele Linie , Dreptunghi
si Text, care reprezinta elemente grafice primitive, vor
implementa operatia Draw pentru a desena linii, dreptunghiuri
si respectiv text. Aceste clase nu vor include celelalte operatii din grafic,
deoarece ele nu au elemente componente (children).
Clasa Imagine defineste un agregat de obiecte
Grafic. operatia Imagine::Draw este implementata
ca o secventa de apeluri ale operatiilor Draw pentru obiectele
Grafic componente:
void Imagine::Draw( ) {
//for all g in listaElem do g.Draw( ); } |
Deoarece Imagine este un container, ea va include si operatiile specifice containerelor, deci interfata ei este aceeasi cu cea a clasei Grafic. Urmarea este ca un element al containerului Imagine poate fi, la randul sau, un al container. Acest mod de a combina obiectele in structuri ierarhice se numeste compunere recursiva. In figura de mai jos este reprezentata o posibila structura pentru un container de tip Imagine:
Motivatii
Sablonul Composite se aplica in situatiile in care:
se doreste reprezentarea unor ierarhii de obiecte de tipul 'intreg-element component'
se urmareste sa se dea clientilor posibilitatea de a trata uniform obiectele individuale si agregatele de obiecte.
Solutie
In figura de mai jos este data structura de clase care constituie sablonul Composite:
Consecinte
Sablonul Composite:
permite ca un client sa trateze uniform obiectele Leaf si Composite, fara a avea nevoie sa stie exact cu ce obiect concret lucreaza la un moment dat;Implementare
da posibilitatea ca adaugarea unor noi clase Leaf sau Composite sa nu afecteze clasele client;
are dezavantajul ca nu ofera un mecanism prin care sa se poata impune anumite restrictii, inca la compilare, asupra tipului elementelor care pot face parte dintr-un container; pentru aceasta este nevoie sa se faca verificari in timpul executiei.
Pastrarea de referinte dinspre componente spre containerul din care fac parte poate usura traversarea ierarhiei de obiecte, respectiv operatia de stergere a componentelor. Locul cel mai indicat pentru referinta spre container este clasa Component, de unde va putea fi mostenita de Leaf si Composite.
In cazul in care se memoreaza aceasta referinta, este important sa se asigure consistenta informatiei, adica sa nu apara situatii in care un element indica spre un alt container decat cel care il contine. In acest scop, cel mai simplu este ca modificarea referintei spre container sa se faca doar in cadrul operatiilor Add/Remove din Composite, ceea ce asigura automat conditia de consistenta.Unul din scopurile sablonului Composite este, asa cum s-a aratat mai sus, acela de a permite clientilor sa lucreze cu obiectele Leaf si Composite in mod uniform, clasa concreta de care apartin obiectele respective fiind transparenta. Pentru aceasta, clasa Component trebuie sa defineasca cat mai multe dintre operatiile ce pot sa apara in Leaf si Composite. De obicei, Component ofera implementari implicite la aceste operatii, urmand ca Leaf si Composite sa le redefineasca.
Pe de alta parte, aici apare un conflict cu unul dintre principiile proiectarii ierarhiilor de clase, care spune ca intr-o superclasa trebuie definite doar acele operatii care au sens pentru subclasele ei. Tinand cont de aceasta, se observa ca in Component avem anumite operatii care nu par sa aiba sens pentru Leaf (este vorba de operatiile de gestionare a elementelor unui container). Se pune problema daca se poate defini o implementare implicita pentru asemenea operatii. De exemplu, in cazul operatiei GetChild, daca vom considera ca un obiect Leaf este un caz particular de container, adica unul care nu are niciodata nici o componenta, putem sa impunem ca operatia Component::GetChild sa nu returneze nici o componenta, urmand ca Leaf sa utilizeze aceasta implementare, iar Composite sa o redefineasca corespunzator.O problema importanta care apare in sablonul Composite este aceea a locului in care este mai bine sa fie declarate operatiile de lucru cu partile componente ale unui agregat ( ca Add/Remove), si anume: in Component sau in Composite. Decizia implica un compromis intre siguranta si transparenta:
In cazul in care se opteaza pentru a 2-a varianta si apar situatii in care nu se cunoaste clasa la care apartine obiectul concret cu care se lucreaza, in program pot sa se strecoare casting-uri eronate. Pentru a evita acest lucru, se poate prevedea o operatie de forma:
- daca operatiile se declara in Component, deci vor fi disponibile si in Leaf, atunci vom obtine transparenta, deoarece vom putea trata uniform toate obiectele Component si derivate din aceasta. Se va pierde in schimb la capitolul siguranta, deoarece ar putea exista din partea clientilor tentative de aplicare a operatiilor Add/Remove si pentru obiecte Leaf;
- daca operatiile respective se declara in Composite, atunci orice incercare de a le utiliza in contextul obiectelor Leaf va fi detectata inca de la compilare. Se va pierde in schimb la capitolul transparenta, deoarece Composite si Leaf vor avea interfete diferite.
Composite* Component :: getComposite( ); care implicit sa returneze pointerul nul, iar in Composite sa returneze pointer la obiectul receptor insusi. Rezultatul intors de getComposite( ) va putea fi apoi testat, asa ca in secventa urmatoare:
class Composite;
class Component {
public:
virtual Composite *getComposite( ) { return 0; }
//. . .
};
class Composite : public Component {
public:
void Add(Component*);
virtual Composite *getComposite( ) { return this; }
//. . .
};
class Leaf : public Component {
//aici se mosteneste Component::getComposite
//. . .
};
void oFunctie( ) {
Composite *aComposite = new Composite;
Leaf *aLeaf = nea Leaf;
Component *aComponent;
Composite *test;aComponent = aComposite;
if( test = aComponent->getComposite( ) ) {
test->Add(new Leaf); //aici se executa adaugarea
}aComponent = aLeaf;
if( test = aComponent->getComposite( ) ) {
test->Add(new Leaf); //aici nu se executa adaugarea
}
//. . .
}In legatura cu operatia de stergere a componentelor unui container se face observatia ca, in limbajele fara mecanism de "garbage collection", cel mai bine este ca obiectele Composite sa poarte responsabilitatea stergerii elementelor componente, cand containerul respectiv este distrus. O execeptie de la aceasta regula se va face cand obiectele Leaf sunt obiecte constante.
In unele aplicatii este necesar ca elementele ce compun un container sa se succeada intr-o anumita ordine. In exemplul prezentat la inceputul acestei lucrari elementele dintr-un container Imagine pot sa se afle intr-o ordine care sa reflecte modul de suprapunere a lor pe ecran. Sau, daca un obiect Composite reprezinta un arbore sintactic in cadrul unui compilator, atunci ordinea in care se succed elementele sale (care reprezinta structuri sintactice ale programului sursa) va reflecta ordinea textuala a programului.
In situatiile in care ordinea elementelor dintr-un container este semnificativa, operatiile de acces si gestionare a componentelor trebuie concepute astfel incat sa se asigure si ordonarea. In acest scop sablonul Iterator poate constitui un instrument util.
Tema
1. Intr-una din lucrarile de la laboratorul de IP-1 din semestrul trecut (sem.II anul IV) s-a dat ca tema o problema in care s-a aplicat "din greu" sablonul Composite. Sa se precizeze care a fost problema respectiva.2. Sa se elaboreze un program de creare a unui fisier HTML prin aplicarea sablonului Composite. Pentru aceasta se considera ca fisierul HTML are structura:
<html>unde 'text', la randul sau este format dintr-o succesiune de elemente simple sau compuse, care pot fi:
<title>Titlul fisierului</title>
<body>
text
</body>
</html>
- paragraf simplu = o fraza delimitata de tag-urile <p> si </p>
- o lista nenumerotata = o succesiune de elemente de lista, delimitata de tag-urile <ul> si </ul>; un element de lista este o secventa de text incadrata de tag-urile <li> si </li>, textul putand fi:
- o fraza simpla;
- o succesiune de paragrafe
- o alta lista
- un tabel = o succesiune de linii de tabel, delimitata de tag-urile <table BORDER COLS=nr_coloane> si </table>; o linie de tabel este o succesiune de celule, incadrata de tag-urile <tr> si </tr>; numarul celulelor trebuie sa fie egal cu nr_coloane, o celula fiind o secventa de text delimitata de tag-urile <td> si </td>; textul dintr-o celula poate fi:
- o fraza simpla;
- o succesiune de paragrafe.