principii de oop
DESCRIPTION
pooTRANSCRIPT
Şabloane pentru proiectare (Design Patterns)
1. Introducere
Proiectarea orientată pe obiecte a software-ului presupune identificarea de obiecte,
abstractizarea lor în clase de granularitate potrivită, definirea interfeţelor şi ierarhiilor
de moştenire, stabilirea relaţiilor între aceste clase.
Soluţia trebuie să rezolve problema şi să fie în acelaşi timp suficient de flexibilă
pentru a rezista la noi cerinţe şi probleme ce pot apare în timp.
Proiectanţii cu experienţă refolosesc soluţiile bune de câte ori au ocazia. Există
grupări de clase sau obiecte care se repetă în cele mai diferite sisteme. Acestea
rezolvă probleme specifice, folosirea lor fac proiectele mai flexibile, mai elegante,
reutilizabile. Un proiectant care stăpâneşte un set de asemenea şabloane le poate
aplica imediat la noile proiecte fără a mai fi nevoit să le redescopere.
Şabloanele ce se pot refolosi pot fi general valabile sau specifice unui domeniu, de
exemplu pentru probleme de concurenţă, sisteme distribuite, programare în timp real,
etc.
2. Clasificare
Şabloanele utilizate în sistemele OO pot fi clasificate într-o ierarhie după cum
urmează:
Idiomuri
Mecanisme
Cadre (frameworks).
Un idiom este legat de un anumit limbaj de programare şi reprezintă o convenţie
general acceptată de utilizare a limbajului respectiv.
Exemple tipice de idiomuri pot fi găsite în cadrul limbajelor C/C++. Returnarea unei
valori întregi care să semnifice succesul sau eşecul unei funcţii este un idiom din C
(adoptat şi de C++, pe langă generarea excepţiilor). Importanţa acestui idiom constă
în faptul că el reprezintă un anumit stil acceptat de comunitatea utilizatorilor de C şi
orice programator care citeşte o secvenţă C recunoaşte imediat această convenţie.
Încălcarea acestui idiom are drept consecinţă producerea de cod greu de înţeles, chiar
dacă este corect. Practic fiecare limbaj de programare îşi are propriile sale idiomuri.
La fel şi o echipă de programatori îşi poate stabili un set de obiceiuri, în funcţie de
experienţa şi cultura pe care le posedă.
Se poate spune că un idiom este o formă de reutilizare pe scară mică.
Un mecanism este o structură în cadrul căruia obiectele colaborează în vederea
obţinerii unui anumit comportament ce satisface o anumită cerinţă a problemei.
Mecanismele reprezintă decizii de proiectare privind modul în care cooperează
colecţiile de obiecte. Ele se mai numesc şi sabloane de proiectare (design patterns).
Majoritatea sistemelor OO includ mecanisme referitoare la:
persistenţa obiectelor
controlul stocării
controlul proceselor
transmisia/recepţia mesajelor
distribuirea şi migrarea obiectelor
conectarea în reţele (networking)
tranzacţii
evenimente
modul de prezentare ("look & feel") al aplicaţiei.
Un cadru reprezintă o colecţie de clase care oferă un set de servicii pentru un
domeniu particular. Cadrul exportă un număr de clase şi mecanisme pe care
utilizatorii le pot adapta.
Cadrele sunt forme de reutilizare pe scară largă.
Cele mai răspândite tipuri de cadre sunt cele destinate creării de interfeţe grafice.
3. Definiţie
Un sablon reprezintă o soluţie comună a unei probleme într-un anumit context.
Importanţa şabloanelor (standardelor) în construirea sistemelor complexe a fost de
mult recunoscută în alte discipline. În cadrul comunităţii proiectanţilor de software
OO (orientate pe obiecte) ideea de a aplica şabloane se pare că a fost inspirată de
propunerea unui arhitect, Christopher Alexander, care a lansat iniţiativa folosirii unui
limbaj bazat pe sabloane pentru proiectarea clădirilor şi a oraselor. Acesta afirma că:
"Fiecare şablon descrie o problemă care apare mereu în domeniul nostru de activitate
şi indică esenţa soluţiei acelei probleme într-un mod care permite utilizarea soluţiei de
nenumărate ori în contexte diferite".
Deşi în domeniul sistemelor OO soluţiile sunt exprimate în termeni de obiecte şi
interfeţe (în loc de ziduri, uşi, grinzi etc), esenţa noţiunii de sablon este aceeaşi, adică
de soluţie a unei probleme într-un context dat.
Şabloanele de proiectare sunt o memorare pentru posteritate a experienţei în domeniul
proiectării sistemelor OO.
Un şablon este descris de patru elemente:
Nume: foloseşte pentru identificare; descrie sintetic problema rezolvată de
şablon şi soluţia.
Problema: descrie când se aplică şablonul; se descrie problema şi contextul.
Solutia: descrie elementele care intră în rezolvare, relaţiile între ele,
responsabilităţile lor şi colaborările între ele.
Consecinţe si compromisuri: implicaţiile folosirii şablonului, costuri şi
beneficii. Acestea pot privi impactul asupra flexibilităţii, extensibilităţii sau
portabilităţii sistemului, după cum pot să se refere la aspecte ale implementării
sau limbajului de programare utilizat. Compromisurile sunt de cele mai multe
ori legate de spaţiu şi timp.
4. Organizarea unui catalog de şabloane
Şabloanele sunt la diferite niveluri de abstractizare, au granularităţi diferite.
Deoarece există multe şabloane de proiectare este necesară o anumită clasificare a lor
în vederea alcătuirii unui catalog.
Criterii de clasificare:
Scop - şabloanele pot fi creaţionale, structurale sau comportamentale.
o Şabloanele creaţionale (creational patterns) privesc modul de creare al
obiectelor.
o Şabloanele structurale (structural patterns) se referă la compoziţia
claselor sau al obiectelor.
o Şabloanele comportamentale (behavioral patterns) caracterizează
modul în care obiectele şi clasele interacţionează şi îşi distribuie
responsabilităţile.
Domeniu de aplicare - şabloanele se pot aplica obiectelor sau claselor.
o Şabloanele claselor se referă la relaţii dintre clase, relaţii stabilite prin
moştenire şi care sunt statice (fixate la compilare).
Şabloanele creaţionale ale claselor acoperă situaţiile în care o
parte din procesul creării unui obiect cade în sarcina
subclaselor.
Şabloanele structurale ale claselor descriu modul de utilizare a
moştenirii în scopul compunerii claselor.
Şabloanele comportamentale ale claselor utilizează moştenirea
pentru descrierea unor algoritmi şi fluxuri de control.
o Şabloanele obiectelor se referă la relaţiile dintre obiecte, relaţii care
au un caracter dinamic.
Şabloanele creaţionale ale obiectelor acoperă situaţiile în care o
parte din procesul creării unui obiect cade în sarcina unui alt
obiect.
Şabloanele structurale ale obiectelor descriu căile prin care se
asamblează obiecte.
Şabloanele comportamentale ale obiectelor descriu modul în
care un grup de obiecte cooperează pentru a îndeplini o sarcină
ce nu ar putea fi efectuată de un singur obiect.
În tabelul de mai jos sunt incluse cele mai importante şabloane, clasificate după
criteriile enumerate anterior:
Scop
Domeniu de
aplicare
Creationale
Structurale
Comportamentale
Clasa
Factory Method Adapter (clasa)
Interface
Marker Interface
Interpreter
Template Method
Obiect
Immutable
Abstract Factory
Builder
Prototype
Singleton
Delegation
Adapter (obiect)
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor
5. Rolul şabloanele de proiectare în rezolvarea problemele de proiectare
Şabloanele de proiectare rezolvă multe din problemele cu care se confruntă
proiectanţii. Câteva din aceste probleme sunt urmatoarele:
5.1 Găsirea obiectelor adecvate
Un obiect, care intră în alcătuirea programelor OO, împachetează atât date, cât şi
metode (operaţii) ce operează asupra datelor. Obiectul execută o operaţie când
primeşte o cerere (mesaj) de la un client.
Mesajele reprezintă singura cale prin care un obiect este determinat să execute o
operaţie, în timp ce operaţiile sunt singurul mod de a modifica datele interne ale
obiectului. Din cauza acestor restricţii starea internă a obiectului se spune că este
încapsulată: ea nu poate fi accesată direct, iar reprezentarea ei este invizibilă dinspre
exteriorul obiectului.
Partea dificilă în proiectarea unui sistem OO este descompunerea sistemului în
obiecte. Aceasta deoarece procesul este influenţat de mai mulţi factori care acţionează
adesea în mod contradictoriu: încapsularea, granularitatea, dependenţele,
flexibilitatea, performanţele, evoluţia, gradul de reutilizare etc.
Există mai multe metodologii OO: unele se concentrează pe substantivele şi verbele
extrase din enunţul problemei, altele se concentrează pe colaborările şi
responsabilităţile din sistem sau se modelează lumea reală şi obiectele găsite la
analiză se translatează şi în proiectare.
Multe din obiectele care apar la proiectare provin din modelul creat în faza de analiză.
La proiect se vor adauga însă şi clase care nu au corespondenţă în lumea reală. Unele
din aceste clase sunt de nivel primar (de exemplu tablourile), altele au un nivel de
abstractizare mai ridicat. De exemplu sablonul Composition introduce o abstracţie
menită să asigure tratarea uniformă a obiectelor care nu au un corespondent fizic.
Modelarea strictă a lumii reale va duce la un sistem ce reflectă realitatea curentă, dar
nu neapărat şi pe cea viitoare. Abstracţiunile identificate în timpul proiectării sunt
esenţiale în obţinerea unui sistem flexibil.
Şabloanele ne pot ajuta în identificarea unor abstracţiuni mai puţin evidente şi a
obiectelor care le pot reprezenta. De exemplu obiectele care reprezintă procese sau
algoritmi nu apar în natură, dar ele nu pot lipsi dintr-un proiect. Şablonul Strategy
descrie modul de implementare a unor familii interschimbabile de algoritmi. Şablonul
State reprezintă fiecare stare a unei entităţi sub forma unui obiect. Asemenea obiecte
sunt rareori descoperite în timpul analizei sau chiar a stadiului incipient al proiectării.
5.2 Determinarea granularităţii obiectelor
Obiectele ce compun un sistem pot varia enorm ca mărime şi ca număr. Ele pot
reprezenta practic orice începând de la componente hardware până la aplicaţii întregi.
Cum decidem ce ar trebui sa fie un obiect?
Există şabloane care acoperă şi acest aspect. Astfel, şablonul Facade descrie modul în
care subsisteme complete pot fi reprezentate ca obiecte, şablonul Flyweight arată cum
se poate gestiona un număr uriaş de obiecte la nivelurile cele mai fine de
granularitate. Alte şabloane descriu căile prin care un obiect poate fi descompus în
obiecte mai mici. Abstract Factory şi Builder reprezintă obiecte a căror unică
responsabilitate este crearea de alte obiecte. Visitor şi Command reprezintă obiecte a
căror unică responsabilitate este implementarea unui mesaj către alt obiect sau grup de
obiecte.
5.3 Specificarea interfeţelor obiectelor
Pentru fiecare operaţie declarată într-un obiect se precizează numele, obiectele pe
care le ia ca parametrii şi valoarea returnată; aceste elemente formează semnătura
operaţiei. Mulţimea tuturor semnăturilor corespunzătoare operaţiilor dintr-un obiect
reprezintă interfaţa obiectului. Interfaţa unui obiect descrie complet setul mesajelor
care pot fi trimise spre obiectul respectiv.
Un tip este un nume utilizat pentru a referi o anumită interfaţă. Astfel, vom spune
despre un obiect că este de tipul Window dacă el acceptă toate mesajele
corespunzătoare operaţiilor definite în interfaţa numită Window. Ca urmare, un obiect
poate avea mai multe tipuri, adică o parte a interfeţei sale poate fi de un tip, iar altă
parte de alt tip. De asemenea, mai multe obiecte pot partaja un anumit tip comun, daca
interfeţele lor includ tipul respectiv. Interfeţele pot să conţină, la rândul lor, alte
interfeţe ca submulţimi. Având două tipuri, T1 şi T2, vom spune că T1 este subtip al
lui T2 dacă interfaţa T1 include interfaţa T2. În acest caz T2 este supertip al lui T1.
Mai spunem ca T1 moşteneşte interfaţa T2.
Interfeţele sunt lucruri fundamentale în sistemele OO. Obiectele sunt cunoscute doar
prin intermediul interfetelor lor. O interfaţă nu dă nici un detaliu relativ la
implementarea unui obiect, iar obiecte distincte pot implementa în mod diferit o
aceeasi cerere. Altfel spus, două obiecte având implementări complet diferite pot avea
interfeţe identice.
Când o cerere este trimisă unui obiect, operaţia care se va executa depinde de:
cerere
obiectul care receptionează cererea.
Obiecte diferite care pot recepţiona cereri identice pot avea implementări diferite ale
operaţiilor ce vor satisface cererile respective. Asocierea unei cereri cu un obiect şi cu
o operaţie a obiectului la momentul execuţiei se numeşte asociere (legare) dinamică
(dynamic binding). Asocierea dinamică permite scrierea de programe în care:
la emiterea unei cereri să nu ne preocupe ce obiect o va recepţiona, ştiindu-se
că orice obiect a cărui interfaţă include o semnătura potrivită va fi bun;
obiecte având interfeţe identice pot fi substituite unul altuia la executie;
această posibilitate de substituire se mai numeşte polimorfism.
Polimorfismul este un concept esenţial în cadrul tehnologiei orientate pe obiecte.
El permite:
ca un obiect client să nu aibă nevoie să cunoască altceva despre alte obiecte
decât că posedă o anumită interfaţă;
simplificarea definiţiei clienţilor;
decuplarea obiectelor unele de altele;
ca la execuţie obiectele să-şi modifice relaţiile dintre ele.
În acest context, şabloanele de proiectare ne ajută la:
definirea interfeţelor;
identificarea elementelor care NU trebuie să apară într-o interfaţă.
Astfel, şablonul Memento descrie modul de încapsulare şi salvare a stării interne a
unui obiect pentru ca starea respectivă să poată fi restaurată ulterior. Şabloanele
specifică de asemenea şi relaţii între interfeţe.
5.4 Specificarea implementării obiectelor
Implementarea unui obiect este definită prin intermediul clasei obiectului. Clasa unui
obiect specifică datele interne ale obiectului şi definiţiile operaţiilor pe care acesta le
poate executa.
Obiectele sunt create prin instanţierea unei clase; se mai spune că un obiect este o
instanţă a unei clase. Procesul de instanţiere a unei clase presupune alocarea de
memorie pentru datele interne ale obiectului respectiv şi asocierea operaţiilor cu
aceste date. O clasă poate fi instanţiată de mai multe ori, în felul acesta rezultând mai
multe exemplare similare de obiecte.
Pe baza unor clase existente se pot defini noi clase, folosind moştenirea claselor. O
subclasă moşteneşte de la una sau mai multe clase părinte (superclase) toate datele şi
operaţiile definite în acestea din urmă. Obiectele instanţe ale subclasei vor
conţine toate datele definite în subclasa şi în clasele părinte
putea executa toate operaţiile definite în subclasa şi în clasele părinte.
O clasă abstractă are drept scop principal definirea unei interfeţe comune pentru
subclasele sale. Implementarea operaţiilor unei clase abstracte este pasată parţial sau
în întregime subclaselor sale. De aceea, o clasă abstractă nu poate fi instanţiată.
Operaţiile declarate într-o clasă abstractă, dar neimplementate se numesc operaţii
abstracte. Clasele care nu sunt abstracte se numesc clase concrete.
O subclasă poate detalia sau redefini comportamentul claselor părinte. Mai precis,
subclasa poate redefini (override) o operaţie care apare şi într-o clasă părinte, ceea ce
permite subclasei să poată prelua cereri în locul superclasei.
O clasa mixin este o clasă care are drept scop oferirea unei interfeţe sau a unei
funcţionalităţi opţionale altor clase. Ea este similară unei clase abstracte, în sensul că
nu poate fi instanţiată, dar nu poate figura singură ca părinte al unor subclase, ci doar
într-o schemă de moştenire multiplă.
5.4.1 Moştenirea claselor şi moştenirea interfeţelor
Este important să inţelegem diferenţa între clasa unui obiect şi tipul obiectului.
Clasa defineşte starea internă a obiectului şi implementarea operaţiilor lui. Tipul
obiectului se referă doar la interfaţa obiectului - setul cererilor la care obiectul poate
răspunde. Un obiect poate avea mai multe tipuri, iar obiecte de clase diferite pot avea
acelaşi tip.
Desigur că între clasă şi tip există o strânsă legătura: prin faptul că o clasă defineşte
operaţiile pe care un obiect le poate executa, automat ea defineşte şi tipul obiectului.
Când spunem că un obiect este instanţă a unei clase, aceasta înseamnă că obiectul
posedă interfaţa definită de clasa respectivă.
Un limbaj ca C++ foloseşte clasele pentru a specifica tipul obiectului şi
implementarea.
Este de asemenea important să inţelegem diferenţa dintre moştenirea de clasă şi
moştenirea de interfaţă (subtipizare). Moştenirea de clasă presupune că implementarea
unui obiect este definită în termenii implementării altui obiect. Cu alte cuvinte, ea
reprezintă un mecanism de reutilizare (partajare) a reprezentării şi a codului.
Moştenirea de interfaţă (subtyping) este un mecanism prin care un obiect poate fi
utilizat în locul altuia.
Este destul de uşor de confundat aceste concepte între ele deoarece majoritatea
limbajelor de programare OO nu le disting în mod explicit. De exemplu în C++
moştenire înseamnă atât moştenire de clasă, cât şi de interfaţă. O diferenţiere între
cele două s-ar putea face astfel:
moştenirea de interfaţă poate fi redată ca o derivare publică a unei clase
abstracte (o clasă care conţine funcţii-membru pur virtuale);
moştenirea de clasă poate fi modelată prin derivarea privată a unei clase.
Multe dintre şabloanele de proiectare se bazează pe această distincţie. De exemplu
obiectele dintr-un Chain of Responsibility trebuie să aibă un tip comun, fără însă a
avea şi implementarea comună. În cadrul şablonului Composite, Component
defineşte o interfaţă comună, în timp ce Composite defineşte o implementare comună.
Şabloanele Command, Observer, State şi Strategy sunt adesea implementate cu
ajutorul claselor abstracte.
5.4.2 Programarea prin interfeţe şi nu prin implementări
Moştenirea de clasă este în esenţă un mecanism care permite:
extinderea funcţionalităţii unei aplicaţii prin reutilizarea funcţionalităţii din
clasele părinte;
definirea rapidă a unui nou fel de obiect, în termenii unuia deja existent;
obţinerea unor noi implementări aproape fără efort, prin preluarea unei mari
părţi din ceea ce avem nevoie de la clase existente.
Reutilizarea implementării reprezintă doar o faţetă a conceptului de moştenire.
Posibilitatea de a defini familii de obiecte cu interfeţe identice (de obicei prin
moştenirea de la o clasă abstractă) este un alt aspect important, deoarece
polimorfismul depinde de el.
Clasele derivate dintr-o clasă abstractă vor partaja interfaţa acelei clase. Subclasele
vor adăuga sau vor redefini operaţii, dar nu vor ascunde operaţii ale clasei părinte. În
felul acesta toate subclasele vor putea răspunde la cererile corespunzătoare interfeţei
clasei abstracte părinte.
Există doua avantaje ale manipulării obiectelor prin intermediul interfeţelor definite în
clasele abstracte:
clienţii nu trebuie să ştie tipurile particulare ale obiectelor utilizate, atâta timp
cât obiectele respective sunt compatibile cu interfaţa pe care clienţii o
aşteaptă;
clienţii nu trebuie să ştie care sunt clasele care implementează obiectele
respective, ştiu doar despre clasele abstracte care definesc interfaţa.
Toate acestea reduc substanţial dependenţele dintre subsisteme, permitând formularea
următorului principiu al proiectării OO:
PROGRAMAŢI ÎN TERMENI DE INTERFEŢE, NU DE IMPLEMENTĂRI.
Nu declara variabile ca fiind instanţe ale unor clase concrete, mai degrabă angajează-
te să foloseşti o interfaţă definită printr-o clasă abstractă. Când este necesară
instanţierea unor clase concrete, se recomandă aplicarea şabloanelor creaţionale
(Abstract Factory, Builder, Factory Method, Prototype, Singleton) care permit
abstractizarea procesului de creare a obiectelor. În felul acesta se realizează o asociere
a unei interfeţe cu implementările ei transparent la momentul instanţierii.
5.5 Mecanisme ale reutilizării
Problema este cum obţinem un software flexibil şi reutilizabil folosind concepte ca
obiect, clasă, interfaţă, moştenire. Şabloanele constituie un răspuns.
Moştenirea şi compunerea obiectelor
Cele mai cunoscute tehnici de reutilizare a funcţionalităţii în cadrul sistemelor OO
sunt moştenirea de clasă şi asamblarea sau compunerea obiectelor (object
composition).
Moştenirea de clasă este cunoscută şi sub numele de reutilizare tip cutie albă (white-
box reuse) deoarece în majoritatea cazurilor o parte din starea internă a claselor
părinte este vizibilă în subclase.
Asamblarea obiectelor reprezintă o tehnică de obţinere a unei funcţionalităţi noi prin
asamblarea sau compunerea unor obiecte având interfeţe bine definite. Tehnica este
cunoscută sub numele de reutilizare tip cutie neagră (black-box reuse) deoarece
obiectele care se asamblează nu îşi cunosc unul altuia starea internă, ele apar unul faţă
de altul ca nişte cutii negre.
Ambele tehnici au avantaje şi dezavantaje. Moştenirea de clasă se caracterizează prin
următoarele (avantaje):
este definită static la compilare şi poate fi specificată direct, fiind suportată
explicit de limbajele de programare;
permite modificarea uşoară a implementării operaţiilor reutilizate, şi anume
într-o subclasă ce redefineşte o parte din operaţiile clasei părinte pot fi afectate
şi operaţii moştenite dacă acestea apelează operaţii redefinite. În secvenţa C++
de mai jos este ilustrată sintetic această situaţie:
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
}
Dezavantaje:
implementarea moştenită de la clasele părinte nu poate fi modificată în
momentul execuţiei;
cel mai adesea clasele părinte definesc cel puţin parţial reprezentarea fizică a
subclaselor lor, deci subclasele au acces la detalii ale implementării
superclaselor. De aceea se mai spune că moştenirea de clasă încalcă principiile
încapsulării;
modificările aduse implementării unei superclase vor forţa subclasele să se
modifice şi ele. Dependenţele de implementare pot cauza probleme atunci
când se încearcă reutilizarea subclaselor: dacă anumite aspecte ale
implementării moştenite nu corespund necesităţilor aplicaţiei clasa părinte
trebuie rescrisă sau înlocuită. Această dependenţă limitează flexibilitatea şi, în
ultimă instanţă reutilizarea. O soluţie în acest caz ar fi aplicarea moştenirii de
la clase abstracte, deoarece ele includ implementare în mică măsură.
Compunerea obiectelor se caracterizează prin:
se defineste în mod dinamic, la execuţie, prin faptul că anumite obiecte
primesc referinţe ale altor obiecte;
necesită ca obiectele să-şi respecte unul altuia interfaţa, ceea ce presupune că
interfeţele să fie proiectate astfel încât să nu împiedice utilizarea unui obiect în
combinaţie cu mai multe tipuri de obiecte. Deoarece obiectele sunt accesate
doar prin intermediul interfeţelor nu este încălcat principiul încapsulării. În
decursul execuţiei orice obiect poate fi înlocuit cu altul, atâta timp cât
obiectele respective au acelaşi tip. În plus, datorită faptului că şi
implementarea unui obiect este scrisă tot în termenii interfeţelor altor obiecte,
dependenţele de implementare vor fi substanţial reduse;
prin compunerea obiectelor se obţine urmatorul efect asupra unui proiect:
clasele sunt încapsulate şi focalizate asupra câte unui singur obiectiv, ceea ce
face ca ele, ca şi ierarhiile lor, să aibă dimensiuni mici şi să fie mai uşor de
gestionat. Un proiect bazat pe compunerea obiectelor se caracterizează printr-
un număr mai mare de obiecte şi un număr mai mic de clase, iar comportarea
sistemului va depinde de relaţiile dintre obiecte, în loc să fie definită într-o
clasă.
Toate aceste aspecte conduc spre formularea celui de-al doilea principiu al proiectării
OO:
PREFERAŢI COMPUNEREA OBIECTELOR MOŞTENIRII DE CLASĂ.
În mod ideal nu ar trebui create noi componente pentru a realiza reutilizarea. Pentru
obţinerea funcţionalităţii dorite trebuie doar asamblate componentele prin intermediul
compoziţiei obiectelor existente. În practică însă aceasta nu se poate realiza în
totalitate deoarece setul de componente disponibile nu este niciodată destul de bogat.
Reutilizarea prin moştenire este mai uşor de folosit pentru a face componente noi în
comparaţie cu compunerea componentelor deja existente. De aceea moştenirea şi
compunerea obiectelor sunt folosite împreună.
Experienţa arată că adesea proiectanţii folosesc moştenirea în mod abuziv. De aceea
se recomandă studiul şi aplicarea şabloanelor de proiectare, acestea bazându-se foarte
mult pe compunerea obiectelor.
Delegarea
Reprezintă o cale de aplicare a principiului compunerii obiectelor. Într-o relaţie de
delegare două obiecte sunt implicate în rezolvarea unei cereri, şi anume: obiectul care
receptează mesajul – delegatorul - deleagă execuţia operaţiei corespunzătoare unui alt
obiect - delegat. Acest lucru este oarecum similar cu situaţia în care subclasele
pasează sarcina execuţiei unor operaţii claselor părinte (este vorba despre operaţiile
moştenite şi neredefinite). Dar, în timp ce clasa părinte a unei subclase rămâne aceeaşi
pe toată durata execuţiei, în cazul delegării obiectele delegat pot fi schimbate, cu
condiţia să aibă aceeaşi interfaţă.
Delegarea este considerată ca un şablon de proiectare fundamental, pe ea bazându-se
foarte multe din celelalte şabloane (de exemplu State, Visitor, Strategy, Mediator,
Chain of Responsibility, Bridge).
Principalul avantaj al delegării este că face posibil foarte uşor să se compună
comportari în timpul execuţiei, inclusiv să se schimbe dinamic această compunere.
Dezavantajul, pe care îl au şi alte tehnici ce fac software-ul flexibil, este că software-
ul dinamic, parametrizat, este mai greu de înţeles decât software-ul static. În plus
există şi penalităţi în timpul execuţiei.
Moştenirea şi tipurile parametrizate
Tipurile parametrizate reprezintă o altă tehnică de reutilizare a funcţionalităţii care nu
este strict legată de modelul orientat pe obiecte. Ele permit definirea de către
utilizatori a unui tip nou fără a specifica tipurile pe care acesta le foloseşte. Aceste
tipuri se vor furniza ca şi parametrii unde se foloseşte acest tip parametrizat. De
exemplu un tip Lista poate fi parametrizat prin tipul elementelor conţinute. Acesta
poate fi întreg, şir de caractere, etc.
Printre limbajele care suportă această tehnică se numără Ada, Eiffel (prin tipurile
generice) şi C++ (prin template-uri).
Tipurile parametrizate ne oferă a treia cale pentru a compune comportarea în
sistemele OO. Există diferenţe importante între aceste trei tehnici:
compunerea obiectelor permite modificarea în timpul execuţiei a
comportamentului, dar presupune indirectare şi, ca urmare, poate fi mai puţin
eficientă;
moştenirea oferă posibilitatea de a utiliza implementări deja existente ale unor
operaţii, dar şi de a redefini în subclase operaţiile respective;
tipurile parametrizate permit modificarea tipurilor pe care o clasă le
utilizează, dar, la fel ca şi moştenirea, sunt precizate la compilare şi nu mai pot
fi modificate la execuţie.
5.6 Structuri stabilite la compilare şi structuri create la execuţie
Structura unui program OO aflat în execuţie aminteşte foarte puţin cu structura
codului. Acesta din urmă este îngheţat în momentul compilării şi constă dintr-un
ansamblu de clase aflate în relaţii fixe de moştenire. Structura la execuţie constă dintr-
o reţea de obiecte aflate în continuă schimbare şi comunicare. De fapt cele două tipuri
de structuri sunt aproape independente intre ele.
Exemplificăm pe deosebirea dintre relaţiile de agregare şi asociere (sau cunoaştere -
acquaintance) şi cum se manifestă acestea în timpul compilării şi execuţiei. Agregarea
presupune ca un anumit obiect posedă sau este responsabil faţă de un alt obiect. În
general spunem despre un obiect că are sau este parte dintr-un alt obiect. Agregarea
implică faptul că un obiect agregat şi proprietarul lui au durata de viaţă comună.
Asocierea, numită şi relaţie de utilizare (de tip "using"), presupune că un obiect pur şi
simplu ştie de existenţa altui obiect. Cele două obiecte asociate pot cere operaţii unul
altuia dar nu sunt responsabili unul faţă de altul. Asocierea este o relaţie mai slabă
decât agregarea şi sugerează o cuplare mai slabă între obiecte.
Cele două tipuri de relaţii pot fi uşor confundate din cauză că pot fi implementate în
mod asemănător. În C++ agregarea se poate implementa prin definirea de membrii
variabilă ce sunt instanţe adevărate dar este mai folosită practica să o definim prin
pointeri sau referinţe la instanţe. Asocierea este implementată şi ea prin pointeri şi
referinţe.
De fapt agregarea şi asocierea sunt determinate mai mult de intenţia proiectantului
decât de mecanismele din limbaj. Distincţia între ele este dificil de observat în codul
sursă. Agregările apar în număr mai mic, dar au un caracter mai stabil în timp.
Asocierile se fac şi se refac mai frecvent, uneori stabilindu-se doar pe durata unei
operaţii. Asocierile au un caracter mai dinamic, ceea ce le face greu de depistat în
codul sursă.
Multe dintre şabloanele de proiectare, mai ales cele care au domeniul de aplicare la
nivel de obiect, captează distincţia dintre structurile stabilite la compilare şi cele de la
execuţie, în sensul că un specialist care cunoaşte şabloanele de proiectare poate
detecta mai uşor în codul sursă structurile ce se vor crea la execuţie.
6. Tema
1. Pornind de la introducerea în şabloane de proiectare, daţi exemple de aplicare a
următoarelor relaţii:
agregare
asociere
moştenire de clasă
moştenire de interfaţă
Faceţi o apreciere în termeni de avantaje, dezavantaje şi flexibilitate pentru aceste
relaţii.
2. Sa se defineasca o ierarhie de clase pentru a modela mai multe tipuri de masini cu
motorizarile aferente. Masinile se impart in 2 categorii: automobile si camioane,
fiecare din acestea putand avea un motor Diesel sau pe baza de benzina.
Fiecare masina va avea o marca, o culoare si un numar de kilometrii parcursi pana in
prezent. Motorul are o marca si o serie unica.
Printr-un meniu se va permite realizarea urmatoarelor operatii:
- adaugare masina
- stergere masina
- afisarea masinilor
- schimbare tip motor masina selectata
- actualizare nr. km parcursi pentru masina specificata
Indicatie:
Se are in vedere construirea urmatoarei ierarhii de clase:
- clasa abstracta engine
- clasele derivate din engine -> dieselEngine si gasEngine
- clasa abstracta car
- clasele derivate din car -> automobile si truck
Intre engine si car exista e relatie de agregare.
Sa se discute motivatia ce sta la baza contruirii acestei ierarhii