Decorator Iterator

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:

Obiectele Client utilizeaza interfata Component pentru a interactiona cu obiectele dintr-o structura compusa. Daca obiectul receptor este un Leaf, mesajul este tratat direct. Daca receptorul este un Composite, atunci el va redirectiona mesajul spre fiecare din componentele sale. Este posibil ca inainte si/sau dupa redirectionare sa se execute si alte operatii aditionale.
 

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;
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.
Implementare
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:
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>
<title>Titlul fisierului</title>
<body>
text
</body>
</html>
unde 'text', la randul sau este format dintr-o succesiune de elemente simple sau compuse, care pot fi:

Decorator Iterator