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".
Clasificare
Sabloanele utilizate in sistemele OO pot fi clasificate intr-o ierarhie, dupa cum urmeaza:
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:
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.
Scop
Domeniu de aplicare |
|
|
|
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 |
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
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 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 interfetelorEste foarte important sa intelegem diferenta intre
Clasa defineste cum este implementat
obiectul = starea lui interna + implementarea operatiilor.
Tipul se refera doar la interfata
obiectului = setul mesajelor la care obiectul poate reactiona.
Este de asemenea important sa intelegem diferenta dintre
Mostenirea de clasa presupune ca
implementarea unui obiect este definita in termenii implementarii altui
obiect. Cu alte cuvinte, ea reprezinta un mecanism de reutilizare (partajare)
a reprezentarii si a codului.
Mostenirea de interfata este un
mecanism prin care un obiect poate fi utilizat in locul altuia.
Programarea prin interfete si nu prin implementariMostenirea de clasa este in esenta un mecanism care permite:
|
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 obiectelorCele mai cunoscute tehnici de reutilizare a functionalitatii in cadrul sistemelor OO sunt:
Mostenirea de clasa mai este cunoscuta sub numele de reutilizare tip "cutie alba" (white-box reuse), deoarece in majoritatea cazurilor o parte din starea interna a calselor parinte este vizibila in subclase.
Asamblarea obiectelor reprezinta o tehnica de obtinere a unor functii noi prin compunerea unor obiecte avand interfete bine definite. Tehnica mai este cunoscuta sub numele de reutilizare tip "cutie neagra" (black-box reuse), deoarece obiectele care se asambleaza nu isi cunosc unul altuia starea interna (ele apar unul fata de altul ca niste cutii negre).
Mostenirea de clasa se caracterizeaza prin urmatoarele:Toate aceste aspecte conduc spre formularea celui de-al doilea principiu al proiectarii OO:
- este definita static, la compilare si poate fi specificata direct, fiind suportata explicit de limbajele de programare;
- permite modificarea usoara a implementarii operatiilor reutilizate, si anume intr-o subclasa care redefineste o parte din operatiile clasei parinte pot fi afectate si operatii mostenite, daca acestea apeleaza operatii redefinite. In secventa C++ de mai jos este ilustrata sintetic aceasta situatie:
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:
- implementarea mostenita de la clasele parinte nu poate fi modificata la momentul executiei;
- cel mai adesea clasele parinte definesc cel putin partial reprezentarea fizica a subclaselor lor, deci subclasele au acces la detalii ale implementarii superclaselor. De aceea se mai spune ca mostenirea de clasa incalca principiile incapsularii;
- modificarile aduse implementarii unei superclase vor forta subclasele sa se modifice si ele. Dependentele de implementare pot cauza probleme atunci cand se incearca reutilizarea subclaselor: daca anumite aspecte ale implementarii mostenite nu corespund necesitatilor aplicatiei clasa parinte trebuie rescrisa sau inlocuita. Aceasta dependenta limiteaza flexibilitatea si, in ultima instanta, reutilizarea. O solutie in acest caz ar fi aplicarea mostenirii de la clase abstracte, deoarece ele includ implementare in mica masura.
- se defineste in mod dinamic, la executie, prin faptul ca anumite obiecte primesc referinte ale altor obiecte;
- necesita ca obiectele sa-si respecte unul altuia interfata, ceea ce presupune ca interfetele sa fie proiectate astfel incat sa nu impiedice utilizarea unui obiect in combinatie cu mai multe tipuri de obiecte. Deoarece obiectele sunt accesate doar prin intermediul interfetelor, nu este incalcat principiul incapsularii. In decursul executiei orice obiect poate fi inlocuit cu altul, atata timp cat obiectele respective au acelasi tip. In plus, datorita faptului ca si implementarea unui obiect este scrisa tot in termenii interfetelor altor obiecte, dependentele de implementare vor fi substantial reduse;
- prin compunerea obiectelor se obtin urmatoarele efecte asupra unui proiect: clasele sunt incapsulate si "concentrate" asupra cate unui singur obiectiv, ceea ce face ca ele, ca si ierarhiile lor sa aiba dimensiuni mici si sa fie mai usor de gestionat. Un proiect bazat pe compunerea obiectelor se caracterizeaza printr-un numar mai mare de obiecte si un numar mai mic de clase, iar comportarea sistemului va depinde de relatiile dintre obiecte, in loc sa fie definita de o anumita 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.
DelegareaReprezinta 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.
Mostenirea vs Tipurile parametrizateTipurile 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.
Structuri stabilite la compilare vs Structuri create la executieStructura 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.
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.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.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.
Tema
Se cere sa se creeze un ansamblu de clase prin care sa se modeleze urmatoarele relatii: