Delegarea

Notiuni generale legate de conceptul de reutilizare in cadrul proiectarii sistemelor software
Sabloanele in contextul arhitecturilor orientate pe obiecte






Definitie

Toate sistemele orientate pe obiecte bine structurate abunda in sabloane (patterns), mergand de la mecanisme care contureaza forma sistemului in ansamblu, pana la sabloane locale (cum ar fi de exemplu modul de tratare a exceptiilor).

Un sablon reprezinta o solutie comuna a unei probleme intr-un anumit context.
Importanta sabloanelor (standardelor) in construirea sistemelor complexe a fost de mult recunoscuta in alte discipline. In cadrul comunitatii proiectantilor de software OO ideea de a aplica sabloane se pare ca a fost inspirata de propunerea unui arhitect, Christopher Alexander, care a lansat initiativa folosirii unui limbaj bazat pe sabloane pentru proiectarea cladirilor si a oraselor. Acesta afirma ca: "Fiecare sablon descrie o problema care apare mereu in domeniul nostru de activitate si indica esenta solutiei acelei probleme, intr-un mod care permite utilizarea solutiei de nenumarate ori in contexte diferite".
Desi in domeniul sistemelor OO solutiile sunt exprimate in termeni de obiecte si interfete (in loc de ziduri, usi, grinzi etc), esenta notiunii de sablon este aceeasi, adica de solutie a unei probleme intr-un context dat.
 

Clasificare

Sabloanele utilizate in sistemele OO pot fi clasificate intr-o ierarhie, dupa cum urmeaza:

Un idiom este legat de un anumit limbaj de programare si reprezinta o conventie general acceptata de utilizare a limbajului respectiv.
Exemple tipice de idiomuri pot fi gasite in cadrul limbajelor C/C++. Astfel, returnarea unei valori intregi care sa semnifice succesul sau esecul unei functii este un idiom din C (adoptat si de C++, pe langa generarea exceptiilor). Importanta acestui idiom consta in faptul ca el reprezinta un anumit stil acceptat de comunitatea "vorbitorilor" de C si orice programator care citeste o secventa C recunoaste imediat aceasta conventie. Violarea acestui idiom are drept consecinta producerea de cod greu de inteles, chiar daca este corect.
Practic, fiecare limbaj de programare isi are propriile sale idiomuri. La fel si o echipa de programatori isi stabileste un set de "obiceiuri", in functie de experienta si cultura pe care le poseda.
Se poate spune ca un idiom este o forma de reutilizare pe scara mica.

Un mecanism este o structura in cadrul careia obiectele colaboreaza in vederea obtinerii unui anumit comportament care satisface o anumita cerinta a problemei. Mecanismele reprezinta decizii de proiectare privind modul in care coopereaza colectiile de obiecte. Ele se mai numesc sabloane de proiectare (design patterns).
Majoritatea sistemelor OO includ mecanisme referitoare la:

Un cadru reprezinta o colectie de clase care ofera un set de servicii pentru un domeniu particular. Cadrul exporta un numar de clase si mecanisme pe care utilizatorii le pot adapta.
Cadrele sunt forme de reutilizare pe scara larga.
Cele mai raspandite tipuri de cadre sunt cele destinate creerii de interfete grafice.
 

Sabloanele de proiectare

Proiectarea sistemelor OO este o activitate dificila, iar proiectarea sistemelor OO reutilizabile este inca si mai grea. Solutia trebuie sa fie specifica problemei, dar totodata suficient de generala pentru a putea fi aplicata si pe viitor, pentru a evita in ultima instanta "reinventarea rotii" de fiecare data (sau cel putin pentru a minimiza acest lucru).

Un proiectant fara experienta este de multe ori coplesit de multitudinea optiunilor disponibile si are tendinta de a se intoarce la tehnicile non-obiectuale pe care le-a folosit in trecut. Un proiectant experimentat stie ca NU TREBUIE sa rezolve fiecare problema incepand de la zero, ci reutilizand solutii din proiecte anterioare. Atunci cand descopera o solutie buna o va folosi mereu. Acest tip de experienta este o parte din ceea ce confera unui proiectant statutul de expert.

In cadrul sistemelor OO realizate in mod profesionist se pot distinge sabloane de clase si de obiecte in comunicare care rezolva probleme specifice si fac ca sistemele respective sa fie mai flexibile, mai elegante si reutilizabile. Un proiectant familiar cu asemnea sabloane le va putea aplica repede, fara a trebui sa le redescopere.

Sabloanele de proiectare sunt de fapt o memorare pentru posteritate a experientei in domeniul proiectarii sistemelor OO.

Elementele de baza ale unui sablon de proiectare:


Organizarea unui catalog de sabloane

Deoarece exista multe sabloane de proiectare, este necesara o anumita clasificare a lor, in vederea alcatuirii unui catalog cu ele.

Criterii de clasificare:

scop: sabloanele pot fi, din acest punct de vedere: creationale, structurale sau comportamentale.
In tabelul de mai jos sunt incluse cele mai importante sabloane, clasificate dupa criteriile enumerate anterior:
 
Scop
Domeniu de aplicare
Creationale
Structurale
Comportamentale
Clasa Factory Method Adapter (class)
*Interface
*Marker Interface
Interpreter
Template Method
Obiect *Immutable
Abstract Factory
Builder
Prototype
Singleton
*Delegation
Adapter (object)
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor
Obs: sabloanele ale caror nume apar precedate de caracterul '*' formeaza o categorie de asa-numite sabloane fundamentale.
 

Cum ne ajuta sabloanele de proiectare sa rezolvam problemele de proiectare

Sabloanele de proiectare rezolva multe din problemele zilnice cu care se confrunta proiectantii. Cateva din aceste probleme sunt urmatoarele:

Gasirea obiectelor adecvate
Asa cum se stie, un obiect, care este caramida de baza a unui sistem OO, include atat date, cat si metode (operatii) care opereaza asupra datelor. Obiectul executa o operatie cand primeste o cerere (mesaj) de la un client.
Mesajele reprezinta singura cale prin care un obiect este determinat sa execute o operatie, in timp ce operatiile sunt singurul mod de a modifica datele interne ale obiectului. Din cauza acestor restrictii starea interna a obiectului se spune ca este incapsulata: ea nu poate fi accesata direct, iar reprezentarea ei este invizibila dinspre exteriorul obiectului.

Partea dificila in proiectarea unui sisitem OO este descompunerea sistemului in obiecte. Aceasta deoarece procesul este influentat de mai multi factori care actioneaza adesea in mod contradictoriu: incapsularea, granularitatea, dependentele, flexibilitatea, performantele, evolutia, gradul de reutilizare etc.

Multe din obiectele care apar la proiectare provin din modelul creat in faza de analiza. Dar, pe parcurs, la proiect se vor adauga si clase care nu au corespondente in lumea reala. Unele din aceste clase sunt de nivel primar (de ex. tablourile). Altele au un nivel de abstractizare mai ridicat. Sablonul Composite introduce o abstractiune menita sa asigure tratarea uniforma a obiectelor care nu au un corespondent fizic. Modelarea stricta a lumii reale va duce la un sistem care reflecta realitatea curenta, dar nu neaparat si pe cea viitoare. Abstractiunile identificate in timpul proiectarii sunt esentiale in obtinerea unui sistem flexibil.
Sabloanele ne pot ajuta in identificarea unor abstractiuni mai putin evidente si a obiectelor care le pot reprezenta. De exemplu, obiectele care reprezinta procese sau algoritmi nu apar in natura, dar ele nu pot lipsi dintr-un proiect. Sablonul Strategy descrie modul de implementare a unor familii interschimbabile de algoritmi. Sablonul State reprezinta fiecare stare a unei entitati sub forma unui obiect. Asemenea obiecte sunt rareori descoperite in timpul analizei sau chiar a stadiului incipient al proiectarii.

Determinarea granularitatii obiectelor
Obiectele ce compun un sistem pot varia "ingrozitor" ca marime si numar. Ele pot reprezenta practic orice: de la componente hardware pana la aplicatii intregi. Este dificil de stabilit unde trebuie sa se "opreasca" un obiect.
Exista sabloane care acopera si acest aspect. Astfel, sablonul Facade descrie modul in care subsisteme complete pot fi reprezentate ca obiecte, iar sablonul Flyweight arata cum se poate gestiona un numar urias de obiecte la nivelele cele mai fine de granularitate. Alte sabloane descriu caile prin care un obiect poate fi descompus in obiecte mai mici. Abstract Factory si Builder reprezinta obiecte a caror unica responsabilitate este crearea de alte obiecte. Visitor si Command reprezinta obiecte a caror unica responsabilitate este implementarea unui mesaj catre alt obiect sau grup de obiecte.

Specificarea interfetelor obiectelor
Pentru fiecare operatie declarata intr-un obiect se precizeaza numele, obiectele pe care le ia ca parametri si valoarea returnata; aceste elemente formeaza semnatura operatiei. Multimea tuturor semnaturilor corespunzatoare operatiilor dintr-un obiect reprezinta interfata obiectului. Interfata unui obiect descrie complet setul mesajelor care pot fi trimise spre obiectul respectiv.

Un tip este un nume utilizat pentru a referi o anumita interfata. Astfel, vom spune despre un obiect ca este de tipul Window daca el accepta toate mesajele corespunzatoare operatiilor definite in interfata numita Window. Ca urmare, un obiect poate avea mai multe tipuri, adica o parte a interfetei sale poate fi de un tip, iar alta parte - de alt tip. De asemenea, mai multe obiecte pot partaja un anumit tip comun, daca interfetele lor includ tipul respectiv.
Interfetele pot sa contina, la randul lor, alte interfete ca submultimi. Avand doua tipuri, T1 si T2, vom spune ca T1 este subtip al lui T2 daca interfata T1 include interfata T2. In acest caz T2 este supertip al lui T1. Mai spunem ca T1 mosteneste interfata T2.
Interfetele sunt lucruri fundamentale in sistemele OO. Obiectele sunt cunoscute doar prin intermediul interfetelor lor. O interfata nu da nici un detaliu relativ la implementarea unui obiect, iar obiecte distincte pot implementa in mod diferit o aceeasi cerere. Sau, altfel spus, doua obiecte avand implementari complet diferite pot avea interfete identice.

Cand o cerere este trimisa unui obiect, operatia care se va executa depinde de

Obiecte diferite care pot receptiona cereri identice pot avea implementari diferite ale operatiilor care vor satisface cererile respective. Asocierea unei cereri cu un obiect si cu o operatie a obiectului la momentul executiei se numeste asociere (legare) dinamica (dynamic binding). Asocierea dinamica permite scrierea de programe in care: Polimorfismul este un concept esential in cadrul tehnologiei orientate pe obiecte. El  permite: In acest context, sabloanele de proiectare ne ajuta la: Astfel, sablonul Memento descrie modul de incapsulare si salvare a starii interne a unui obiect, astfel incat starea respectiva sa poata fi restaurata ulterior.
Sabloanele specifica de asemenea si relatii intre interfete.

Specificarea implementarii obiectelor
Implementarea unui obiect este definita prin intermediul clasei obiectului. Clasa unui obiect specifica datele interne ale obiectului si definitiile operatiilor pe care acesta le poate executa.
Obiectele sunt create prin instantierea unei clase; se mai spune ca un obiect este o instanta a unei clase. Procesul de instantiere a unei clase presupune alocarea de memorie pentru datele interne ale obiectului respectiv si asocierea operatiilor cu aceste date. O clasa poate fi instantiata de mai multe ori, in felul acesta rezultand mai multe exemplare similare de obiecte.

Pe baza unor clase existente se pot defini noi clase, folosind mostenirea claselor. O subclasa mosteneste de la una sau mai multe clase parinte (superclase) toate datele si operatiile definite in acestea din urma. Obiectele instante ale subclasei vor

O clasa abstracta are drept scop principal definirea unei interfete comune pentru subclasele sale. Implementarea operatiilor unei clase abstracte este "pasata" partial sau in intregime subclaselor sale. De aceea, o clasa abstracta nu poate fi instantiata. Operatiile declarate intr-o clasa abstracta, dar neimplementate se numesc operatii abstracte.
Clasele care nu sunt abstracte se numesc clase concrete.

O subclasa poate detalia sau redefini comportamentul claselor parinte. Mai precis, subclasa poate redefini (override) o operatie care apare si intr-o clasa parinte, ceea ce permite subclasei sa poata prelua cereri in locul superclasei.

O clasa mixin este o clasa care are drept scop oferirea unei interfete sau a unei functionalitati optionale altor clase. Ea este similara unei clase abstracte, in sensul ca nu poate fi instantiata, dar nu poate figura singura ca parinte al unor subclase, ci doar intr-o schema de mostenire multipla.
 

Mostenirea claselor vs Mostenirea interfetelor
Este foarte important sa intelegem diferenta intre Un obiect poate avea mai multe tipuri, iar obiecte ale unor clase diferite pot avea acelasi tip.
Desigur ca intre clasa si tip exista o stransa legatura: prin faptul ca o clasa defineste operatiile pe care un obiect le poate executa, automat ea defineste si tipul obiectului. Cand spunem ca un obiect este instanta a unei clase, aceasta inseamna ca obiectul poseda interfata definita de clasa respectiva.

Este de asemenea important sa intelegem diferenta dintre

Este destul de usor de confundat aceste concepte intre ele deoarece majoritatea limbajelor de programare OO nu le disting in mod explicit. De exemplu, in C++ mostenire inseamna atat mostenire de clasa, cat si de interfata. O diferentiere intre cele doua s-ar putea face astfel: Multe dintre sabloanele de proiectare se bazeaza pe aceasta distinctie. De exemplu, obiectele dintr-un Chain of Responsibility trebuie sa aiba un tip comun, fara insa a avea si implementarea comuna. In cadrul sablonului Composite, Component defineste o interfata comuna, in timp ce Composite defineste o implementare comuna. Sabloanele Command, Observer, State si Strategy sunt adesea implementate cu ajutorul claselor abstracte.
 
Programarea prin interfete si nu prin implementari
Mostenirea de clasa este in esenta un mecanism care permite: Totusi, reutilizarea implementarii reprezinta doar o fateta a conceptului de mostenire. Posibilitatea de a defini familii de obiecte cu interfete identice (de obicei prin mostenirea de la o clasa abstracta) este un alt aspect important, deoarece polimorfismul depinde de el.
Clasele derivate dintr-o clasa abstracta vor partaja interfata acelei clase. Subclasele vor adauga sau vor redefini operatii, dar nu vor ascunde operatii ale clasei parinte. In felul acesta, toate subclasele vor putea raspunde la cererile corespunzatoare interfetei clasei abstracte parinte.
Exista 2 avantaje ale manipularii obiectelor prin intermediul interfetelor definite in clasele abstracte: Toate acestea reduc substantial dependentele dintre subsisteme, permitand formularea urmatorului principiu al proiectarii OO:
 
PROGRAMATI IN TERMENI DE INTERFETE, NU DE IMPLEMENTARI.

Printre altele, aceasta inseamna ca nu se recomanda declararea de variabile ale unor clase concrete, ci folosirea de referinte ale interfetelor definite prin clase abstracte. Pe de alta parte, atunci cand este necesara instantierea unor clase concrete, se recomanda aplicarea sabloanelor creationale care permit abstractizarea procesului de creare a obiectelor. In felul acesta se realizeaza o asociere a unei interfete cu implementarile ei transparenta la momentul instantierii.
 

Mecanisme ale reutilizarii
 

Mostenire vs Compunerea obiectelor
Cele mai cunoscute tehnici de reutilizare a functionalitatii in cadrul sistemelor OO sunt: Ambele tehnici au avantaje si dezavantaje. Astfel,
Mostenirea de clasa se caracterizeaza prin urmatoarele:
class Parent {
    //. . .
    public:
        void Operation1( );
        void Operation2( ); //apeleaza metoda Operation1
};
class Child: public Parent {
    //. . .
    public:
        void Operation1( ); //redefineste Operation1 
        //Operation2 ramane cea mostenita
};
void aFunction( ) {
    Parent p;
    Child c;
    p.Operation2( );
    c.Operation2( );
    //deoarece Operation2 apeleaza Operation1, metoda se va comporta
    //diferit pentru cele 2 obiecte
}
Compunerea obiectelor se caracterizeaza prin:
Toate aceste aspecte conduc spre formularea celui de-al doilea principiu al proiectarii OO:
 
PREFERATI COMPUNEREA OBIECTELOR MOSTENIRII DE CLASA.

Ideal ar fi ca reutilizarea sa se aplice nu in vederea crearii de componente noi, ci in vederea obtinerii unei functionalitati dorite prin compunerea obiectelor deja existente. In practica insa aceasta nu se poate realiza 100% deoarece setul de componente disponibile nu este niciodata destul de cuprinzator. De aceea, mostenirea si compunerea obiectelor merg "mana in mana".
Experienta arata ca adesea proiectantii folosesc mostenirea in mod abuziv. De aceea se recomanda studiul si aplicarea sabloanelor de proiectare, acestea bazandu-se foarte mult pe compunerea obiectelor.
 
 

Delegarea
Reprezinta o cale de aplicare a principiului compunerii obiectelor. Intr-o relatie de delegare 2 obiecte sunt implicate in rezolvarea unei cereri, si anume: obiectul care recepteaza mesajul (delegatorul) deleaga executia operatiei corespunzatoare unui alt obiect - delegat.
Acest lucru este oarecum similar cu situatia in care subclasele "paseaza" sarcina executiei unor operatii claselor parinte (este vorba despre operatiile mostenite si neredefinite). Dar, in timp ce clasa parinte a unei subclase ramane aceeasi pe toata durata executiei, in cazul delegarii obiectele delegat pot fi schimbate, cu conditia sa aiba aceeasi interfata.
Delegarea este considerata ca un sablon de proiectare fundamental, pe ea bazandu-se foarte multe din celelalte sabloane (ex: State, Visitor, Strategy, Mediator, Chain of Responsibility, Bridge).
 
Mostenirea vs Tipurile parametrizate
Tipurile parametrizate reprezinta o tehnica de reutilizare a functionalitatii care nu este neaparat legata de modelul orientarii pe obiecte. Ele permit definirea de catre utilizatori a unor tipuri noi, bazate pe alte tipuri care sa dau ca parametri. De exemplu, un tip Lista poate fi parametrizat prin tipul elementelor continute.
Printre limbajele care suporta aceasta tehnica se numara: Ada, Eiffel (prin tipurile generice) si C++ (prin template-uri).
Tipurile parametrizate sunt amintite aici deoarece reprezinta o a 3-a posibilitate de a defini un anumit comportament prin reutilizare (pe langa mostenire si compunerea obiectelor). Analizand comparativ cele 3 tehnici, se poate spune ca: Nici unul din sabloanele de proiectare nu se bazeaza explicit pe tehnica tipurilor parametrizate, dar acestea pot sa para in cazul implementarii unor sabloane in C++.
 
 
Structuri stabilite la compilare vs Structuri create la executie
Structura unui program OO aflat in executie aduce foarte putin cu structura codului. Aceasta din urma este "inghetata" la momentul compilarii si consta dintr-un ansamblu de clase aflate in relatii statice de mostenire.
Structura la executie consta dintr-o retea de obiecte aflate in continua schimbare si comunicare.
Practic, cele doua tipuri de structuri sunt aproape independente intre ele. Diferenta dintre ele poate fi redata in esenta prin compararea relatiilor de agregare si asociere (sau cunoastere - acquaintance).
Agregarea presupune ca un anumit obiect poseda sau este responsabil fata de un alt obiect, implicand faptul ca cele doua au durata de viata comuna.

Asocierea, numita si relatie de utilizare (de tip "using") presupune ca un obiect pur si simplu "are cunostinta" de existenta altui obiect. Cele 2 pot primi mesaje unul de la altul, dar nu sunt responsabile unul fata de altul. Asocierea este o relatie mai slaba decat agregarea.

Cele 2 tipuri de relatii pot fi usor confundate din cauza ca pot fi implementate in mod asemanator. De ex., in C++ se utilizeaza de obicei pointerii ca date membru pentru a stabili legatura intre obiecte.
De fapt agregarea si asocierea sunt determinate mai mult de intentia proiectantului decat de mecanismele de limbaj. Distinctia intre ele este dificil de observat in codul sursa. Agregarile apar in numar mai mic, dar au un caracter mai stabil in timp. Asocierile, din contra, se fac si se refac mai frecvent, uneori stabilindu-se doar pe durata unei operatii. Asocierile au un caracter mai dinamic, ceea ce la face greu de depistat in cadrul codului sursa.
Multe dintre sabloanele de proiectare, mai ales cele care au domeniul de aplicare la nivel de obiect, capteaza distinctia dintre structurile stabilite la compilare si cele de la executie, in sensul ca un specialist care cunoaste sabloanele de proiectare poate detecta mai usor in cadrul codului sursa structurile ce se vor crea la executie.


Tema

Se cere sa se creeze un ansamblu de clase prin care sa se modeleze urmatoarele relatii:

aratand care sunt facilitatile oferite, respectiv dezavantajele in fiecare dintre cazuri.

Delegarea