Strategy Composite

Decoratorul





Definitie
Reprezinta o alternativa mai flexibila fata de derivarea claselor in scopul extinderii functionalitatii. Acest sablon permite atasarea in mod dinamic de functiuni aditionale unui obiect. Se mai numeste infasurator (wrapper).

Context
De multe ori, intr-o aplicatie, se doreste adaugarea de functiuni suplimentare unor obiecte individuale, nu unei intregi clase. Spre exemplu, un generator de interfete utilizator trebuie sa permita proiectantului adaugarea de elemente cum ar fi barele de defilare sau chenarele oricarei componente a interfetei.
O posibilitate de a adauga functiuni o reprezinta mostenirea. A mosteni un chenar de la o alta clasa, insa, presupune ca fiecare instanta a subclasei obtinute va reprezenta o componenta cu chenar.
O varianta mai flexibila este aceea de a incorpora componenta in cauza intr-un alt obiect care stie sa adauge chenarul. Acest obiect se numeste decorator. Decoratorul respecta interfata componentei pe care o "decoreaza", astfel incat prezenta lui este transparenta pentru clientii componentei. Decoratorul redirecteaza spre componenta mesajele clientilor si, in plus, poate realiza actiuni suplimentare (ca de exemplu desenarea unui chenar) inainte sau dupa redirectare. Aceasta transparenta permite incuibarea recursiva a decoratorilor, rezultatul fiind obtinerea unui numar teoretic nelimitat de functiuni aditionale.
Sa presupunem, de exemplu, ca avem un obiect al unei clase TextView, care afiseaza un text intr-o fereastra. TextView nu va avea bare de defilare in mod implicit, deoarece nu are nevoie de ele intotdeauna. Atunci cand este necesar, se poate utiliza un obiect ScrollDecorator pentru a aduga bare de defilare. Daca dorim sa mai adaugam si un chenar in jurul lui TextView, putem folosi un obiect BorderDecorator. Practic, vom compune decoratorii cu obiectul TextView pentru a obtine efectul dorit:

Clasele ScrollDecorator si BorderDecorator sunt subclase ale unei clase abstracte, Decorator, care modeleaza componente vizuale ce pot decora alte componente vizuale. Relatiile intre aceste clase sunt redate in figura de mai jos:

VisualComponent este o clasa abstracta care modeleaza elementele vizuale ale interfetei. Ea defineste operatiile de desenare si de tratare a evenimentelor.
Clasa Decorator contine o referinta (component) spre un obiect VisualComponent, reprezentand componenta care trebuie decorata (componenta infasurata). Operatia Decorator::Draw realizeaza pur si simplu redirectarea cererii de desenare spre componenta infasurata:
 
void Decorator::Draw( ) {
    component -> Draw( );
}

Subclasele lui Decorator extind aceasta operatie, in sensul ca, pe langa redirectarea cererii de desenare, mai realizeaza si desenarea elementului de decor reprezentat:
 

void BorderDecorator::Draw( ) {
    Decorator::Draw( );
    DrawBorder();
}

In plus, subclasele lui Decorator pot include elemente specifice (de exemplu ScrollDecorator::ScrollTo), care vor fi utilizate atunci cand clientul lucreaza explicit cu obiecte ale acestor subclase.

Aspectul important al sablonului Decorator este acela ca decoratorii pot sa apara oriunde sunt asteptate obiecte ce reprezinta componentele decorate (VisualComponent in exemplul prezentat).
In felul acesta, din punct de vedere al clientilor, nu exista diferente intre o componenta decorata si una nedecorata.
 

Motivatii

Sablonul Decorator se aplica in urmatoarele situatii:

pentru a adauga functiuni in mod dinamic si transparent unor obiecte individuale;
pentru a putea elimina anumite functionalitati;
cand extinderea functionalitatii prin mostenire nu este posibila. Uneori avem de a face cu un numar mare de extensii independente intre ele, ceea ce ar duce la necesitatea crearii unui numar imens de subclase, ca sa se acopere toate combinatiile posibile.
Solutie
In figura de mai jos este data structura de clase care constituie sablonul Decorator:

Orice decorator redirectioneaza mesajul Operation primit de la clienti spre componenta pe care o infasoara.
 
void Decorator::Operation( ) {
    compref -> Operation( );
}

Optional, decoratorul concret poate executa operatii aditionale (AddedBehavior) inainte sau dupa redirectare.
 

void ConcreteDecorator::Operation( ) {
    Decorator::Operation( );
    AddedBehavior( );
}

Consecinte
 

Decoratorii permit adaugarea/eliminarea de functiuni in timpul executiei, pur si simplu prin asocierea/disocierea obiectelor cu rol de decoratori la/de obiectele cu rol de componente infasurate. Daca s-ar aplica mostenirea, ar trebui sa se defineasca subclase pentru fiecare functie noua (de exemplu BorderedScrollableTextView, BorderedTextView etc).
Spre deosebire de mostenire, decoratorii permit, printre altele, si adaugarea aceleiasi functiuni de mai multe ori, De exemplu, pentru a prevedea un TextView cu chenar dublu, vom atasa 2 obiecte BorderDecorator.
In lucrul cu decoratorii se aplica principiul "platesti in masura in care consumi". Aceasta inseamna ca, in loc sa se includa toate functiile previzibile intr-o singura clasa complexa, care apoi sa fie adaptata necesitatilor, se defineste o clasa simpla, careia i se adauga proprietati suplimentare folosind obiectele Decorator. Functiunile pot fi asamblate din piese simple. Ca urmare, o clasa nu trebuie sa suporte (deci sa "plateasca") pentru facilitati pe care nu le foloseste. Pe de alta parte, se pot defini noi tipuri de decoratori, independent de clasele obiectelor infasurate.
Un decorator nu este identic cu componentele pe care le infasoara. Desi el actioneaza ca o "manta" transparenta pentru o componenta, din punct de vedere al identitatii obiectelor, componenta decorata nu este identica cu componenta simpla.
Un proiect care utilizeaza sablonul Decorator se materializeaza adesea in sisteme compuse dintr-un numar foarte mare de obiecte mici, care seamana intre ele. Diferentele se manifesta doar in modul de interconectare a obiectelor. Desi aceste sisteme sunt usor de adaptat de catre cei care le inteleg, ele pot fi dificil de invatat si depanat.


Implementare

Interfata unui decorator trebuie sa fie conforma cu interfata componentelor pe care le va decora. De aceea, clasele ConcreteDecorator trebuie sa implementeze (sau sa mosteneasca) o interfata (clasa) comuna.
Daca exista doar o singura functie aditionala care poate fi atasata unei componente, atunci nu mai este necesara clasa abstracta Decorator, ci este suficienta o clasa ConcreteDecorator.

De obicei referinta spre Component dintr-un decorator se initializeaza prin constructorul decoratorului.

Pentru a asigura consistenta interfetelor, atat componentele, cat si decoratorii trebuie sa mosteneasca o clasa comuna, Component. Este important ca aceasta clasa sa fie simpla, scopul ei principal fiind definirea interfetei si nu stocarea de date.

Putem considera un decorator ca fiind un invelis in jurul unui obiect, care schimba comportamentul acelui obiect. O alta alternativa ar fi sa se modifice interiorul obiectului respectiv (in loc sa se modifice "pielea"). Strategy este un exemplu de sablon care implica schimbarea interiorului.
Sablonul Strategy reprezinta o alegere mai indicata in situatiile in care clasa Component este foarte complexa, ceea ce ar face ca aplicarea decoratorului sa fie mai costisitoare. In sablonul Strategy, componenta (contextul) redirecteaza anumite cereri spre un obiect Strategy separat. Modificarea sau extinderea functionalitatii componentei se realizeaza prin inlocuirea obiectului Strategy.
Considerand exemplul cu componentele vizuale, s-ar putea accepta ca o componenta a interfetei sa fie decorata cu mai multe tipuri de chenare. Pentru aceasta se poate utiliza un obiect Border care sa incapsuleze un anumit algoritm de desenare a chenarului. Daca un obiect Component (care in acest caz joaca rolul de Context pentru sablonul Strategy) in loc sa contina o referinta la un singur obiect Strategy, ar contine o refeinta spre inceputul unei liste de obiecte Strategy, am abtine un efect similar cu incuibarea recursiva a decoratorilor.

Intrucat un decorator modifica din afara comportamentul unei componente, aceasta nu trebuie sa stie nimic despre decoratorii atasati ei. In cazul sablonului Strategy, componenta trebuie sa stie care sunt extensiile posibile; de aceea, uneori poate aparea necesitatea modificarii componentei daca se adauga strategii noi.
Pe de alta parte, o strategie poate avea propria interfata specializata, in timp ce interfata unui decorator trebuie sa includa obligatoriu interfata componentei. Spre exemplu, o strategie necesara desenarii unui chenar necesita doar operatiile strict specifice (ca DrawBorder, GetWidth etc), ceea ce inseamna ca strategia ramane o clasa simpla, chiar daca clasa Component este complexa.

In cazul aplicatiilor de generare a interfetelor utilizator, varianta cu Strategy se poate aplica si pentru tratarea evenimentelor, si anume: o componenta a interfetei va referi o lista de obiecte "sensibile" la anumite evenimente, cum ar fi actionarea unor taste. La aparitia unui asemenea eveniment, lista de obiecte va fi baleiata, in cautarea handler-ului adecvat. Daca nu exista un handler adecvat, se va executa o secventa de operatii implicite, prevazute pentru evenimente neasteptate.


Exemple de aplicare a sablonului din Java API
(Pentru anul VI)

Clasele FilterInputStream, respectiv FilterOutputStream, impreuna cu clasele derivate din ele constituie "decoratori" pentru clasele InputStream, respectiv OutputStream.



Tema

Se cere sa se scrie o aplicatie de creare a unui fisier HTML constand dintr-o secventa de cuvinte care pot fi formatate prin aplicarea unor combinatii de urmatorii "decoratori":

Se precizeaza ca fisierul HTML va avea urmatoarea structura:
<html>
<title>Titlul fisierului</title>
<body>
cuvant_1 <br>
cuvant_2 <br>
. . .
</body>
</html>
Cuvintele vor fi citite dintr-un fisier de text. "Decorarea" cuvintelor presupune incadrarea acestora cu urmatoarele secvente: Stabilirea decoratiunilor pentru fiecare cuvant se lasa la latitudinea proiectantului.

Strategy Composite