Sunteți pe pagina 1din 214

Universitatea Transilvania din Braov Facultatea de Matematic i Informatic Catedra de Informatic aplicat

Programare obiect orientat 2

Dorin Bocu

...O parte din eforturile tiinei calculatoarelor sunt dedicate mbuntirii permanente a paradigmelor de modelare. Prin istoric i prin realizrile de ultim or, ingineria softului nu face dect s confirme aceast aseriune. Modelarea orientat pe obiecte este un exemplu remarcabil de instrument, gndit pentru a fi utilizat n realizarea de sisteme soft, competitive din punct de vedere al preului i al calitii. Programarea orientat pe obiecte ngduie fanilor ei s verifice, n practic, fora unui stil de a modela, a crui capacitate de a mapa domeniul problemei peste domeniul soluiei este cu mult superioar altor paradigme.

Cuvnt nainte al autorului Au trecut ani buni de cnd lumea a nceput s ntrebuineze, n vorbire i n alte contexte, sintagma programare obiect orientat sau, ceva mai aproape de spiritul limbii romne, programare orientat pe obiecte. Pregtit i anunat de numeroasele controverse pe marginea preamultelor slbiciuni ale vechilor paradigme de programare,

orientarea pe obiecte s-a instalat confortabil de-a lungul ntregului proces de dezvoltare a unui sistem soft, devenind n zilele noastre religia care guverneaz att etapa de modelare a unui sistem soft ct i etapa de implementare. Au aprut limbaje, precum i medii de programare i dezvoltare a sistemelor soft, a cror arhitectur este esenial tributar ideii de orientare pe obiecte (Java, C++, C#, Object Pascal cteva exemple mai cunoscute de limbaje, Delphi, C-Builder, Visual C++ - cteva exemple mai cunoscute de medii de programare, Rational Rose, ObjectiF dou dintre mediile de dezvoltare cu bun rspndire n lumea ingineriei softului). Este clar, orientarea pe obiecte nu este doar o mod n programare, ci o modalitate, fr rival, pentru moment, de a dezvolta sisteme soft. Pentru informaticienii a cror practic se reduce la apropierea ct mai rapid de tastatur pentru a rezolva o problem, orientarea pe obiecte este o ncercare care tulbur minile i ntrzie rezolvarea problemei. Seriile de studeni care mi-au trecut prin mn mi-au ntrit convingerea c nsuirea spiritului orientrii pe obiecte este o problem destul de dificil, deoarece, aproape tot ceea ce este reclamat de obnuinele omului n materie de nvare, este dificil de operaionalizat cnd este vorba de nsuirea acestui spirit. Mult mai apsat dect n alte paradigme, n orientarea pe obiecte, specialistul trebuie s acorde atenia cuvenit elaborrii soluiei nainte de a o implementa. Metodele cele mai rspndite de nvare a limbajelor de programare se bazeaz pe formula: Prin aplicaii, spre descoperirea subtilitilor sintactice, semantice i pragmatice programare. ale unui limbaj de

Cititorul atent a neles faptul c nvarea unui limbaj de programare este un exerciiu de voin care presupune parcurgerea a trei etaje: nvarea stereotipurilor sintactice fundamentale ale limbajului (sintaxa limbajului) Descoperirea unui numr ct mai mare de semantici primitive, care pot fi nvelite cu sintaxa limbajului (semantica limbajului) Formarea unor deprinderi de utilizare eficient a limbajului, n funcie de natura i complexitatea problemei, pentru rezolvarea respectivei probleme (pragmatica limbajului).

Ideea cluzitoare a acestei cri este de a invita cititorul s nvee singur sintaxa orientrii pe obiecte, ncercnd s l ajute, ndeosebi n efortul de deconspirare a semanticii i pragmaticii orientrii pe obiecte. Trimiterile de natur sintactic vor fi raportate la limbajele Java i C++, suficient de nrudite din punct de vedere sintactic pentru a nu pune la grea ncercare disponibilitatea la effort de rutin a cititorului. Ceva mai rar, se vor face trimiteri i la Object Pascal, un limbaj cu o istorie asemntoare limbajului C++, din punct de vedere al modului n care permite mbinarea trecutului i a prezentului, n materie de suport pentru paradigmele de modelare. Trebuie s mrturisesc, totodat, faptul c, n viziunea acestui cri, fiecare cititor este o instan cognitiv activ, capabil de effort permanent de abstractizare, singura modalitate de a ajunge la o nelegere superioar a esenei unei paradigme de modelare i, n particular, de programare. Atitudinea de spectator, cu instinct de conservare puternic, la derularea ideilor din aceast carte, este absolut contraproductiv pentru atingerea obiectivului fundamental: nvarea ct mai multor elemente - suport, eseniale pentru modelarea / programarea orientat pe obiecte a soluiei unei probleme.

Ce se va ntmpla cu cei care nu se vor putea mpca cu ideea de a se dezmori voluntar, iat o problem n legtur cu care prefer s spun doar att: nu peste mult timp vor descoperi c au mbtrnit nainte de vreme, fr a fi neles mare lucru despre plcerea de a uita de scurgerea timpului, realiznd lucruri care s uimeasc, deopotriv, pe alii i pe propriul lor creator. Referindu-m la studenii pentru care, n principal, am scris aceast carte, trebuie s spun c, n viziunea mea, ideea de student se confund cu imaginea unui individ care are apeten pentru studiu. Dac, n facultate, se mai studiaz i discipline care, unora li se par, nefolositioare, datoria studentului este s fac efortul de a se identifica cu ideile fundamentale ale unui numr ct mai mare de discipline, socotite de el folositoare. Dac, n facultate, studenii se mai ntlnesac i cu profesori ale cror cutri nu sunt nc suficient de bine sistematizate, acesta nu este un motiv de a renuna la dorina de a cunoate. De la natur, studentul poate fi asimilat cu un obiect care are toate capabilitile necesare pentru a parcurge drumul dificil, dar pasionant, al cunoaterii elementelor fundamentale, n diferite ramuri ale matematicii i tiinei calculatoarelor. A aminti, tuturor celor care nu au realizat nc, faptul c omul are nevoie de tiin pentru a nelege o parte din realitatea necunoscut ( rezult c tiina are funcie explicativ), pentru a modela comportamentul unor fenomene i procese din realitatea obiectiv, n vederea optimizrii dinamicii lor (rezult c tiina are funcie modelatoare), pentru a mbogi realitatea obiectiv cu obiecte artificiale (rezult c tiina are funcie demiurgic). Sper ca cititorul s neleag bine rolul activ pe care trebuie s i-l asume n deconspirarea, prin studiu individual i exerciii perseverente, a etajelor sintactice eseniale programrii n C++ i Java.

Capitolul 1
Cum se explic permanenta nevoie de paradigme noi n ingineria softului Ce nelegem prin orientarea pe obiecte

1.1 Cum se explic permanenta nevoie de paradigme noi n ingineria softului?


Binefacere sau blestem, omenirea este, asemenea ntregului univers, ntr-o continu deplasare spre alte repere ontologice i gnoseologice. Avea dreptate Heraclit din Efes cnd, folosind cuvinte meteugit alese, constata c singura certitudine a universului pare a fi devenirea. Dac, acum peste 2500 de ani n urm, devenirea ocupa o poziie central n modul de gndire al lui Heraclit, n zilele noastre devenirea s-a transformat n izvor nesecat i cauz a tuturor transformrilor importante pe care le suport universul cunoscut omului. S-ar prea c nimic din ceea ce face omul nu dureaz. Acest fapt este, dup ascuimea simurilor noastre, n opoziie cu ceea ce Natura sau Marele Creator fac. Omul aspir la eternitate luptnd cu efemerul inerent al creaiilor lui. Marele Creator este eternitatea nsi. Las pe seama filozofilor continuarea efortului de preamrire sau punere la index a rolului pe care l joac devenirea n viaa omului i, de ce nu, n univers. Caracterul obiectiv al devenirii poate explica, n genere i nevoia de paradigme 1 noi n ingineria softului i, n particular n programare. * Permindu-mi o scurt digresiune, ingineria softului este o ramur a tiinei calculatoarelor care se ocup, la urma urmei, de studierea unei probleme extrem de delicate: Dat o problem oarecare, ce trebuie fcut pentru a o rezolva cu ajutorul calculatorului? Confruntai de-a lungul timpului, cu diferite tipuri de probleme, specialitii n ingineria softului au fcut o descoperire banal: Efortul depus pentru a rezolva o problem cu ajutorul calculatorului este direct proporional cu complexitatea problemei. Odat fcut aceast descoperire iese la iveal alt ntrebare: Cum definim complexitatea unei probleme? Atenie, cititorule, este vorba de complexitatea unei probleme nu de complexitatea soluiei algoritmice a unei probleme. Complexitatea unei probleme este o caracteristic intrinsec a enunului asumat al acesteia. Complexitatea soluiei algoritmice a unei probleme este o caracteristic intrinsec a modului n care o anumit instan cognitiv (de pild un student din anul II) obine respectiva soluiie algoritmic. Este, sper, ct se poate de clar faptul c cele dou tipuri de complexitate cuantific caracteristicile structurale ale unor colecii de obiecte, diferite din punct de vedere al sferei de cuprindere i al coninutului.

Este cazul s spunem c, prin paradigm se nelege, fr a exagera cu explicaiile, un mod de abordare a problemelor dintr-un anumit domeniu, evident, cu scopul de a rezolva aceste probleme. Poate c limba romn nu avea neaprat nevoie de acest cuvnt, dar insistena cu care este utilizat n alte pri poate fi considerat o scuz pentru utilizarea lui n aceast carte.

Cei care au parcurs unul sau mai multe cursuri de iniiere n Algoritmic i Programare i amintesc, probabil, nelesul pe care l are complexitatea unui algoritm. Formalizarea matematic a complexitii algoritmilor este un prilej minunat de a ilustra fora de analiz a matematicii de puterea continuului, aplicat universului algoritmilor, al cror comportament este eminamente discret. Este corect s evideniem, n acest punct, utilitatea teoretic i practic (ndeosebi n cazul aplicaiilor critice relativ la complexitatea algoritmilor folosii) a eforturilor de clasificare a algoritmilor secveniali i paraleli, n funcie de complexitatea lor Acest gen de afirmaii sunt prezentate n toat cuprinderea i adncimea lor n cri fundamentale pentru nvrea programrii calculatoarelor2. Ingineria softului (IS) ia act de aceast realitate i, n replic, i concentreaz atenia asupra complexitii problemelor. O ncercare de a evidenia elementele cu ajutorul crora putem descrie complexitatea unei probleme n ingineria softului ne conduce la seria de observaii de mai jos. Rezolvarea unei probleme cu ajutorul calculatorului nseamn, de cele mai multe ori, simularea / asistarea de ctre calculator a unor activiti, desfurate de ctre sisteme de alt natur. Aadar, aproape invariabil, ne aflm n faa unor dificulti care rezult din caracterul instabil al relaiei dintre model i sistemul modelat. Neglijarea acestui aspect simplific efortul de realizare, n cele din urm, a unui sistem soft, dar preul pltit este exprimat, sintetic, astfel: uzur moral rapid i posibiliti de adaptare reduse. Se poate lupta cu mijloace raionale mpotriva caracterului instabil al relaiei dintre model i sistemul modelat? Rspunsul este: da, se poate lupta, dac specialistul n IS este dispus s anticipeze impactul posibilelor schimbri ale sistemului modelat asupra soluiei (modelului). Anticipnd schimbrile sistemului modelat, simplificm efortul de adaptare a soluiei la aceste schimbri. Modelul care st la baza soluiei unei probleme abstractizeaz viziunea specialistului sau grupului de specialiti n IS, asupra sistemului modelat. Semn al posibilitilor noastre limitate de a cunoate, orice viziune nu poate dect s aproximeze comportamentul sistemului modelat. Chiar i n aceste condiii, de la a aproxima defectuos pn la a aproxima inspirat este nu doar un drum lung ci i plin de diverse tipuri de ncercri. Exemple de astfel de ncercri: specificarea complet a cerinelor funcionale; acordarea ateniei cuvenite cerinelor non-funcionale; alegerea inspirat a modalitilor de armonizare a cerinelor contradictorii; alegerea inspirat a paradigmei de modelare, etc. Trecerea, cu succes, peste dificultile schiate mai sus este realizat n condiii ergonomice dac managementul acestor dificulti (plus nenumrate altele) este tratat cu maximum de seriozitate i competen.

ncercnd s form o concluzie, complexitatea unei probleme n IS este influenat de: 1. Stabilitatea relaiei model-sistem modelat; lipsa de stabilitate n aceast relaie, asumat contient, induce creterea complexitii problemei de rezolvat.

Knuth, Cormen

2. 3.

Complexitatea structurii sistemului modelat (creterea acesteia induce, de asemenea, creterea complexitii problemei de rezolvat). Desfurarea n condiii de eficien a procesului de rezolvare a unei probleme, adaug un plus de complexitate oricrei probleme. Neasumndu-se acest plus de complexitate, putem uor compromite procesul de realizare a unui sistem soft (=soluia executabil pe un calculator real a unei probleme date). Un management bun are de rezolvat probleme de procurare i alocare optim a resurselor, comunicare ntre partenerii de proiect (=specialitii n IS, beneficiarii, utilizatorii), lansare cu succes pe pia (dac este cazul), etc. Nu a sftui pe nici un specialist n IS s considere normal o afirmaie de genul: Dac cele spuse la punctul 2 sunt tratate cu maximum de seriozitate i competen, 1 i 3 nu mai nseamn mare lucru. ndeosebi n cazul proiectelor mari, s-a dovedit, de nenumrate ori, neadevrul unei astfel de afirmaii.

n IS, complexitatea problemelor nu este un accident; mai mult, putem spune c este legitim s ne ateptm la elemente de complexitate chiar i acolo unde s-ar prea c acestea sunt lips. Dac mai amintim cititorului observaia tendenioas, potrivit creia complexitatea de calitate se ntemeiaz pe simplitate, acesta nelege mai bine insistena cu care prezint implicaiile complexitii unei probleme asupra calitii soluiei acestei probleme. Calitate? Complexitate3? Complexitate de calitate? Multe se mai pot spune. Specialistul n IS dorete s tie cteva lucruri simple: Care sunt primejdiile? Care sunt exigenele? Cum se poate evita un eec?, etc.

Astfel de ntrebri, ne vom pune, ntr-o form sau alta i n aceast carte de iniiere n programarea orientat pe obiecte i le putei gsi reluate n crile Iniiere n ingineria sistemelor soft i Iniiere n modelarea obiect orientat a sistemelor soft utiliznd UML, scrise de D. Bocu i aprute la Editura Albastr. Nevoia presant de a cuta, permanent, rspunsuri noi la astfel de ntrebri, justific avalana de nouti care caracterizeaz i lumea limbajelor de modelare, specifice ingineriei softului.

1.2 Ce se nelege prin orientarea pe obiecte?


S-au scris destule cri pe tema orientrii spre obiecte. i mai multe sunt, probabil, articolele din revistele de specialitate. Unele, foarte convingtoare. Altele mai puin. Nu cred c voi umple, brusc, golul care mai exist, nc, n limba romn, scriind aceast carte. Dar voi ncerca s fac neles modul de gndire al unui specialist n IS, care vede realitatea informaional n termeni specifici orientrii spre obiecte. Orice sistem informaional4 este mai uor de neles dac avem elemente suficiente referitoare la datele vehiculate n sistem, procedeele de prelucrare (vehiculare) a datelor din sistem, interfeele sistemului cu mediul n care acesta opereaz. Fiecare dintre elementele specificate mai sus are reguli proprii de organizare i fiinare. n plus,
3

Orice exerciiu de modelare urmrete, indiferent de acuitatea mijloacelor de investigaie, un anumit mod de organizare a complexitii sistemului modelat. 4 Ca i realitatea informaional, sistemul informaional desemneaz un ansamblu de resurse care optimizeaz fluxurile informaionale dintr-un sistem gazd. Pentru o documentare mai atent invit cititorul s rsfoiasc atent o carte bun de ingineria softului.

mai exist i relaiile strnse dintre aceste elemente. De fapt, ntreaga istorie a IS se nvrte n jurul ecuaiei prezentat n Figura 1.

<Soluia unei probleme>

= <Organizarea datelor> + <Organizarea prelucrrilor> + <Optimizarea interfeelor> Figura 1. Ecuaia general a soluiei unei probleme n IS.

Ecuaia de mai sus a primit de-a lungul timpului numeroase rezolvri. * nainte de a descrie tipurile fundamentale de rezolvri, voi efectua o scurt trecere n revist a vocabularului IS, esenial n comunicarea dintre partenerii unui proiect de dezvoltare a unui sistem soft. Prin urmare, o firm productoare de soft produce i livreaz produse i servicii care se adreseaz nevoilor i cerinelor clienilor. Cerinele clienilor constitue un exemplu de problem cu care trebuie s se confrunte echipa de dezvoltare. Produsele i serviciile care satisfac aceste cerine pot fi considerate ca fiind soluii. Pentru a livra soluii valide (= de calitate, la pre de cost redus i n timp util) firmele trebuie s fac permanent achiziie, comunicare (partajare) i utilizare de cunotine specifice referitoare la domeniul problemei de rezolvat. Pentru a discrimina i comunica eficient informaiile necesare pentru rezolvarea unei probleme, firmele folosesc tehnologii adecvate. O tehnologie este, n general vorbind, un instrument pe care membrii echipei de dezvoltare trebuie s nvee s-o foloseasc la ntregul ei potenial. Aadar, problema de rezolvat, la care se adaug necesitatea de a nva modul de utilizare a tehnologiilor necesare n rezolvarea problemei, ne permit o imagine, nc optimist, asupra complexitii efortului de realizare a unui sistem soft. Amplificarea acestei complexiti este motivul pentru care ntregul efort de realizare a unui sistem soft este structurat sub forma unui proiect. n cadrul unui proiect se desfoar, dup reguli precise, toate activitile necesare pentru a asigura succesul efortului de realizare a unui sistem soft. Dou dintre dimensiunile eseniale pentru succesul unui proiect sunt limbajul de modelare folosit i tipul de proces utilizat pentru a materializa fora limbajului de modelare. Despre toate acestea, mai multe aspecte n [Iniiere n ingineria sistemelor soft]. * Revenind la ntrebarea de baz a acestui paragraf, s observm c, nc de la apariia primelor calculatoare, a aprut o nou dilem n faa omenirii: Cum putem nva calculatoarele s ne rezolve n mod optim problemele? La nceput, cnd calculatoarele erau folosite exclusiv pentru rezolvarea unor probleme cu carater tiinific, soluia era programarea n cod main. Dificultile de baz n rezolvare acestor tipuri de probleme erau dou :elaborarea

modelelor matematice i transpunerea lor n cod main. Mai ales programarea n cod main, era un gen de exerciiu la care nu se ngrmdeau dect indivizi care manifestau un interes ieit din comun pentru sistemele electronice de calcul din acea generaie. Apariia limbajelor de asamblare a generat o relaxare a cortinei de fier instalat de codul main ntre calculatoare i marea mas a curioilor. O relaxare asemntoare s-a produs i n ceea ce privete tipurile de probleme abordabile cu ajutorul calculatoarelor. Era momentul n care intrau pe scen aplicaiile de gestiune, datorit apariiei memoriilor externe. Locul datelor simple ncepe s fie luat de date structurate i din ce n ce mai voluminoase. Presiunea exercitat de cererea crescnd de aplicaii de gestiune a impus trecerea la limbajele de nivel nalt i mediu. n paralel, cu deosebire n faa proiectelor uriae (controlul traficului aerian, gestiunea tranzaciilor unor bnci, informatizarea tot mai multor activiti ale intreprinderilor industriale) apare nevoia disciplinrii procesului de derulare a acestor proiecte din faza de specificare a cerinelor, trecnd prin faza de analiz, continund cu proiectarea soluiei i terminnd (grosier vorbind) cu programarea. De unde aceast nevoie de disciplinare? Foarte simplu, complexitatea problemelor (parc am mai auzit undeva despre acest lucru) nu mai putea fi stpnit lucrnd fr metod. Astfel au aprut, de-a lungul timpului, tot felul de mode n ceea ce privete abstractizarea soluiei i nu numai. Managementul unui proiect trebuie s aib, permanent, n vizor, cel puin, asigurarea limbajului de modelare adecvat i a modelului de dezvoltare optim5. De la moda programelor-mamut (singura posibil n vremea programrii n cod main) s-a ajuns treptat la nevoia modularizrii (= descompunerea problemei iniiale n subprobleme, eventual aplicarea repetat a acestui procedeu, obinndu-se nite piese ale soluiei numite module, care, asamblate, compuneau, n sfrit soluia). ntrebarea care a furnicat, ani la rnd, creierele teoreticienilor i practicienilor, deopotriv, era: Care sunt criteriile dup care se face modularizarea unei soluii? Exist o serie de factori externi ai calitii unui sistem soft care in sub presiune tendina de a modulariza de amorul artei, precum: corectitudinea, robusteea, reutilizabilitatea, portabilitatea, etc. Exist i o serie de factori interni ai calitii unui sistem soft, care preseaz, n egal msur, precum: structurarea soluiei, claritatea algoritmilor, calitatea arhitecturii, etc. Meninerea n echilibru a acestor factori de presiune este o sarcin extrem de dificil. n cele din urm, cercettorii au ajuns la concluzia c, din punct de vedere al celui care lucreaz, materializarea acestor factori la parametri satisfctori sau marginalizarea lor, depind de decizia luat n ceea ce privete relaia dintre date i prelucrri n procesul de modularizare a soluiei unei probleme. Istoria ne arat c au existat teoreticieni i practicieni ncredinati c singura metod de modularizare valid este modularizarea dirijat de date (altfel spus, nainte de a te ocupa de organizarea prelucrrilor, rezolv, n mod optim, problema organizrii datelor; din schema de organizare a acestora se va deriva i schema de structurare a prelucrrilor). Tot istoria ne arat c au existat i teoreticieni i practicieni ncredinati c singura metod de modularizare valid este modularizarea dirijat de prelucrri (altfel spus, ocup-te, mai nti, de organizarea prelucrrilor i, mai apoi, f rost de datele necesare i organizeaz-le conform cerinelor prelucrrilor). Curnd s-a observat c dihotomia date-prelucrri nu este benefic sub nici o form. A existat o soluie de compromis (modularizarea dirijat de fluxurile de date), care s-a dovedit n timp nesatisfctoare n numeroase situaii din realitate. Din punct de vedere al criticilor fundamentale care se pot formula la adresa acestor abordri esena este urmtoarea: Dac soluia unei probleme nseamn ansamblul date5

Mai multe detalii n aceast privin n cartea Iniiere n ingineria sistemelor soft, D. Bocu, Editura Albastr, Cluj-Napoca, 2002

prelucrri, fiecare component avnd o existen relativ autonom i reguli proprii de organizare, atunci avem situaia din Figura 2.

DATE Problem (activitate-sistem real al crui comportament trebuie modelat)

PRELUCRRI

Figura 2. Perspectiva clasic asupra relaiei dintre date i prelucrri n structura unei soluii Esenial n mesajul transmis de Figura 2 este faptul c datele sunt sficient de izolate de prelucrri pentru ca o modificare n structura unei componente s dea uor peste cap structura celeilalte componente. Conform paradigmelor caracterizate n Figura 2, era posibil o caracterizare de tipul celei prezentate pentru problema de mai jos. Problema 1: S se realizeze un sistem soft care simuleaz deplasarea unui om pe o suprafa plan. Dac a fi un partizan al modularizrii dirijate de date, mai nti mi-a pune urmtoarele ntrebri: care sunt atributele informaionale care caracterizeaz un om (stilizat convenabil, s spunem)?. Cum se caracterizeaz un plan? Cum se memoreaz intern i extern datele despre aceste obiecte? Dup ce am distilat satisfctor lumea datelor, ncep s m ocup i de lumea prelucrrilor necesare n jurul acestor date. Obin, inclusiv n viziunea limbajelor de programare suport, dou lumi, care interfer, dar au reguli de organizare i reprezentare n memorie distincte. Mai trist, legtura dintre date i prelucrri este o problem care se gestioneaz prin programare, ca i cnd nu ar fi suficiente problemele celelalte. Ce ne facem, ns, dac apare necesitatea simulrii deplasrii unei vieuitoare n genere, pe o suprafa plan? Pentru fiecare vieuitoare n parte iau travaliul de la capt? Posibil, dar total contraindicat din multe puncte de vedere( pre de cost, extensibilitate, ncadrare n termenele de execuie, etc.). Este limpede, reformulat ca mai sus, enunul problemei ne pune n faa sarcinii de a modela comportamentul unor obiecte, heterogene ca tip, dar ntre care exist afiniti, att de natur informaional ct i comportamental. Astfel apare, ceea ce n modelarea obiect orientat se numete problema gestiunii similaritilor unor colecii de obiecte. Gestiunea corect a similaritilor unei colecii de obiecte, heterogene din punct de vedere al tipului definitor, se realizeaz desfurnd n paralel efort de clasificare i, acolo unde este cazul, ierarhizare cu ajutorul operaiilor de generalizare / specializare. Cum arat lumea privit din aceast perspectiv? Simplificnd intenionat, cam ca n Figura 3. Nu voi da, nc, nici o definiie, dar voi face o serie de observaii pe care le voi aprofunda n capitolele urmtoare.

1.

Se insinueaz faptul c soluia orientat pe obiecte a unei probleme se obine n urma unui demers de organizare a unor obiecte, care au att proprieti informaionale ct i comportament (se manifest, astfel, ntrun cadru absolut natural, un mai vechi principiu utilizat n modelare i anume principiul ncapsulrii datelor i prelucrrilor. ncapsulare facem i n Pascal, cnd scriem unit-uri care furnizeaz anumite servicii unor categorii bine definite de utilizatori. Aceste unit-uri au interfa i implementare. Soluia orientat pe obiecte a problemei

Problem (activitatesistem real al crui comportament trebuie modelat)

C1

C11

C12

C111 C121 C122

Figura 3. Perspectiva orientat pe obiecte a soluiei unei probleme Manifestarea principiului ncapsulrii, n acest cadru, nsemna ascunderea detaliilor de implementare fa de utilizatori, prin publicarea unei interfee stabile. O interfa este stabil dac utilizatorul ei nu sesizeaz eventualele modificri aduse implementrii interfeei. 2. n sfrit, aplicnd riguros principiul ncapsulrii, putem defini clase de obiecte care au o importan esenial pentru maparea domeniului problemei peste domeniul soluiei. Exist, ns, numeroase alte raiuni pentru care principiul ncapsulrii se aplic conjugat cu alt principiu important n modelarea orientat pe obiecte: principiul motenirii. Aplicarea acestui principiu ne ajut s raionalizm redundanele care apar, n mod inerent, n procesul de elaborare a unei soluii n genere. Mai mult, principiul motenirii pregtete terenul pentru rezolvarea unor probleme interesante care in de polimorfism i genericitate. Exemplul de referin n aceast privin este Java.

Fr a mai insista prea mult, s desprindem concluzia care se impune evident la acest nivel de prezentare: Modelnd orientat pe obiecte, asigurm maximum de coresponden posibil ntre obiectele care populeaz sistemul modelat i obiectele care dau, practic, via soluiei. Lucru absolut remarcabil, deoarece principiul ncapsulrii (temelie a modularizrii de calitate n orientarea pe obiecte) introduce elemente de stabilitate deosebit a soluiei chiar i n situaia n care apara modificri n sfera domeniului problemei.

Prpastia dintre date i prelucrri este nlocuit de reguli precise de asociere a datelor i prelucrrilor, pentru a descrie tipuri de obiecte ntlnite n domeniul problemei i care sunt importante pentru economia de resurse a soluiei. Din aceast perspectiv privind lucrurile, este evident faptul c modelarea orientat pe obiecte este altceva dect modelarea clasic (indiferent de nuan). Desluirea acestui altceva, la nivel de sintax, semantic i pragmatic (prin raportare la un limbaj de programare) ne va preocupa n continuare. Cei care se grbesc s abordeze i specificul modelrii orientate pe obiecte, abstracie fcnd de limbajele de programare- suport pentru implementare, pot consulta lucrarea [Iniiere n modelarea obiect orientat utiliznd UML6].

Dorin Bocu, Editura Albastr, Cluj-Napoca, 2002

Capitolul 2
Concepte n programarea orientat pe obiecte Principii n programarea orientat pe obiecte

2.1 Concepte n programarea orientat pe obiecte


ncepnd cu acest capitol, orientarea pe obiecte va fi privit, nu de la nlimea preceptelor ingineriei softului, ci din perspectiva programrii Java i C++, ndeosebi. Subliniez, nc odat, marea provocare pentru un programator care ncearc fora unui limbaj n materie de obiect orientare nu este n sintax, semantica asociat diferitelor tipuri de enunuri sintactice sau stilul de codificare, ci nsuirea spiritului orientrii pe obiecte aa cum este el promovat de elementele suport ale limbajului.

De aceea, reamintesc cititorului contient faptul c va trebui s se ntrebuineze serios pentru a descifra, dac mai este cazul, oferta limbajului C++ n ceea ce privete: tipurile fundamentale de date (similariti remarcabile cu Java, dar i deosebiri, datorate n principal faptului c n C++ pointerii se manifest cu foarte mult vigoare n cele mai neateptate contexte), reprezentarea structurilor de prelucrare (din nou, similariti remarcabile ntre C++ i Java), operaiile I/O relative la consola sistem, din perspectiv C, precum i suportul C pentru lucrul cu fluxuri, dac se dorete acest lucru. Nu voi spune dect urmtoarele: C++ este un superset al limbajului C; compilatoarele de C++ sunt realizate astfel nct toate enunurile din C sunt acceptate, dar ele recunosc i o varietate mare de enunuri specifice modului de lucru n orientarea pe obiecte. Dup cum rezult din titlul acestui paragraf, n atenia mea se vor afla enunurile tipice programrii orientate pe obiecte n C++ sau Java. nainte de a ajunge la aceste enunuri, trebuie s facem primii pai n nvarea spiritului orientrii pe obiecte. Voi prezenta, n continuare conceptele fundamentale de care ne lovim frecvent cnd programm orientat pe obiecte. Spuneam n Capitolul 1 c, din perspectiv orientat pe obiecte, sistemul pe care l modelm va fi ntotdeauna abstractizat de o colecie de tipuri de obiecte, ntre care exist anumite relaii. S ne imaginm, de exemplu, c vrem s modelm lumea poligoanelor astfel nct s putem oferi suport pentru nvarea asistat de calculator a proprietilor poligoanelor. Exist o mare diversitate de poligoane. Chiar i cele care sunt de maxim interes din punct de vedere al predrii/nvrii n coal, sunt suficient de multe pentru a pune probleme de abordare a prezentrii proprietilor lor. i n acest caz, ca n oricare altul, la nceput avem n faa ochilor realitatea de modelat, care poate fi sau nu structurat dup legile ei naturale. Pentru un individ cu pregtire matematic adecvat este evident c obiectele din Figura 4 sunt clasificate aprioric. Eventual, putem spune c lipsesc unele tipuri de poligoane, pentru c inventarul fcut de noi n Figura 4 este incomplet.

Figura 4. Diferite tipuri de poligoane, aa cum se pot ntlni, n realitatea modelat, prin reprezentani Probleme noastr nu este de a stabili nite frontiere nuntrul crora s avem obiecte de acelai tip, ci de a spune care sunt obiectele care nu ne intereseaz. Nu se ntmpl, ntotdeauna, aa. Exist probleme n care efectiv trebuie s depunem eforturi pentru a clasifica obiectele.

Operaia de clasificare presupune identificarea unor categorii de obiecte, apelnd, simultan la omiterea unor detalii, socotite nesemnificative, pentru a obine efectul de similaritate n procesul de caracterizare a obiectelor. Dac n atenia noastr se afl problema clasificrii poligoanelor, atunci, dac n caracterizarea unui poligon reinem atribute precum: lista coordonatelor vrfurilor, definiia(), aria(), perimetrul(), atunci rezultatul clasificrii este o clas de obiecte pe care o putem numi clasa Poligon. Astfel c putem da definiia de mai jos. Definiia 1 Se numete clas o colecie de obiecte care partajeaz aceeai list de atribute informaionale i comportamentale. Prin urmare, primul concept important cu care ne ntlnim n programarea orientat pe obiecte este conceptul de clas. Rezolvarea orientat pe obiecte a unei probleme se bazeaz esenial pe abilitatea specialistului (n cazul nostru programatorul) de a descoperi care sunt clasele pe baza crora se poate construi soluia. Presupunnd c avem i noi aceast abilitate i, n acord cu criteriile proprii de clasificare (reflectate i n inventarul din Figura 4), obinem urmtoarea colecie de clase candidate la obinerea soluiei problemei noastre.

Clasa patrulaterelor

Clasa triunghiurilor

Clasa hexagoanelor

Figura 5. Clasele candidate la obinerea soluiei pentru problema modelrii poligoanelor Departe de mine ideea c am dat o soluie definitiv problemei clasificrii poligoanelor. Am prezentat, ns, o soluie tributar unei anumite viziuni. n conformitate cu aceast viziune, singurele poligoane care prezint interes pentru noi sunt triunghiurile, patrulaterele i romburile. Figura 5 ne atrage atenia, explicit, asupra diversitii tipologice

a patrulaterelor, fapt care evideniaz necesitatea recurgerii i la alt operator dect clasificarea pentru a gestiona aceast diversitate. Situaia este, oarecum asemntoare i n cazul triunghiurilor, dar nu am subliniat explicit acest lucru. Fcnd abstracie de aceste elemente, deocamdat, s revenim asupra problemei care ne intereseaz cel mai mult n acest moment: cum stabilim proprietile unei clase? Regulile de baz n stabilirea proprietilor unei clase sunt urmtoarele: Lista atributelor informaionale ale unei clase este, ntotdeauna, rezultatul unui compromis ntre necesitatea unui maximum de informaii despre obiectele clasei respective (informaii, care, n fond, caracterizeaz starea obiectelor din clasa respectiv) i necesitatea unui minimum de redundane acceptate. Obiceiul unor programatori de a supradimensiona lista atributelor unei clase pe motiv c mai bine s fie dect s le ducem lipsa, nu este un model de urmat, nici atunci cnd exist memorie cu carul. Odat specificate, atributele trebuie declarate ca fiind resurse private ale clasei, folosind sintaxa specific limbajului pentru ascunderea unei resurse. Dogma orientrii pe obiecte n legtur cu lista atributelor este c acestea sunt accesibile, diferitelor categorii de clieni, n setare ca i n consultare, prin intermediul unor metode speciale de setare(numite i modificatori) sau consultare(numite i selectori), crora li se mai adaug metode speciale implicate n crearea obiectelor unei clase (constructorii), respectiv, eliminarea acestor obiecte (destructorii). Evident, mai exist i alte tipuri uzuale de metode, precum iteratorii sau indexatorii, crora li se acord o atenie special n C#. Odat specificat lista atributelor informaionale se poate trece la specificarea listei operaiilor clasei, list care abstractizeaz comportamentul clasei. n procesul de specificare a comportamentului unei clase trebuie avute permanent n vedere cele dou dimensiuni ale comportamentului unei clase: comportamentul reclamat de gestiunea strii obiectelor (crearea lor, setarea valorilor atributelor, modificarea valorilor atributelor, consultarea valorilor atributelor, distrugerea obiectelor) precum i comportamentul reclamat de relaia clasei n cauz cu alte clase. Lista operaiilor unei clase, la nevoie, poate fi organizat, din punct de vedere al modului de acces la aceste operaii. Un singur lucru este general valabil n aceast privin: faptul c orice clas trebuie s afieze o list cu operaiile publice, care asigur accesul clienilor la serviciile oferite de clas. Lista acestor operaii se numete, n mod normal, interfaa clasei. Atenie, cititorule! Cnd specifici resursele unei clase, eti preocupat s spui ce fac obiectele clasei respective, omind intenionat cum face clasa ceea ce trebuie s fac. Aadar, nu stric s facem o distincie necesar ntre definirea unei clase (= specificarea atributelor i a operaiilor) i implementarea clasei (= scrierea codului asociat operaiilor clasei). Definirea rspunde unor comandamente externe (ce in de modul de utilizare a obiectelor clasei); implementarea rspunde unor comandamente care in de intimitatea comportamentului obiectelor (mod de reprezentare n memorie a obiectelor, mod de implementare a operaiilor n acest context). S mai adugm c o operaie implementat se mai numete i metod. Folosind notaia UML pentru reprezentarea vizual a proprietilor unei clase, avem situaia din Figura 6.

Un concept, inevitabil n programarea orientat pe obiecte este i conceptul de obiect. L-am folosit, deja, la modul intuitiv, ca fiind o parte a unei realiti avnd o anumit valoare de ntrebuinare n contextul n care apare. Acum este momentul s dm urmtoarea definiie. Definiia 2. Se numete obiect o instan a unei clase. De la teoria general a tipurilor de date, se tie c instana unui tip de dat este o variabil avnd o anumit reprezentare n memorie, deci o identitate, i o anumit stare din punct de vedere al coninutului memoriei asociate. n toate limbajele de programare, care ofer suport orientrii pe obiecte, clasa este asimilat unui tip de dat (este drept un tip special), care devine folositor n momentul n care se manifest prin intermediul instanelor. Instanele unei clase se vor numi, aadar, obiecte sau, uneori, variabile obiect. Dogmatic vorbind, dac soluia unei probleme este modelat ca o singur clas, atunci ne ateptm ca dinamica aplicaiei corespunztoare s fie opera comportamentului unei instane a clasei. Ce se ntmpl, ns, dac soluia unei probleme este abstractizat de o ierarhie sau de o reea de clase? n acest caz, dinamica aplicaiei este opera colaborrii ntre instaele unora dintre clasele ierarhiei sau reelei n cauz. Semantic vorbind, modul n care colaboreaz mai multe obiecte pentru a rezolva o anumit problem, este greu de fixat ntr-o formul definitiv. Din punct de vedere tehnic, ns, rezolvarea este relativ simpl, dup cum se poate deduce i din Figura 7. <Nume clas> Lista atributelor informaionale, specificate prin nume, tip i eventual valoare implicit Lista operaiilor, specificate prin signatur Figura 6. Notaia UML pentru o clas

Obiect

(Produs) CodProd:112233

ListareFurn(CodProd)

(Furnizor) AdresaListaFurn: XXXXXXXX

Produs char codprod[11]; : void afisare(); :

Clasa definitoare a obiectului Furnizor

ListaFurn *AdresaListaFurn; : ListareFurn(char codp[11]) :

Operaia afisare() apeleaz operaia ListareFurn() Figura 7. Exemplu de comunicare(colaborare) ntre dou obiecte Notaia UML7 pentru o clas i nelesul complet al noiunii de signatur8 pot fi urmrite, lund-o naintea timpului, consultnd lucrarea [Iniiere n modelarea obiect orientat utiliznd UML, Dorin Bocu, Editura Albastr, ClujNapoca, 2002] n Figura 7 se prezint schema fundamental pentru colaborarea dintre obiecte. Unul dintre obiecte (obiectul de tip Produs, n cazul nostru) iniiaz comunicarea cu cellalt obiect (de tip Furnizor, n cazul nostru). Se mai obinuiete s se spun c obiectul de tip Produs i-a trimis un mesaj obiectului de tip Furnizor. Este de ateptat ca, ntr-o form sau alta, mesajul s fie urmat de un rspuns. Aadar, dac OFurnizor este o variabil de tip Furnizor i dac aceast variabil este accesibil unui obiect de tip Produs, atunci comunicarea este posibil, cu condiia ca obiectil de tip Produs s cunoasc interfaa clasei definitoare a obiectului OFurnizor i s aib acces la aceast interfa. n varianta cea mai simpl, un mesaj are structura:
7

UML-prescurtare de la Unified Modeling Language-Limbaj de modelare unificat, specificat de Rational Software Corporation i omologat ca standard de facto de ctre grupul OMG 8 Signatura cuprinde, n genere: numele opreraiei, lista de parametri i, opional, tipul returnat

<Obiect>.<Nume_metod>([<Lista_de parametri_actuali>]); ceea ce ne ndreptete s dm definiia de mai jos. Definiia 3. Se numete mesaj apelul unei metode a unui obiect, apel efectuat de ctre un client potenial al obiectului n cauz. Cunoaterea acestui fapt este deosebit de important n situaia n care vrem s explicm semantica polimorfismului n programarea orientat pe obiect, ceea ce vom face n partea urmtoare a acestui capitol. n sfrit, s mai menionez faptul c rspunsul pe care l d un obiect cnd primete un mesaj de la alt obiect depinde de starea n care se afl obiectul care primete mesajul. Este un aspect asupra cruia programatorul trebuie s fie atent, deoarece o stare improprie a obiectului destinatar, poate provoca euarea iniiativei obiectului expeditor de a comunica. Multe excepii apar, n faza de testare a programelor, tocmai pentru c nu s-a manifestat suficient preocupare pentru evitarea utilizrii obiectelor atunci cnd acestea sunt ntr-o stare critic (referine nerezolvate, resurse necesare insuficiente, etc.). Fie, n continuare, definiia conceptului de stare. Definiia 4. Se numete stare a unui obiect o abstractizare a valorilor atributelor acelui obiect precum i a relaiilor pe care obiectul le are cu alte obiecte. Aadar, recapitulnd, conceptele eseniale cu care operm n lumea orientrii pe obiecte sunt: clas, obiect, stare obiect, mesaj.

2.2 Principii n programarea orientat pe obiecte


Am vzut n paragraful 2.1 care sunt conceptele cele mai importante cu care ne ntlnim n programarea orientat pe obiecte. Se tie de la alte discipline exacte c, fr principii de utilizare a lor, conceptele sunt bune doar de pus n raft, ca nite bibelouri cu care putem aminti posteritii de o lume disprut. Conceptele prind via, cu adevrat, doar n momentul n care sunt acompaniate de un set de principii care fixeaz regulile eseniale de utilizare a conceptelor. Evident, vom vedea care sunt aceste principii. Ce ne mai ateapt dincolo de ele? Ne ateapt invitaia de a proba singuri valabilitatea acestor principii, la nceput, improviznd cu inerent stngcie, mai apoi descoperind adevrate abloane de rezolvare a unor probleme tip. Ne st la dispoziie, n cantiti industriale, n internet, experiena tuturor celor care i-au fcut o religie din orientarea pe obiecte i au realizat aplicaii de referin n acest spirit. Voi prezenta, n continuare, principiile pe care le consider absolut necesare pentru a programa n spiritul orientrii pe obiecte. Abstractizarea Obinuiesc s insist pe importana acestui principiu deoarece mnuirea lui fr inspiraia necesar (iar inspiraia, ntr-un domeniu, are cunoaterea acelui domeniu ca naintemergtor) poate pune lesne sub semnul ntrebrii calitatea unei soluii.

Abstractizarea este procesul de ignorare intenionat a detaliilor nesemnificative i de reinere a proprietilor definitorii ale unei entiti. Prin urmare, din perspectiv procesual, abstractizarea este o modalitate de a reflecta asupra proprietilor unei entiti, cu scopul de a obine reprezentri care descriu comportamentul entitii, reprezentri care pot ndeplini simultan funcii explicative, funcii modelatoare i, de ce nu, funii demiurgice, la diferite paliere de profunzime. Utilitatea unei abstracii se manifest atunci cnd apar beneficiari n sfera ei de manifestare. Ca un exemplu, referindu-ne la limbajele de programare, putem observa c, toate limbajele ofer suport specific pentru abstractizare. Cu ct suportul pentru abstractizare este mai consistent, cu att putem spune c avem de-a face cu un limbaj de programare de nivel mai nalt. In cazul limbajelor de programare, abstractizarea nseamn un anumit gen de apropiere de limbajul uman i, prin aceasta, de gndirea uman. De asemenea, la nivelul limbajelor de programare vorbim despre trei tipuri fundamentale de abstracii, ca rezultate ale procesului de abstractizare: abstraciile procedurale, abstraciile la nivelul datelor i clasele. Abstraciile procedurale sunt cel mai mult folosite n programare. Utilizarea lor metodic permite ignorarea detaliilor legate de desfurarea proceselor. Toate funciile puse la dispoziia programatorilor n C prin intermediului sistemului de fiiere antet sunt exemple de abstracii procedurale, a cror utilizare este uor de nvat dac le cunoatem: numele, eventual lista de parametri i/sau tipul returnat, plus semantica operaiilor realizate de respectivele abstracii. Nu trebuie s avem, neaprat, informaii despre implementarea acestor funcii. Care este ctigul? Se creaz, la un anumit nivel de abstractizare a unui program, posibilitatea ca acesta s fie exprimat ca o succesiune de operaii logice i nu n termeni de instruciuni primare ale limbajului. Pentru lizibilitatea programului i, n consecin, pentru depanare, aa ceva este de maxim interes. Abstraciile la nivelul datelor permit, de asemenea, ignorarea detaliilor legate de reprezentarea unui tip de date, n beneficiul utilizatorilor tipului de date. Un exemplu remarcabil de astfel de abstracie la nivelul datelor este tipul variant, specificat de cei de la firma Borland n cadrul limbajului Object Pascal. Cei care au realizat aplicaii Delphi i au dat cu nasul peste tipul variant au probabil amintiri plcute despre versatilitatea i uurina n utilizare a acestui tip de dat. Clasele ca abstracii combin ntr-o nou abstracie, extrem de puternic i polivalent semantic, cele dou abstracii mai sus pomenite, care in de acea epoc din istoria programrii n care deviza era: Algoritmi+structuri de date=programe. Arsenalul pus la dispoziia programatorului de clase, n calitate de instrumente de abstractizare va fi n atenia acestui curs, n continuare. ncapsularea Principiul ncapsulrii insist pe separarea informaiilor de manipulare a unei entiti de aspectele implementaionale. Se deduce, cu uurin, faptul c ncapsularea este o varietate de abstractizare. Practicat metodic i cu suport sintactic adecvat, n programarea orientat pe obiecte, ncapsularea este susinut i la alte nivele, de ctre limbaje de programare diferite. De exemplu, conceptul de unit din Object Pascal, permite ncapsularea resurselor unei aplicaii Delphi, ceea ce, n fond, nseamn modularizare, la un nivel de abstractizare mai nalt dect cel specific ncapsulrii la nivel de clase. Cuvintele cheie pe care se sprijin operaionalizarea principiului ncapsulrii sunt interfaa i implementarea. Motivul pentru care se insist att pe acest principiu este simplu: separnd interfaa de

implementare i admind c interfaa este foarte bine structurat (rezult c este stabil n timp i acceptat de utilizatori din punct de vedere al utilitii i comoditii n utilizare) nseamn c eventuale modificri ale implementrii (absolut fireti n condiiile mbuntirii permanente a mediilor de execuie i programare) nu vor putea afecta utilizatorii. Este bine ca cititorul s fac distincie ntre ncapsulare i localizarea datelor i a funciilor, n cadrul aceleeai entiti. ncapsularea are nevoie de localizare pentru a sublinia caracterul de black-box al entitilor, dar ea nseamn mult mai mult dect att. De asemenea, nu trebuie s fetiizm ncapsularea, ateptnd de la ea s garanteze sigurana n procesul de manipulare a obiectelor. Ct de sigur n utilizare este un sistem soft, hotrte programatorul, care combin eficient fora principiului ncapsulrii cu procedeele tehnice de asigurare a proteciei fa de ingerinele cu efecte negative. Aadar, din perspectiva ncapsulrii, o clas, indiferent de limbajul n care va fi implementat, trebuie s aib, obligatoriu, dou compartimente, ca n Figura 8.

Student

Date i operaii private

(Implementarea)

Operaii publice

(Interfaa)

Figura 8. Componentele obligatorii ale unei clase, din perspectiva ncapsulrii Motenirea Mult vreme, motenirea a fost un vis pentru a crui ndeplinire, ntr-o form destul de primitiv, programatorii trebuiau s depun eforturi intense. Apariia limbajelor care ofer suport sintactic pentru operaionalizarea principiilor orientrii pe obiecte (OO), a confirmat, printre altele i marea importan a principiului motenirii pentru specificul unei soluii OO. Din perspectiv mecanic privind lucrurile, acest principiu afirm posibilitatea ca o clas B s moteneasc o parte dintre proprietile unei clase A. n acest fel avem la dispoziie un mecanism practic pentru gestiunea similaritilor, naturale, de altfel ntr-o societate de obiecte foarte diversificat. n paragraful 2.1 (Figura 4) am prezentat, deja, un exemplu de societate posibil de obiecte, n care diversitatea punea probleme de clasificare, lsnd deschis problema gestiunii similaritilor, n cazul mulimii patrulaterelor. Mergnd pe linia utilizrii notaiei UML pentru reprezentarea unei soluii OO, atunci trebuie s spunem c dac avem o clas B care motenete o parte dintre proprietile clasei A, acest lucru va fi redat grafic n felul n care este artat n Figura 9.

GENERALIZARE

Clasa printe, superclasa, clasa de baz Simbolul care indic relaia de generalizare dintre clasele A i B

Dup cum se vede, relaia de motenire nu este doar o relaie al crei neles se reduce la posibilitatea ca B s moteneasc o parte din proprietile lui A; semantica relaiei de motenire este mult mai subtil. Mai nti, este vorba despre necesitatea de a reflecta cu ndemnarea necesar la cele dou posibiliti de a identifica o astfel de relaie: procednd top-down sau bottom-up, deci specializnd, dup ce am identificat o clas rdcin, sau generaliznd, dup ce am terminat operaia de clasificare a claselor i am nceput s organizm clasele n familii, dup similaritile care le leag. Indiferent de abordare, rezultatul trebuie s fie acelai: o ierarhie de clase, n care similaritile sunt distribuite pe nivele de abstractizare, reducnd astfel la minimum redundanele i pregtind terenul pentru o reutilizare elegant a codului i pentru o serie de alte avantaje care nsoesc un lan de derivare. Exemplificm cu ierarhia din Figura 10, a crei semantic ne este deja cunoscut. n Figura 10 regsim aproape toate elementele specifice unei soluii obiect orientate, care folosete judicios principiul motenirii. Astfel, aproape toate soluiile orientate pe obiect au o clas rdcin (care, n anumite situaii poate fi o clas abstract, adic o clas care nu poate avea instane directe, dar referine avnd tipul ei pot fi asociate cu instane ale descendenilor), dac soluia se reduce la o singir ierarhie de clase. n ierarhie putem ntlni i clase intermediare, precum i clase terminale sau frunz.

SPECIALIZARE

Clasa copil, subclasa, clasa derivat Figura 9. O parte din semantica relaiei de motenire care opereaz ntre clase

Poligon

Clas rdcin, poate fi i abstract

Triunghi

Patrulater

Clas intermediar

Paralelogram

Trapez

Patrulater inscriptibil

Clase frunz Romb Dreptunghi

Figura 10. Principiul motenirii n aciune Din punctul de vedere al programatorului, pe lng utilitatea motenirii n procesul de gestiune a similaritilor, mai exist un avantaj care poate fi exploatat n faza de implementare a soluiei, avantaj derivat din principiul motenirii sub forma unui alt principiu care afirm c: Orice printe poate fi substituit de oricare dintre descendeni Acest principiu este de mare utilitate pentru programatori, el fiind operaionalizat prin intermediul operaie de casting, aplicat obiectelor ale cror clase definitoare sunt n relaie de motenire. Evident, este vorba de un casting implicit n lumea obiectelor (descendentul motenind tot ceea ce se poate de la strmoi, va putea opera n locul strmoului). Castingul de acest tip se numete up-casting. Se practic, explicit i down-castingul, dar programatorul trebuie s fie pregtit s rspund de consecine. ntr-un down-casting, este posibil ca obiectul de tip strmo, convertit la un obiect de tip descendent, s nu asigure coerena informaional de care are nevoie descendentul, deci pot apare, principial, excepii. n Java se ntlnesc amndou tipurile de casting. n sfrit, n Figura 10 avem un exemplu de utilizare a motenirii simple, potrivit creia un descendent poate avea un singur strmo. Unele limbaje de programare(C++, de exemplu) accept i motenirea multipl, ceea ce nseamn c o clas poate avea doi sau mai muli strmoi. O astfel de soluie pentru principiul motenirii este plin de riscuri, cu toate avantajele pe care le presupune. Motenirea multipl poate genera ambiguiti care pot pune la ncercare rbdarea programatorului.

D
Figura 11. Exemplu de motenire multipl n exemplul din Figura 11, clasa D are ca strmoi clasele B i C. Obiectele clasei D vor avea motenite resursele lui A, att pe traseul lui B ct i pe traseul lui C. Ambiguitatea referirii la o resurs a lui A n cadrul unei instane a lui D este evident. Cnd este considerat strict necesar, motenirea multipl se utilizeaz cu discernmnt. Java i C# nu mai promoveaz motenirea multipl, propunnd alte soluii la aceast problem. Motenirea nu este doar o problem de sintax, ci este esena nsi a programrii orientate pe obiecte. Pe temeiul motenirii are rost s vorbim n continuare de principiul polimorfismului, aplicat la lumea obiectelor. Polimorfismul Dup cum se vede i n exemplul prezentat n Figura 10, ierarhia de clase care modeleaz, la urma urmei, comportamentul unei aplicaii poate avea mai multe clase frunz, deci clase care pote genera instane. Crearea unei instane este, indicutabil o problem n care programatorul trebuie s se implice, el trebuind s stabileasc, n funcie de dinamica aplicaiei, ce constructor va coopera la crearea unei instane. n ideea c avem o aplicaie care se ocup de simularea nvrii poligoanelor, vi se pare interesant s declarm cte o variabil pentru fiecare tip de poligon din ierarhia prezentat n Figura 10 (mai puin clasa rdcin)? Nu este interesant din dou motive: 1. 2. Explozia de variabile obiect nu este un indiciu de performan n programare. Odat create obiectele, programatorul trebuie s tie n orice moment ce fel de obiect lucreaz la un moment dat. Chiar c a ceva poate deveni suprtor ntr-o aplicaie de mare complexitate.

Nu ar fi mai civilizat s declarm o variabil de tip Poligon n care s pstrm instane create cu ajutorul constructorilor oricror descendeni care pot avea urmai direci? Admind c, n clasa Poligon, am declarat o operaie calculArie() care este suprascris n fiecare descendent, atunci situaia creat este urmtoarea: obiectul creat cu ajutorul constructorului unui descendent al clasei Poligon i pstrat ntr-o variabil de tip Poligon (principiul substituiei ngduie acest lucru), s-i spunem p, va putea s apeleze diferite implementri ale operaiei calculArie(), n funcie de

contextul n care se creaz p. Aadar, un mesaj de tipul p.calculArie() ce rspuns va genera? Rspunsul va fi dependent de context, controlul contextului (adic alegerea metodei specifice clasei definitoare a obiectului pstrat n p) realizndu-se de ctre sistem prin mecanisme specifice, n timpul execuiei programului. Acest mod de legare a unui nume de operaie de codul metodei specifice tipului definitor al variabile obiect gazd se numete late binding. Este un mod de legare mai puin performant dect legarea static (la compilare), dar avantajele scuz dezavantajele insignifiante, n condiiile n care viteza procesoarelor i disponibilul de RAM sunt n expansiune permanent. S-a neles, probabil, faptul c polimorfismul este de neconceput fr motenire n care specializarea claselor s se bazeze i pe suprascrierea unor metode n clase aflate n relaie de motenire. Tot ceea ce trebuie s tie programatorul de aici ncolo este avertismentul c: Motenirea i polimorfismul sunt ideale ca uz i contraindicate ca abuz. Terminnd excursia n lumea conceptelor i a principiilor programrii orientate pe obiecte, nu ne rmne dect s anunm c n capitolele urmtoare vom ncerca s vedem aceste abstracii la lucru n context Java sau C++.

Capitolul 3
Specificarea i implementarea unei clase Perspectiva Java

3.1 n loc de introducere


Fr s am pretenia c am epuizat semantica modelrii orientate pe obiect, n capitolele precedente, n acest capitol voi ncepe incursiunea n lumea elementelor suport oferite de Java i C++ pentru implementarea orientat pe obiect a modelelor obiect orientate. Laboratoarele de cercetare i lumea practicienilor vd, nc, n aceste dou limbaje nite momente de referin n zbuciumata evoluie a limbajelor de programare. Apariia limbajului C# la orizont, prin bunvoina celor de la Microsoft, se anun un concurent redutabil, care, i propune s ctige, deopotriv, atenia fanilor Java i C++. Pn ce apele se vor limpezi, m voi ocupa de ceea ce, tocmai, am anunat n lista subiectelor care fac obiectul acestui capitol.

3.2 Atenie la importana efortului de abstractizare!


Voi ncerca s art cum se specific i implementeaz clasele n Java. n tot acest timp, ncercai, mpreun cu mine, s nu minimalizai nici o clip importana hotrtoare a abstractizrii n elaborarea unor soluii stabile i flexibile9. n acest scop voi considera un exemplu pretext de problem prin intermediul creia voi ncerca s pun n valoare puterea de reprezentare a limbajelor Java i C++. S se scrie codul Java / C++ care pune la dispoziia unor utilizatori poteniali capabiliti de lucru cu liste simplu nlnuite de numere ntregi, pentru care resursele necesare reprezentrii sunt alocate dinamic. n continuare m voi referi la aceast problem cu acronimul LSI.
9

Nu este o contradicie ntre termeni. Cele mai bune modele, ntr-o lume care nu st pe loc, sunt modelele care pot fi socotite, n acelai timp, nchise i deschise, deci stabile i flexibile.

De ce numai acest tip de list? De ce doar numere ntregi? Pentru c aa cere utilizatorul n acest moment. Nici mai mult, nici mai puin. Aa se ntmpl i n viaa de toate zilele. Specialitii n IS trebuie s livreze beneficiarilor ceea ce acetia se ateapt s primeasc. Amorul artei sau dispreul fa de beneficiar, sunt taxate necrutor de ctre ansamblul regulilor jocului din industria de soft. Nu voi exagera cu descrierea travaliului conceptual n urma cruia am ajuns la nite concluzii n legtur cu rezolvarea problemei LSI. Dar, cteva elemente de descriere a atmosferei trebuie, totui precizate. Se tie c structurile dinamice de date sunt preferate structurilor statice de date, atunci cnd utilizarea chibzuit a memoriei i flexibilitatea relaiilor dintre obiectele care populeaz structura sunt critice pentru calitatea unui sistem soft. Se mai tie, totodat, c o structur dinamic de date este complet specificat dac am clarificat: Tipul datelor care populeaz structura. Mecanismul de nlnuire a datelor din structur. Punctele de intrare n structur. Operaiile cu ajutorul crora ntreinem i consultm structura.

Fiecare din cerintele de mai sus, poate face obiectul unor consideraii cu implicaii interesante asupra soluiei. M limitez doar la a observa faptul c o structur de date dinamic este un exemplu reprezentativ de tip de dat care poate fi modelat orientat pe obiecte, n a fel nct serviciile oferite s fie complete i uor de apelat. Intrnd puin n domeniul problemei, dac ne gndim la o list simplu nlnuit, semantica ei poate fi vizualizat, parial, ca n Figura 12.
Adresa de start a primului nod

Data_ 1

Data_2

Data_n

Informae nod Legtura spre urmtorul nod Ultimul nod nu are un succesor

Figura 12. Incercare de vizualizare a unei liste simplu nlnuite Prin urmare, obiectele care fac parte din inventarul problemei sunt: Nod (avnd ca proprieti informaionale Data i Adresa de legtur cu urmtorul element iar ca proprieti comportamentale, cel puin operaii legate de crearea unui

nod i consultarea proprietilor lui informaionale) i Lista (un obiect care, n principiu este o agregare10 de obiecte de tip Nod). Un obiect care poart marca tipului Lista va avea, aadar, proprieti informaionale i comportament specific. Abstractiznd cu nverunare, lista proprietilor informaionale ale unui obiect de tip Lista ar putea s conin doar adresa primului nod din list. ns, dac trecem n revist exigenele clienilor fa de un obiect de tip Lista, vom descoperi c este indicat s avem un atribut pentru a indica dac lista conine sau nu elemente (un exemplu de redundan n procesul de utilizare a memoriei interne care asigur o vitez sporit n procesul de utilizare a unei liste). Dac acest atribut este un ntreg care indic chiar numrul elementelor din list, cu att mai bine. Aadar, n notaie UML, n acest moment am putea avea situaia din Figura 13.
Lista -Nod Astart; -Nod AUltim; -Int NrElem; Nod -Int Data; -Nod Urmatorul; +Nod(int Numar); +void setareData(int Numar); +int consultareData(); +void setareUrmator(Nod AUrm) +Nod consultUrmator() +void setareAStart(Nod Element); +Nod consultaAStart(); +void setareNrElem(int ne); +int consultaNrElem(); +setareAUltim(Nod Element) +Nod consultaAUltim(); +void insereazaDNod(Nod Element); +void insereazaINod(Nod Element); +void stergeNodAdr(Nod Element); +void stergeNodNum(int Nr); +salveazaInFisier(char numef[]); +incarcaDinFisier(char numef[]);)

Figura 13. Clasele candidate la rezolvarea problemei LSDI Ce observaii putem face? Clasa Nod este o clas concret, adic, avnd constructor, poate avea instane. S mai observm faptul c atributele clasei Nod sunt prefixate cu cte un semn -, ceea ce, n UML, nseamn c sunt private. Conform dogmei OO, absolut normal. De asemenea, s mai observm c operaiile clasei Nod sunt prefixate cu semnul + i sunt scrise cu font drept, ceea ce, n UML, nseamn c sunt publice i au deja implementare valabil. Semantica acestei clase i a contextului n care opereaz s-ar prea c nu ne pretinde specificarea unor operaii private sau protejate. Oare? Este adevrat c n Java, de exemplu, crearea unui obiect de tip Nod se va face rezonabil, chiar i dac nu am prevedea un constructor, dat fiind faptul c obiectele se creaz i n acest caz, atributele fiind iniializate cu valori predefinite (0 pentru numere, null pentru obiecte, false pentru valori booleene, \u0000 pentru caractere). Dac un astfel de comportament implicit nu este de acceptat, atunci este loc pentru a specifica operaii care fac validarea strii
10

In modelarea obiect orientat se folosete relaia de agregare pentru a indica o asociere ntre obiecte care, din punct de vedere semantic, sunt ntro relaie parte-ntreg. Reprezentarea unei relaii de agregare este la latitudinea programatorului.

obiectelor. Aceste operaii ar putea fi folosite, ca uz intern, de orice alt operaie care este sensibil la starea obiectului care o folosete la un moment dat. Pe de alt parte, observm faptul c unele dintre operaiile clasei Lista sunt scrise cu caractere italice. n UML aceasta nseamn c aceste operaii sunt abstracte. Prin urmare, clasa Lista este o clas abstract, deci nu are constructor i tipul introdus de aceast clas nu va putea avea instane directe. Pentru a fi util, clasa Lista trebuie s aib descendeni. Care vor fi aceti descendeni? Vom considera c aceti descendeni sunt doi: o clas care modeleaz o list simplu nlnuit de ntregi, n care cuvntul de ordine la creare este ordinea fizic de introducere i o clas care modeleaz o list simplu nlnuit de ntregi, n care elementele listei sunt introduse astfel nct, dup fiecare introducere acestea s fie n ordine cresctoare. Pe scurt: liste indiferente la ordinea numerelor ntregi i liste sensibile la ordinea numerelor ntregi. Vom obine ierarhia din Figura 14.
Lista

ListaOarecare

ListaSortata

Figura 14. Ierarhia claselor care modeleaz dou varieti de list simplu nlnuit. Se poate observa c cele dou varieti posed operaiile necesare pentru a simula, n caz de nevoie i comportamentul unei stive (AStart este Top-ul iar inserareINod() i stergeNodAdr(AStart) sunt operaiile specifice unei stive, adic Push() i Pop()). Cititorul bnuiete c inserareINod() este abreviere de la inserare nainte de nod iar inserareDNod() este abtreviere de la inserare dup nod. Consideraii de acest gen i chiar mai profunde, trebuie s prilejuiasc orice ncercare de rezolvare orientat pe obiect a unei probleme. 3.3 Specificarea i implementarea unei clase n Java Este cunoscut faptul c, n Java, orice aplicaie este puternic obiect orientat, cel puin datorit cadrului sintactic obligatoriu pentru realizarea unui applet sau a unei aplicaii Java obinuite. Dac spiritul orientrii pe obiecte este bine neles, atunci Java este o soluie interesant pentru multe probleme a cror rezolvare presupune realizarea unor aplicaii pentru care lumea Internet-ului este puternic deschis. Indiferent de tipul aplicaiei, piesele de baz n realizarea acesteia sunt clasele. Vom avea, n cazul unei aplicaii Java obinuite, o singur clas public, care conine funcia main() i una sau mai multe clase care modeleaz, la diferite nivele de rafinare, comportamentul aplicaiei. De asemenea, n cazul unui applet Java, vom avea o singur clas public care extinde clasa Applet i una sau mai multe clase care modeleaz, la diferite nivele de rafinare, comportamentul applet-ului. Privit de la cel mai nalt nivel de abstractizare, definiia unei clase n Java este: [<List modificatori>] class <Nume clas> [extends <Clasa de baz>] [implements <Lista interfee>]

{ //List de resurse sau corp clas } Resursele unei clase sunt de dou tipuri: atribute i/sau operaii. Problema noastr, dup cum am vzut, este de a organiza aceste resurse, astfel nct s putem beneficia de o serie de avantaje din punct de vedere al efortului de dezvoltare ct i din punct de vedere al calitii softului11. n paragraful 2.2 am vzut c o ncapsulare corect (n acord i cu dogma OO) nseamn s declarm ca private atributele i s definim o interfa corespunztoare clasei. Vom vedea, mai jos, ce nseamn acest lucru n cazul problemei noastre. Atributele unei clase se specific sintactic astfel: [<List modificatori atribut>] Tip <Lista identificatori variabile>; Operaiile unei clase se specific prin signatur i implementare ca mai jos: [<List_modificatori_operaie>]Tip_returnat Identificator_metod> ([<List parametri>) [throws <Lista excepii>] { <Corp operaie> } Formalizmul folosit pentru prezentarea sintaxei de definire a unei clase se bazeaz, dup cum se poate deduce, pe urmtoarele convenii: Orice construcie a utilizatorului este prezentat ntre simbolurile < ...>. Orice construcie opional este ncadrat de simbolurile [...]. Cuvintele rezervate sunt evideniate prin ngroare. ntrebarea fireasc care se pune este urmtoarea: la ce ne folosesc aceste elemente de variaie n procesul de definire a unei clase. Voi ncerca s rspund pe rnd la toate ramurile acestei intrebri. Modificatorii aplicabili claselor Dup cum se poate observa, cuvntul cheie class poate fi prefixat, opional, de un modificator. Lista complet a acestor modificatori este: abstract, final, public. Modificatorul de clas abstract Java permite extinderea unei clase existente cu o subclas. Cu timpul, este posibil s v constituii propriile biblioteci de clase care considerai c vor fi extinse de ali programatori. Pentru unele clase, poate s fie inutil s

11

Despre calitatea softului cititorul poate gsi elementele eseniale n D. Bocu, Iniiere n ingineria sistemelor soft, Editura Albastr, 2002

implementai o operaie ct timp nu se cunoate cum va fi extins clasa. n astfel de cazuri, putei utiliza cuvntul cheie abstract pentru a indica faptul c n descendeni toate operaiile clase trebuie s fie supradefinite obligatoriu. Clasa Lista din Figura 14 ar putea fi definit, prin urmare astfel, n Java: abstract class Lista { private Nod AStart; private Nod AUltim; private int NrElem; public void setareAStart(Nod Element); public Nod consultaAStart(); public void setaretNrElem(); public int consultaNrElem(); public Nod consultaAUltim(); public abstract void insereazaDNod(Nod Element); public abstract void insereazaINod(Nod Element); public abstract void stergeNodAdr(Nod Element); public abstract void stergeNodNum(int Nr); public final void salveazaInFisier(char numef[]); public final Nod incarcaDinFisier(char numef[]);) } Este evident faptul c descendenii ListaOarecare i ListaSortata sunt obligai s implementeze toate operaiile clasei Lista, care sunt abstracte. De asemenea, s observm c implementarea definitiv a operaiilor de salvare/restaurare a listei este realizat n clasa Lista i va fi folosit fr modificri n descendeni. Modificatorul de clas final (tocmai l-am utilizat mai sus) n general vorbind, Java permite unei clase s extind o alt clas. Atunci cnd definim o clas, n contextul n care anticipm utilizarea ei, s-ar putea s nu dorim extinderea ei de ctre alte clase. n acest caz, prin includerea cuvntului cheie final n cadrul definiiei clasei vom mpiedica crearea de subclase ale clasei n cauz. Aadar, clasa: public final class NumeClasa {...} nu va putea s aib urmai. Modificatorul de clas public Atunci cnd utilizm cuvntul cheie public n cadrul declaraiei clasei, ne asigurm c acea clas este vizibil / accesibil de oriunde. Dac dorim s controlm accesul la o clas, cuvntul cheie public nu are ce cuta n declaraia clasei. S reamintim, totodat, faptul c Java permite o singur clas public ntr-un fiier cu cod surs. Evident, caracterul public al unei clase poate avea alte conotaii n contextul organizrii codului surs al unei aplicaii cu ajutorul pachetelor. Mai precis spus, o clas public poate fi utilizat din exteriorul pachetului n care a fost declarat, pentru a crea instane sau pentru a o extinde. n schimb, o clas care nu a fost declarat public este considerat o clasa friend , putnd fi accesat doar din interiorul pachetului n care este rezident.

Modificatorii aplicabili atributelor Domeniul de valabilitate al unui atribut definete locaiile din program n care atributul este cunoscut. n procesul de definire a unei clase putem controla domeniul unui atribut al clasei precednd declaraia lui cu unul din cuvintele cheie: public, private, protected, static, final, tranzient, volatile. S menionm faptul c un atribut care nu este nsoit de nici un modificator este vizibil friendly, adic doar din interiorul clasei i din clasele din acelai pachet. Modificatorul de atribut public Un atribut public este vizibil / accesibil oriunde este vizibil / accesibil clasa care l conine. Aadar, pentru a declara ca public un atribut vom proceda ca mai jos: : public int vizibilOriundeEsteVizibilaClasa; : Dogma spune c o clas care ajunge la client trebuie s-i ascund atributele fa de acesta, accesul la ele fiind mijlocit de interfa, dac este cazul. Nu este, ns, exclus ca o serie de clase neterminale (care nu sunt, deci clase frunz) s declare ca publice o parte a atributelor, protecia lor fiind controlat, la nivelul descendenilor prin intermediul interfeelor sau al organizrii n pachete. Modificatorul de atribut private Un atribut privat este vizibil numai n interiorul clasei sale. Subclasele i clienii externi nu pot accesa aceste atribute. Modificatorul de atribut protected Un atribut al clasei, declarat ca protejat, este accesibil n descendenii clasei sau n cadrul pachetului din care face parte clasa deintoare. Atenie, un atribut declarat ca protected ntr-o clas va putea fi accesat n scriere i citire n toi descendenii clasei n cauz, rezideni chiar i n afara pachetului gazd al clase care deine atributul protejat. Nu va fi permis accesul direct la atributul protejat pentru clase care nu sunt descendei ai clasei care declar atributul protejat. Modificatorul de atribut static Orice atribut care nu este declarat ca static este numit atribut de instan, ceea ce nseamn c fiecare instan are propria copie a atributului. Atunci cnd este n interesul comportamentului clasei ca un atribut s fie partajat de toate obiectele clasei n cauz, acel atribut va fi declarat ca static. Modificatorul de atribut final Atunci cnd n definiia unei clase menionm un atribut final, indicm compilatorului faptul c acel atribut are valoare constant, care nu poate fi modificat de program. Iniializarea atributului cu o valoare se poate face la crearea obiectlui gazd, prin contribuia constructorului sau n cadrul unei declaraii de tipul: : protected static final int nr=10; : Atenie! Cele dou metode de iniializare sunt mutual exclusive.

Modificatorul de atribut transient Atunci cnd declarm un atribut ca fiind de tip transient, indicm compilatorului Java faptul c atributul nu este o parte permanent a obiectului, deci de uz intern i n cazul serializrii, de exemplu, nu va fi salvat pe memoria extern. Un atribut tranzient se declar astfel: : private transient String password; : Modificatorul de atribut volatile Atunci cnd se compileaz programul, compilatorul analizeaz codul i, adeseori va efectua anumite manevre cu scopul de a optimiza performanele codului. Atunci cnd dorim s scoatem un atribut de sub incidena unei astfel de eventualiti, o declarm ca volatil. Practic, aceasta nseamn c exist situaii particulare n care comunicaia cu alte programe sau rutine necesit neintervenia compilatorului asupra unui atribut, esenial pentru buna desfurare a comunicaiei n cauz. Modificatorii aplicabili operaiilor n aceast seciune vom prezenat o serie de modificatori care, de cele mai multe ori, sunt aplicabili metodelor care implementeaz operaiile claselor. Aceti modificatori sunt: public, private, protected, static, final, abstract, native, synchronized. Modificatorul de metod public Semnificaia acestui modificator, n cazul n care se aplic unei metode, este asemntoare cu cea pe care o are cnd se aplic unui atribut. Modificatorul de metod private Semnificaia acestui modificator, n cazul n care se aplic unei metode, este asemntoare cu cea pe care o are cnd se aplic unui atribut. Modificatorul de metod protected Semnificaia acestui modificator, n cazul n care se aplic unei metode, este asemntoare cu cea pe care o are cnd se aplic unui atribut. Modificatorul de metod static Semnificaia acestui modificator, n cazul n care se aplic unei metode, este, ntr-o oarecare msur, asemntoare cu cea pe care o are cnd se aplic unui atribut. Mai precis, trebuie s spunem c o metod static, pentru a fi utilizat nu reclam neaprat o instan, putnd fi utilizat i printr-un apel de tipul: NumeleClasei. NumeleMetodeiStatice(parametri);

O metod static poate fi utilizat pentru a accesa ali membri statici ai clasei, dar, n nici un caz, pentru a accesa variabile nestatice. Modificatorul de metod final Am vzut, deja, n cursul 2, c anumite metode ale claselor pot fi supradefinite n clasele descendente. Dac, din variate motive, dorim s blocm posibilitatea supradefinirii unei metode, atunci vom informa compilatorul de aceast intenie declarnd metoda ca final astfel: public final void MetodaNuPoateFiSupradefinita(); Modificatorul de metod abstract Dac o metod a unei clase este precedat de cuvntul cheie abstract, atunci compilatorul nu va autoriza crearea de instane ale clasei n cauz. Totodat, o clas care extinde clasa n cauz va trebui s implementeze metoda abstract obligatoriu. Declararea se face astfe: public abstract void implementareUlterioar(); Atenie! O metod abstract nu poate fi privat sau final. Semantica cuvntului cheie abstract (= metoda va fi implementat n descendeni) vine n contradicie cu semantica cuvintelor cheie private i final care spun, n moduri diferite, c metoda nu poate fi modificat. Modificatorul de metod native Acest modificator se utilizeaz pentru a spune compilatorului c o anumit metod utilizeaz cod scris ntr-un alt limbaj de programare, cum ar fi C/C++, de exemplu. Aceast posibilitate trebuie folosit cu discernmnt deoarece lovete puternic n portabilitatea aplicaiei, aa cum este ea neleas n Java. Modificatorul de metod synchronized Se tie c Java ofer suport pentru multitasking sub forma un program mai multe fire de execuie. n funcie de activitatea programului respectiv, uneori este necesar s garantm faptul c, dou sau mai multe fire nu pot accesa simultan aceeai metod. Prin urmare, pentru a controla numrul de fire de execuie care pot accesa o metod la un moment dat, utilizm cuvntul cheie synchronized. Atunci cnd compilatorul Java ntlnete o metod prefixat de cuvntul cheie synchronized, introduce un cod special care blocheaz metoda cnd un fir ncepe execuia instruciunilor metodei i o deblocheaz cnd firul i ncheie execuia. n mod uzual, sincronizarea metodelor este reclamat de necesitatea partajrii datelor. Am prezentat, mai sus, semantica cuvintelor cheie ascunse sub sintagma modificatori, aplicai atributelor sau operaiilor. Sintaxa care st la baza definirii unei clase mai conine, opional i cuvntul cheie extends pentru a indica o clas de baz clasei n curs de definire. De asemenea, n sintaxa prezentat se mai evoc i eventualitatea apariiei cuvntului cheie implements urmat de numele interfeelor pe care le implementeaz clasa, atunci cnd este cazul. Evident, cuvntul cheie extends este legat de problematica motenirii n programarea orientat pe obiecte n Java iar cuvntul cheie implements este legat de

problematica utilizrii interfeelor, pentru a introduce numeroase elemente de flexibilitate n programarea Java, inclusiv rezolvarea motenirii multiple, neadmis direct n Java. Despre aceste dou probleme vom discuta pe ndelete in Capitolul 5. nainte de a trece la prezentarea cadrului C++ pentru definirea unei clase putem urmri, mai jos, codul Java asociat definirii claselor Nod i Lista, aa cum apar ele n lumina observaiilor fcute pn acum. //--------------------------------------------//Cod Java care demareaza procesul de rezolvare //a problemei LSI //--------------------------------------------//Specificare minimala a clasei Nod //Implementare clasa Nod class Nod { private int Data; private Nod Urmatorul; //constructor public Nod(int Numar) { Data=Numar; }; //modificator atribut Data public void setareData(int nr) { Data=Numar; }; //selector atribut Data public int consultareData() { return Data; }; //Modificator atribut Urmatorul public void setUrmator(Nod Element) { Urmatorul=Element; }; //Selector atribut Urmatorul

public Nod consultaUrmator() { return Urmatorul; }; }; //Specificare aproape completa a clasei abstracte Lista abstract class Lista { private Nod AStart; private Nod AUltim; private int NrElem; //Modificator atribut AStart public void setareAStart(Nod Element) { AStart=Element; }; //Selector atribut AStart public Nod consultaAStart() { return AStart; }; //Modificator atribut NrElem public void setareNrElem(int ne) { NrElem=ne; }; //Selector atribut NrElem public int consultaNrElem() { return NrElem; }; //Modificator atribut AUltim public void setareAUltim(Nod Element) { AUltim=Element; };

//Selector atribut AUltim public Nod consultaAUltim() { return AUltim; }; //Metoda abstracta //Va fi implementata in descendenti //Insereaza un element dupa ultimul nod introdus public abstract void insereazaDNod(Nod Element); //Metoda abstracta //Va fi implementata in descendenti //Insereaza un element inaintea ultimului nod introdus public abstract void insereazaINod(Nod Element); //Metoda abstracta //Va fi implementata in descendenti //Sterge un element de adresa specificata public abstract void stergeNodAdr(Nod Element); //Metoda abstracta //Va fi implementata in descendenti //Insereaza un element de pozitie specificata public abstract void stergeNodNum(int Nr); //Metoda publica finala //Deocamdata neimplementata //Salveaza lista reperata de AStart //intr-un fisier de nume specificat public final void salveazaInFisier(char numef[]) { System.out.println("Neimplementata..."); }; //Metoda publica finala //Deocamdata neimplementata //Restaureaza lista folosind informatiile din fisierul //de nume specificat public final Nod incarcaDinFisier(char numef[]) {

System.out.println("Neimplementata..."); return null; }; } Codul prezentat mai sus trebuie s fie extins i rafinat pentru a acoperi toate cerinele iniiale ale problemei i pentru a face fa exigenelor de calitate fireti (modularizare corect, fiabilitate, extensibilitate, etc.).

Capitolul 4
Specificarea i implementarea unei clase Perspectiva C++ Comparaie Java / C++

4.1 Specificarea i implementarea unei clase n C++ 4.1.1 Scurt introducere


Istoria limbajului C a fost scris de mult. Istoria limbajului C++, nc se scrie. La apariia lor, aceste dou limbaje au fcut rapid prozelii muli, ndeosebi printre profesioniti. Noutile aduse la ramp de C i C++ au fost preluate, fr drept de apel i de ctre noile limbaje de programare (Java, C#), care au fost specificate n contextul apariiei universului INTERNET. Atenionnd cititorul asupra faptului c n C++ orientarea pe obiecte are o mare diversitate de elemente suport, a cror prezentare n detaliu n aceast carte nu este posibil din motive obiective, voi ncerca, n continuare, prezentarea direciilor cheie pe care se poate merge n C++ pentru a face programare orientat pe obiecte.

4.1.2 Orientarea pe obiecte cu structurile i uniunile din C++


Nevoia de sintax care s susin implementarea orientat pe obiect a modelelor orientate pe obiecte a fost resimit nc din epoca programrii n C, epoc n care definirea structurilor ngduia punerea laolalt a datelor i a

prelucrrilor, ceea ce nseamn suport de un anumit tip pentru ncapsulare. Prezena prelucrrilor alturi de date era realizat prin posibilitatea de a declara membri ai unei structuri care erau pointeri la funcii. Contextul sintactic i cte ceva despre semantica ideii de mai sus, n exemplele de mai jos. Cod C care prezint un exemplu de structur orientat pe obiecte //Exemplul 4.1 #include<conio.h> #include<iostream.h> //Definirea tipului abstract de date //utilizand cuvantul cheie struct //cu semantica din C struct tad { int data; //Exemplu de membru pointer la functie int (*funct)(); }; //Prototipul functiei, compatibila cu pointerul //specificat ca membru in struct int f(); //Instanta tad tad vtad; void main() { //Setare valoare membru data vtad.data=10; clrscr(); //Setare valoare membru pointer la functie vtad.funct=&f; //Utilizare membru pointer la functie cout<<vtad.funct(); getch(); }; //A se observa modul in care se realizeaza implementarea. //Accesarea membrilor de tip data se face in context //variabila struct int f() {

return vtad.data* vtad.data; }; Cod C++ care prezint un exemplu de structur orientat pe obiecte //Exemplul 4.2 #include<conio.h> #include<iostream.h> //Definirea tipului abstract de date //utilizand cuvantul cheie struct //cu semantica din C++ struct tad { int data; //Exemplu de membru functie int funct(); }; int tad::funct() { return data*data; }; //Instanta tad tad vtad; void main() { //Setare valoare membru data vtad.data=10; clrscr(); //Utilizare membru functie //Se observa progresul fata de implementarea aceleasi idei //in C cout<<vtad.funct(); getch(); }; La vremea apariiei lor aceste mecanisme au fost o surpriz foarte plcut pentru programatorii educaii n spiritul orientrii pe obiecte. Evident, aceste mecanisme erau nc departe de semantica complet a orientrii pe obiecte. i totui, cu ajutorul enunului struct, programatorul poate defini structuri de date, reprezentabile n memoria intern,

echivalente ale tipului nregistrare (record) teoretizat n algoritmic i n numeroase limbaje de programare. Caracteristica fundamental a acestor structuri o reprezint posibilitatea de a grupa date membre care contribuie la caracterizarea informaional a unui obiect, posibilitate care are ca rezultat i introducerea unui mecanism de adresare nou (calificarea cu punct), cunoscut i n Object Pascal, de exemplu. Evident, dac vom considera necesar, pentru reprezentarea n memorie a unei structuri putem aloca memorie i dinamic. Astfel apar pointerii la structuri de date, prilej cu care se combin fora pointerilor cu posibilitile pe care le ofer structurile. Cod C++ care prezint un exemplu de structur orientat pe obiecte alocat dinamic //Exemplul 4.3 #include<conio.h> #include<iostream.h> #include<alloc.h> //Definirea tipului abstract de date //utilizand cuvantuli cheie struct //cu semantica din C++ struct tad { int data; //Exemplu de membru functie int funct(); }; int tad::funct() { return data*data; }; //Declararea unui pointer la un tad tad *ptad; void main() { //Alocare de memorie pentru ptad ptad=(tad*)malloc(sizeof(tad)); //Utilizare pointer...observati mecanismul nou de selectare //a membrului in cazul pointerilor ptad->data=10; clrscr();

//Utilizare membru functie in context de pointer //la structura cout<<ptad->funct(); getch(); //Eliberarea memoriei gestionat de ptad free(ptad); }; Tipul union, prezent n C i C++ este introdus, n esen, cu scopul de a permite partajarea de ctre datele membre a aceleeai zone de memorie, fapt care ne ngduie s imaginm foarte multe soluii simple la situaii frecvent ntlnite n realitate, a cror rezolvare, n lipsa tipului union, ne poate obliga la eforturi deosebite. Mai jos este prezentat un exemplu trivial de operare asupra coninutului unui ntreg, n condiiile n care l obligm pe acesta s partajeze aceeai zon de memorie cu un tablou de dou caractere. Versatilitatea tabloului ne va permite s operm asupra coninutului ntregului, dat fiind faptul c, atunci cnd fac parte dintr-o uniune, datele membre i partajeaz i accesul la coninutul comun. Cod C++ care prezint un exemplu de uniune orientat pe obiecte //Exemplul 4.4 #include<conio.h> #include<iostream.h> //Definirea tipului abstract de date //utilizand cuvantul cheie union union tad { void schimb(); void set_octet(unsigned nr); void arata_cuvant(void); unsigned u; unsigned char c[2]; }; void tad::schimb() { unsigned char tamp; tamp=c[0]; c[0]=c[1]; c[1]=tamp; }; void tad::arata_cuvant() { cout<<u<<endl;

}; void tad::set_octet(unsigned nr) { u=nr; }; void main() { clrscr(); tad vtad; vtad.set_octet(10000); cout<<"Inainte de..."; vtad.arata_cuvant(); vtad.schimb(); cout<<"Dupa...."; vtad.arata_cuvant(); getch(); }; Se mai pot spune multe lucruri despre puterea acestor dou cuvinte cheie. De pild, cuvntul cheie struct poate fi utilizat pentru a defini cmpuri de bii, instrumente de lucru la nivel de structuri de bii, extrem de puternice n C/C++. De asemenea, n cazul acestor structuri opereaz i semantica specificatorilor de vizibilitate public i private (asupra crora vom reveni pe larg la prezentarea tipului class. Dup cum se vede, vizibilitatea implicit, att n cazul struct ct i n cazul union este public. n sfrit, ambele tipuri ne ofer posibilitatea de a face benefica distincie ntre specificarea i implementarea unei structuri de date. Mai mult dect aceste cuvinte cheie, obinem dac apelm la tipul class, recunoscut de orice compilator de C++. De fapt, temelia specificrii supersetului C++ o reprezint tipul class i extensiile cerute de acest tip pentru a spori suportul acordat programatorilor care lucreaz orientat pe obiecte, n toate sensurile posibile.

4.1.3 Orientarea pe obiecte cu tipul class


Adevrata fort a orientrii pe obiecte n C++ este ntruchipat de tipul class, care aduce, pe lng sintaxa de baz, o serie de alte elemente remarcabile pentru confortul i fora programrii n C++ (clasele i funciile prietene, suprascrierea operatorilor, motenirea multipl, clasele template, supradefinirea funciilor ntr-un lan de derivare, etc.). Voi ncerca n continuare s prezint ct mai multe dintre aceste elemente, reunite n sintagma sintaxa de baz. Sintaxa C++ pentru specificarea unei clase class <Nume_clas> [:<Nume_clas_de_baz>] { <Date membre> <Operaii>

[public: <Date membre> <Operaii>] [protected: <Date membre> <Operaii>] [private: <Date membre> <Operaii>] }; Dup cum se poate observa, sintaxa C++ care st la baza specificrii unei clase conserv mare parte din semantica specificrii unei clase n Java. n ceea ce privete sintaxa de baz pentru specificarea datelor membre i a operaiilor membre, cadrul C++ este prezentat mai jos. <Tip data> <Identificator>; <Tip returnat> <Nume operaie>([<Lista de parametri>]); n ceea ce privete specificarea datelor membre, trebuie s atrag atenia asupra faptului c nu se admite asocierea unor valori implicite la definirea datelor membre, problema setrii valorice a datelor membre fiind exclusiv de competena programatorului (constructorii sau operaiile dedicate setrii valorilor datelor membre). n ceea ce privete specificarea operaiilor membre, deocamdat s semnalm faptul c, n principiu, exist operaii care respect sintaxa precizat mai sus, dar exist i o excepie important, constructorii, care nu accept s ntoarc un tip, nici mcar void. De asemenea, mai exist o serie de variaiuni la sintaxa de specificare a unei operaii membre, precum: Orice versiune de constructor are acelai nume cu clasa; deosebirile ntre versiuni apar la lista de parametri. Destructorul are ca nume numele clasei prefixat cu caracterul ~. Funciile virtuale beneficiaz de un supliment de sintax. Operaiile statice, ca i membrii statici, beneficiaz, de asemenea, de un supliment de sintax.

Utilizarea membrilor statici n C++ poate fi neleas din Exemplul 4.5. //Exemplul 4.5 #include <iostream.h> #include <conio.h> class Numar { int nr;

//Data membra statica //Aici doar este definita //Cererea de memorie, catre compilator, este facuta //in zona globala a programului static float nf; public: int getnr(); //Operatie membra statica static float getnf(); Numar() { nr=0; nf=0.5; cout<<"Constructorul explicit...fara parametri"<<endl; }; Numar(int nr); }; //Cererea de memorie pentru data membra statica float Numar::nf; //Implementare externa constructor cu parametru void Numar::Numar(int nr) { Numar::nr=nr; nf=0.5; cout<<"A lucrat constructorul explicit...1 parametru"<<endl; }; //Implementare operatie membra statica //Nu se poate referi decat la date membre statice float Numar::getnf() { return nf; }; int Numar::getnr() { return nr; }; void main() {

clrscr(); Numar N1=Numar(10); Numar N2; cout<<N1.getnr()<<endl; cout<<N2.getnr()<<endl; cout<<N2.getnf()<<endl; cout<<N1.getnf()<<endl; getch(); }; Din sintaxa cadru deducem c una dintre principalele probleme cu care ne confruntm la specificarea unei clase n C++, la fel ca n Java, de altfel, este stabilirea condiiilor de acces la membrii unei clase. Tabloul complet al semanticii condiiilor de acces la membrii unei clase n C++ este prezentat n Figura 15.
class F class A Membrii privai Clas prieten a clasei A

friend class F;

Membrii protejai Funcia f, prieten a clasei A class B:public A

Figura 15. Controlul accesului n clasele C++ Aadar, cuvintele cheie public, protected i private ne ajut s definim seciuni n care avem un anumit tip de vizibilitate. Fiecare astfel de seciune ncepe cu unul din cuvintele cheie de mai sus, urmat de caracterul :. Valabilitatea unei astfel de seciuni nceteaz la ntlnirea caracterului } sau la ntlnirea unui alt cuvnt cheie pentru controlul accesului.

Un exemplu de seciune public avem n Exemplul 4.5. Pentru mai mult precizie s precizm urmtoarele: Cuvntul cheie public introduce o seciune ale crei componente sunt vizibile n tot programul, ntr-un context adecvat. Cuvntul cheie protected introduce o seciune ale crei componente pot fi accesate doar de descendenii clasei n care apare seciunea. Cuvntul cheie private introduce o seciune ale crei componente sunt vizibile doar n interiorul clasei. Clienii clasei pot accesa componentele private doar prin intermediul interfeei. Dac definiia clasei ncepe cu o seciune n care nu apare nici unul din cuvintele cheie de mai sus, tipul de acces implicit este private.

Sintaxa C++ pentru implementarea unei clase Codul aferent operaiilor unei clase poate fi scris n afara clasei sau n interiorul acesteia. Implementarea extern a unei operaii urmeaz sintaxa: <Tip><Nume clas deintoare>::<Nume operaie>([<Lista parametri>]) { //Cod }; De observat utilitatea operatorului de rezoluie :: la implementarea unei operaii, pentru a indica compilatorului clasa deintoare a operaiei. Utilizatorul de rezoluie global mai are i alte valori de ntrebuinare n C++. Una dintre ele apare n Exemplul 4.5, unde operatorul :: este folosit pentru a transmite compilatorului o cerere de alocare de memorie pentru o variabil static. Dac se opteaz pentru implementarea extern, atunci apelul unei funcii, membr a clasei, se face n manierea cunoscut de la funcii n genere (utilizarea stivei pentru a memora punctul de revenire i datele locale ale funciei, predarea controlului ctre codul funciei iar la return golirea stivei, etc.). Toate aceste operaii pot afecta performanele programului n anumite condiii. Evitarea unei astfel de situaii se face implementnd inline anumite funcii membre. Implementnd inline o funcie i spunem compilatorului ca fiecare apel al funciei inline s fie nlocuit cu o copie a codului funciei respective. Crete viteza, dar poate s creasc alarmant i memoria intern folosit. De aceea, implementarea inline trebuie utilizat cu discernmnt. O funcie al crei cod asociat este scris la definirea clasei este automat considerat ca inline de ctre compilator. O funcie implementat n afara definiiei clasei, a crei signatur este prefixat de cuvntul cheie inline, este, de asemenea, tratat de compilator ca inline. inline <Tip> <Nume clas deintoare>::<Nume operaie> ([<Lista parametri>]) { //Cod

}; Cu toate c este un truism pentru majoritatea cititorilor, s menionm faptul c o funcie care ntoarce un rezultat face acest lucru cu ajutorul instruciunii return, utilizat cu o sintax de tipul: return <Expresie>; Nu cred c este necesar s iniiez o dezbatere special pe tema controlului vizibilitii asupra resurselor unei clase C++. Semantica tipurilor de vizibilitate este similar celei discutat pe larg n Cursul 3 (Specificarea unei clase n Java). Alocarea memoriei pentru obiecte n C++ Exemplele prezentate au artat, deja, pentru cunosctori, cum se aloc memoria pentru obiecte n C++. n C++ putem aloca memorie pentru obiecte static sau dinamic. Varianta static de alocare Revenind la Exemplul 4.5, observm c avem dou maniere de alocare static a memoriei: : Numar N1=Numar(10); Numar N2; : Mai nti, N1 este o instan a clasei Numar, creat i iniializat cu concursul constructorului parametrizat al clasei Numar. n al doilea rnd, N2 este o instan a clasei Numar, creat i iniializat cu concursul constructorului fr parametri al clase Numar. Apelul constructorului fr parametri este implicit. Varianta dinamic de alocare Alocarea dinamic a memoriei pentru obiecte este neleas corespunztor odat cu nelegerea complet a rolului pointerilor n programarea C++. Dat fiind importana acestui subiect pentru formarea unui programator, voi insera o serie de consideraii de strict necesitate pentru nelegerea i utilizrarea pointerilor n C++. Modelul de memorie folosit pentru implementarea acestei idei este prezentat n Figura 16.

Variabil pointer

Adres de memorie

Memoria referit de adres (poriune contigu)

Figura 16. Modelul de alocare dinamic a memorie n C++ Aadar, tipul pointer este un tip de dat ale crui instane pot pstra adrese ctre alte tipuri de date. Declararea unui pointer n C++ corespunde sintaxei de mai jos: <Tipul de baz al pointerului> *<Pointer>; Elementul magic n aceast declaraie este caracterul * Cu ajutorul lui, n declaraia de mai sus am specificat compilatorului c <Pointer> este o variabil care poate pstra o adres ctre o poriune de memorie n care am una sau mai multe valori, al cror tip definitor este <Tipul de baz al pointerului>. n variabila <Pointer> putem obine o adres n mai multe moduri: Prin atribuire, de la alt variabil pointer; Prin atribuirea adresei unei variabile statice (folosind operatorul &, de obinere a adresei unei variabile statice; de menionat faptul c operatorul & se mai folosete i pentru definirea de alias-uri sau n sintaxa de transmitere prin referin a parametrilor unei funcii); Prin formularea unei cereri de alocare de memorie (n unul dintre modurile posibile n C i C++), caz n care, dac cererea poate fi rezolvat se va primi adresa unei poriuni din Heap, memorie destinat special alocrii dinamice. Aceast ultim consideraie este dependent de platforma DOS, ca platform int pentru executabilul rezultat n urma compilrii. n Windows, datele problemei n ceea ce privete alocarea dinamic a memorie se modific, dat fiind faptul c avem alt model de memorie. Nu mai lucrm cu memorie segmentat ci cu memorie flat, manevrabil dup alte tehnici, mult mai bine puse la punct dect cele corespunztoare platformei DOS. n momentul n care n variabila pointer avem o adres, ncepe s se manifeste potenialul pointerilor sub forma: aritmetica pointerilor, paralela tablou-pointer, conversii, creare de structuri dinamice, lucrul la nivel de octei i bii, etc. Revenind la Exemplul 4.5, ne putem imagina c am declarat un pointer la clasa Numar (fie acesta pnumar) pe care l-am asociat cu o adres prin apelul operatorului new, urmat de apelul constructorului, pentru a realiza iniializarea. A se urmri codul prezentat mai jos.

: //Declarare i alocare de memorie Numar *pnumar=new Numar(11); //Utilizare pointer //Referirea unui membru in contextul unui pointer //se face cu ajutorul secventei de simboluri ->. cout<<pnumar->getnr()<<endl; cout<<pnumar->getnf()<<endl; : Problematica metodelor virtuale sau a metodelor virtuale pure o vom relua n contextul discuiei referitaore la polimorfism. Implicaiile motenirii i polimorfismului asupra unora dintre conceptele prezentate mai sus, vor fi discutate n capitolele urmtoare. Se poate urmri, n continuare, varianta C++ a codului Java pentru schiarea, la nivel nalt de abstractizare, a soluiei pentru problema LSI. Cod C++ care exemplifica utilizarea tipului class pentru rezolvarea problemei LSI #include <iostream.h> #include <conio.h> //Specificare minimala a clasei Nod class Nod { int Data; Nod *Urmatorul; public: //constructorul explicit al clasei Nod Nod(int Numar); //modificator atribut Data void setareData(int Numar); //selector atribut Data int consultareData(); //Modificator atribut Urmatorul void setUrmator(Nod *Element);

//Selector atribut Urmatorul Nod *consultaUrmator(); }; //Specificare clasa abstrcata Lista class Lista { Nod *AStart; Nod *AUltim; int NrElem; public: //Metoda virtuala pura virtual void insereazaDNod(Nod Element)=0; //Metoda virtuala pura virtual void insereazaINod(Nod Element)=0; //Metoda virtuala pura virtual void stergeNodAdr(Nod Element)=0; //Metoda virtuala pura virtual void stergeNodNum(int Nr)=0; //Modificator atribut AStart implementat inline inline void setareAStart(Nod *Element) { AStart=Element; }; //Selector atribut AStart implementat inline inline Nod *consultaAStart() { return AStart; }; //Modificator atribut NrElem implementat inline inline void setareNrElem(int ne) {

NrElem=ne; }; //Selector atribut NrElem implementat inline inline int consultaNrElem() { return NrElem; }; //Modificator atribut AUltim implementat inline inline void setareAUltim(Nod *Element) { AUltim=Element; }; //Selector atribut AUltim implementat inline inline Nod *consultaAUltim() { return AUltim; }; //Metoda publica //Salvare lista in fisier de nume specificat void salveazaInFisier(char numef[]); //Metoda publica //Restaureaza lista folosind informatiile din fisierul //de nume specificat Nod *incarcaDinFisier(char numef[]); }; class ListaOarecare: public Lista { public: //Constructor explicit inline inline ListaOarecare() { setareAStart(NULL); setareAUltim(NULL); setareNrElem(0); cout<<"Constructor ListaOarecare..."<<endl; };

// inline void insereazaDNod(Nod Element) { cout<<"Neimplementata...Lista oarecare"<<endl; setareNrElem(consultaNrElem()+1); } inline void insereazaINod(Nod Element) { cout<<"Neimplementata...Lista oarecare"<<endl; setareNrElem(consultaNrElem()+1); }; inline void stergeNodAdr(Nod Element) { cout<<"Neimplementata...Lista oarecare"<<endl; }; inline void stergeNodNum(int Nr) { cout<<"Neimplementata...Lista oarecare"<<endl; }; }; //Implementare Nod::Nod(int Numar) { Data=Numar; }; //modificator atribut Data void Nod::setareData(int Numar) { Data=Numar; }; //selector atribut Data int Nod::consultareData() { return Data; };

//Modificator atribut Urmatorul void Nod::setUrmator(Nod *Element) { Urmatorul=Element; }; //Selector atribut Urmatorul Nod *Nod::consultaUrmator() { return Urmatorul; }; //Specificare aproape completa a clasei abstracte Lista void Lista::salveazaInFisier(char numef[]) { cout<<"Neimplementata...Lista"<<endl; }; //Metoda publica //Deocamdata neimplementata //Restaureaza lista folosind informatiile din fisierul //de nume specificat Nod *Lista::incarcaDinFisier(char numef[]) { cout<<"Neimplementata...Lista"<<endl; return NULL; }; //Functia principala //Introdusa doar ca parghie pentru verificarea //evolutiei implementarii codului //In stadiul final codul va fi utilizat ca un ansablu //de doua fisiere antet: unul care contine //specificarea claselor si unul care realizeaza implementarea // acestor clase void main() { clrscr();

//Alocarea dinamica a memoriei pentru obiecte //in C++ ListaOarecare *LO=new ListaOarecare(); LO->salveazaInFisier("Test"); LO->stergeNodNum(1); getch(); }; Invit cititorul s depun eforturi personale pentru a aprofunda urmtoarele subiecte: Rolul pointerilor n programarea C++. Legtura cu tablourile. Pointerii la funcii. Clase i funcii prietene. Constructorii i destructorii n programarea C++.

4.2 Comparaie Java/C++ Similitudinile sintactice i semantice care apar n utilizarea limbajelor Java i C++ sunt de necontestat. Similitudinile sunt benefice pentru fanii C++ care migreaz ctre Java sau invers. n categoria similitudinilor putem include: Acelai nucleu de cuvinte cheie utilizat n cele dou limbaje (class, public, protected, private, static); Aceleai principii promovate(ncapsularea, motenirea, polimorfismul); Sintax asemntoare pentru reprezentarea prelucrrilor aferente operaiilor; La capitolul deosebiri semnalez: Modul diferit de utilizare a cuvintelor cheie pentru controlul accesului la resursele unei clase; Modul diferit de rezolvare, n cele dou limbaje, a problemei alocrii dinamice a memoriei pentru obiecte; Diferena de abordare n ceea ce privete motenirea (simpl, n Java, multipl, n C++). Fiecare soluie are fani i critici. Diferene n rezolvarea problemei supradefinirii n cele dou limbaje (metode virtuale, n C++, pur i simplu supradefinire pentru metode cu acelai nume, n clase nrudite, n Java). Soluia Java pare mai elegant. Diferene datorate prezenei pointerilor n C++ i absenei lor din Java. Diferene datorate prezenei claselor i funciior prietene n C++ i absenei lor din Java. Diferene datorate lipsei claselor template din Java. Diferene datorate posibilitii de a suprancrca operatorii n C++ i lipsei de suport n acest sens n Java. Diferene n ceea ce privete controlul complexitii i modularizrii proiectelor n cele dou limbaje (pachete n Java, fiiere header n C++).

Diferene n rezolvarea problemei implementrii operaiilor unei clase. Flexibilitatea i uurina n manevrarea datelor membre statice (Java st mai bine). Posibilitatea de a bloca supradefinirea unei operaii ntr-un lan de derivare (Java ofer suport, C++ nu). Faciliti pentru crearea de clase singleton (Java are suport, C++ nu) etc.

Pentru curioi, apariia limbajului C#, nsoit de o logistic impresionant, ar putea s nsemne rezolvarea multora dintre aceste diferene.

Capitolul 5
Motenirea n programarea orientat pe obiecte Perspectiva Java i Perspectiva C++

5.1 Scurt introducere Am vzut, deja, faptul c unul dintre principiile importante n programarea orientat pe obiecte este principiul motenirii. Aa cum se ntmpl, n general, cu principiile, nici principiul motenirii nu trebuie fetiizat sau bagatelizat. Bagatelizarea lui nseamn, ceva de genul: Hai s facem motenire ca s ne aflm n treab sau ca s vedem dac funcioneaz. Fetiizarea, din contr, ar nsemna Sfinte Sisoe, nimic mai frumos i mai eficient dect aplicarea principiului motenirii la tot pasul.Ambele variante sunt false. Cum am mai spus i alt dat, principiul motenirii este disponibil pentru uz, nu pentru abuz. Ceva mai concret spus: Motenirea este o modalitate performant de reutilizare a codului, dar nu este ntotdeauna cel mai bun instrument pentru ndeplinirea acestui obiectiv. Dac este folosit necorespunztor, programele obinute vor fi destul de fragile. Motenirea poate fi utilizat cu succes n cadrul unui pachet, unde implementrile subclaselor i superclaselor sunt controlate de aceeai programatori. De asemenea, poate fi folosit la extinderea claselor care au fost concepute i documentate exact n acest scop. ns, motenirea unor clase concrete obinuite, n afara granielor unui pachet, poate fi periculoas. Trebuie s subliniez faptul c toate consideraiile pe care le fac n acest paragraf se refer la motenirea neleas ca motenire a implementrii (o clas extinde o alt clas, exprimndu-ne n spirit Java). Se tie, desigur c relaia de motenire opereaz i n relaia dintre interfee, chestiune care nu cade sub incidena observaiilor critice, de mai sus, la adresa motenirii. Defeciunea esenial care poate apare cnd apelm la motenire se refer la faptul c, prin motenire, ncapsularea are de suferit n mod natural. i aceasta deoarece funcionarea corect a unei subclase depinde de detaliile de implementare ale superclasei. Implementarea superclasei se poate modifica de la o versiune a programului la alta, situaie n care , subclasa este expus deteriorrii chiar i n cazul n care codul ei a rmas neatins. Prin urmare, subclasa este obligat s evolueze odat cu superclasa, exceptnd, bineneles, situaia n care autorii superclasei au conceput-o i documentat-o special pentru a fi extins. O regul de bun sim n utilizarea principiului motenirii ne spune c este bine s folosim motenirea numai dac subclasa este, ntr-adevr, un subtip al superclasei. Altfel spus, o clas B trebuie s extind o clas A numai dac ntre cele dou clase exist o relaie de tipul B este un A.

Cu referire la mulimea poligoanelor, prerea unor aa zii specialiti, conform creia putem deriva clasa dreptunghi din clasa triunghi este complet n afara logicii relaiei de motenire. Nici n visul cel mai frumos un dreptungi nu este un triunghi. n schimb, putem spune c triunghiul este un descendent al poligonului, pe motiv c orice triunghi este un poligon, etc. Concluzionnd, motenirea este un aspect important n programare, dar problematic, pentru c poate fi aplicat denaturndu-i esena i pentru c ncalc, n mod natural, regulile ncapsulrii. Motenirea se poate folosi eficient numai cnd ntre superclas i subclas exist o relaie real de genul tipsubtip. Chiar i n acest caz, codul obinut poate fi fragil, avnd probleme cnd apar modificri i fiind predispus la anumite bree n securitatea resurselor unei clase. Evitarea unor astfel de probleme este posibil, n anumite situaii, prin utilizarea compunerii n locul motenirii. Relaia de compunere presupune ca n definiia unei clase A s apar i instane ale altor clase, ale cror servicii urmeaz s fie utilizate de ctre clasa A. Pe de alt parte, este tot att de adevrat faptul c multe probleme rezolvate n C++ cu ajutorul pointerilor, nu ar avea rezolvare echivalent semantic n Java, fr suportul motenirii. 5.2 Motenirea n Java Nu este cazul s revin asupra sintaxei operaiei de motenire n Java. Java a optat pentru un cuvnt cheie pentru a informa compilatorul de intenia unei clase de a moteni proprietile altei clase. Acest cuvnt cheie este extends. Ceea ce trebuie s semnalm ca important, pentru spiritul motenirii n limbajul Java, este faptul c acesta nu ofer suport pentru motenirea multipl, n sensul n care este aceasta neleas n alte limbaje. n Java, o clas poate avea o singur superclas. De aici rezult posibilitatea teoretic de a construi doar ierarhii de clase, cu suportul oferit de motenirea cu extends. Dat fiind faptul c n practic exist i nenumrate situaii n care semantica motenirii multiple se impune ca alternativa cea mai valabil, Java ofer o porti pentru a simula motenirea multipl cu ajutorul interfeelor. Jongleriile care se pot face cu ajutorul interfeelor au depit de mult inteniile iniiale ale specificatorilor limbajului Java. M voi ocupa de acest subiect n paragraful 5.3. Exemplul 5.1 ilustreaz motenirea, avnd ca idee cluzitoare specializarea clasei de baz prin adugare de membri noi. De asemenea, Exemplul 5.1 ilustreaz i cele dou tipuri de casting posibile n programarea orientat pe obiecte din Java. n esen, este vorba despre faptul c, avnd contextul din Figura 17, sunt posibile dou tipuri de casting, principial deosebite. A

B Figura 17. Derivare pretext pentru dou tipuri de casting

Primul tip de casting este implicit. Este vorba despre conversia unei variabile obiect de tip B la o variabil de tip A. Motivul pentru care acest tip de conversie este implicit este simplu: resursele lui A se regsesc printre resursele lui B. Astfel c, atunci cnd B este coerent din punct de vedere al strii, nu exist nici un motiv ca A s fie altfel, n urma conversiei. Al doilea tip de casting necesit acordul explicit al programatorului pentru a fi efectuat i ntr-un anumit sens, asumarea rspunderii pentru aceast conversie. Este vorba de conversia unei variabile obiect de tip A la o variabil de tip B. De ce este necesar acordul? Foarte simplu: resursele lui A sunt incluse printre resursele lui B. Conversia de mai sus este posibil s aduc variabila de tip B ntr-o stare improprie pentru utilizarea unor metode, deoarece unele date membre pot rmne neiniializate adecvat. Cu toate acestea, down casting-ul este esenial n programarea generic, asupra creia vom reveni ntr-un curs special. Exemplul 5.1 //Clasa de baz //Modeleaz informaional obiectul Fruct //Potrivit abordrii din acest cod //metoda consultaTipFruct() nu poate fi //redefinit n descendeni class Fruct { private int tipfruct; Fruct(int t) { tipfruct=t; System.out.println("Constructor Fruct..."); } final int consultaTipFruct() { return tipfruct; } } //Subclas a clasei Fruct //Para este un fruct->este respectat spiritul natural //al motenirii //Se adaug noi atribute informaionale //Se adaug metode specifice //Este un exemplu clasic de specializare prin adaugare class Para extends Fruct { private double greutate; private int forma; Para(int t,double g,int f)

{ super(t); System.out.println("Constructor Para..."); greutate=g; forma=f; } double consultaGreutate() { return greutate; } int consultaForma() { return forma; } } //Subclas a clasei Fruct //Portocala este un fruct->este respectat spiritul natural //al motenirii //Se adaug noi atribute informaionale //Se adaug metode specifice //Este un exemplu clasic de specializare prin adugare class Portocala extends Fruct { private int tipcoaja; Portocala(int tf,int tc) { super(tf); System.out.println("Constructor Para..."); tipcoaja=tc; } int consultaTipCoaja() { return tipcoaja; } } //Clasa care utilizeaz ierarhia de clase de mai sus //Tipurile Para i Portocala au acelai supertip //dar sunt incompatibile la atribuire //deoarece sunt pri ale unor lanuri de derivare

//diferite public class Mosten1 { public static void main(String[] s) { //Declarare variabil referin //avnd tipul clasei rdcin Fruct of; //Declarare i alocare variabil referin //de tip Para Para obpara=new Para(1,1.5,2); //Declarare i alocare variabil referin //de tip Portocala Portocala obport=new Portocala(10,1); //Utilizare normal a variabilei de tip Para System.out.println("Para ..creata ca referinta Para"); System.out.println("Tip fruct:"+ obpara.consultaTipFruct()); System.out.println("Greutate fruct:"+obpara.consultaGreutate()); System.out.println("Forma fruct:"+obpara.consultaForma()); //Utilizare normala a variabilei de tip Portocala System.out.println("Portocala creata ca referinta Portocala"); System.out.println("Tip fruct:"+obport.consultaTipFruct()); System.out.println("Tip coaja:"+obport.consultaTipCoaja()); //Exemplu de Up casting (implicit) of=new Para(1,2.5,3); //Exemplu de Down casting (explicit); //Foarte util in programarea generica obpara=(Para)of; //Utilizare variabile referin //setate prin casting explicit System.out.println("Para ...creata ca referinta Fruct"); System.out.println("Tip fruct:"+obpara.consultaTipFruct()); System.out.println("Greutate fruct:" +obpara.consultaGreutate()); System.out.println("Forma fruct:"+obpara.consultaForma()); }

}; Exemplul 5.2 ilustreaz specializarea prin redefinirea metodelor, ca baz pentru comportamentul polimorfic al obiectelor n Java. Mecanismul polimorfismului l voi pune n discuie n Capitolul 6. Exemplul 5.2 //Clasa de baza //Modeleaz informional obiectul Fruct //Metoda consultaTipFruct() se va redefini //in descendentul Para class Fruct { private int tipfruct; Fruct(int t) { tipfruct=t; System.out.println("Constructor Fruct..."); } int consultaTipFruct() { return tipfruct; } } //Subclas a clasei Fruct //Para este un fruct->este respectat spiritul natural //al motenirii //Se adaug noi atribute informaionale //Se redefinete metoda consultaTipFruct() //Este un exemplu clasic de specializare prin redefinire class Para extends Fruct { private double greutate; private int forma; Para(int t,double g,int f) { super(t); System.out.println("Constructor Para..."); greutate=g; forma=f; }

int consultaTipFruct() { System.out.println("Consultare tip fruct..redefinire Para"); return super.consultaTipFruct(); } double consultaGreutate() { return greutate; } int consultaForma() { return forma; } } //Subclas a clasei Fruct //Portocala este un fruct->este respectat spiritul natural //al motenirii //Se adaug noi atribute informaionale //Se redefinete metoda consultaTipFruct() //Este un exemplu clasic de specializare prin redefinire class Portocala extends Fruct { private double greutate; private int forma; Portocala(int t,double g,int f) { super(t); System.out.println("Constructor Portocala..."); greutate=g; forma=f; } int consultaTipFruct() { System.out.println("Consultare tip fruct..redefinire Porto"); return super.consultaTipFruct(); } double consultaGreutate() {

return greutate; } int consultaForma() { return forma; } } public class Mosten2 { public static void main(String[] s) { //Declarare variabil referin de tipul clasei rdcin Fruct of; //Alocare referin utiliznd constructorul unei subclase //a clasei rdcin (Para) of=new Para(1,2.75,3); System.out.println("Para ...creata ca referinta Fruct"); //Utilizare, de fapt, n spirit polimorfic //a variabilei definite i alocate mai sus System.out.println("Tip fruct:"+of.consultaTipFruct()); //Alocare referin utiliznd constructorul unei subclase //a clasei rdcin (Portocala) of=new Portocala(2,0.75,4); System.out.println("Portocala ...creata ca referinta Fruct"); //Utilizare, de fapt, n spirit polimorfic //a variabilei definite si alocate mai sus System.out.println("Tip fruct:"+of.consultaTipFruct()); } }; 5.3 Motenirea multipl n Java Dei o modalitate comod de a iei din impas n anumite situaii, motenirea multipl este criticat pentru confuziile pe care le poate genera, dac este utilizat fr un efort de inventariere, adecvat focalizat asupra proprietilor claselor candidate la calitatea de superclase pentru o clas derivat din ele. Duplicarea cmpurilor i a metodelor, precum i violarea cras a ncapsulrii sunt principalele motive de ngrijorare cnd este vorba de utilizarea motenirii multiple. Aadar, ne aflm n situaia din Figura 18.

C Figura 18. Motenirea multipl Dac semantica din domeniul problemei impune o asemenea abordare, din punct de vedere conceptual, atunci n Java soluia pentru posibilitatea de a spune despre C c este A sau B sau A i B o reprezint utilizarea interfeelor. Interfaa este o varietate de clas, caracterizat prin faptul c poate declara metode abstracte i publice i, dac este necesar, variabile care sunt considerate, implicit, de tip public, static i final, deci constante. n foarte mare msur, comportamentul unei interfee este asemntor comportamentului unei clase. Att de profund este asemnarea nct, n anumite situaii interfeele i clasele se pot substitui reciproc. Pentru a nelege modul de lucru cu interfeele consider eseniale urmtoarele precizri: 1. Mai nti trebuie s nvm cum se declar o interfa. Aceasta este o problem de sintax, mult mai simpl dect problema conceptual, care trebuie clarificat nainte de a ajunge la sintax. Din punct de vedere conceptual, tipul de raionament pe care l facem seamn cu cel pe care l facem cnd specificm o clas abstract care are toate metodele virtuale pure, n C++ sau abstracte n Java. Din punct de vedere sintactic, avem cadrul: interface <Nume_interfa> [extends <List _de_interfee>] { <Signaturi de metode> } Se observ, deja, amnuntul care deosebete mecanica utilizrii claselor de mecanica utilizrii interfeelor, anume, suportul pentru motenire multipl n cazul interfeelor. Pe acest amnunt se bazeaz alternativa Java la motenirea multipl relativ la clase. 2. n al doilea rnd, utilizarea unei interfee este posibil, n mai multe moduri, dup ce am specificat i implementat o clas care o utilizeaz. Ne amintim de sintaxa: class <Nume_clas> [extends Nume_superclas] implements <List de interfee> { <Date membre> <Funcii membre>

}; Dup cum se poate observa, o clas poate implementa mai multe interfee, depindu-se, astfel, restricia Java n legtur cu motenirea multipl n relaia dintre clase. S mai subliniez i faptul c o clas care implementeaz una sau mai multe interfee poate avea cel mult un strmo, eventual nici unul. Disciplina astfel introdus este, sper, clar: metodele unei clase (care are, eventual un strmo) pot fi acoperitoare ca specificare i implementare pentru listele de metode ale unor de interfee. n acest mod, avem la dispoziie un mecanism de a vedea, din unghiuri diferite, ansamblul resurselor unei clase. Este clar c soluia Java determin programatorii s mediteze mai atent nainte de a face jonciunea cu mai multe interfee. 3. Utilizarea efectiv a interfeelor este divers: ca tipuri definitoare pentru referine la obiecte, ca tipuri implicate n casting, ca tipuri utile n programarea generic, etc. Cteva modaliti de utilizare a interfeelor se pot vedea n Exemplul 5.3 i n Exemplul 5.4. Exemplul 5.3 //Interfata I1 //Expune functia f1() interface I1 { public void f1(); } //Interfata I2 //Expune functia f2() interface I2 { public void f2(); }; //Interfata I12 //Extinde interfetele I1 si I2 //Exemplu de mostenire multipla //a interfetelor interface I12 extends I1,I2 { public void f3(); } //Clasa A implementeaza interfata I1 class A implements I1 { public A()

{ System.out.println("AAAA..."); }; public void f1() { System.out.println("f1...."); }; }; //Clasa B implementeaza interfata I2 class B implements I2 { public B() { System.out.println("BBBB..."); }; public void f2() { System.out.println("f2...."); }; }; //Clasa C implementeaza interftata I12 //Evident,functiile expuse de interfetele //I1 si I2 sunt implementate si de catre //clasa C //Deoarece I12 este derivata din I1 si I2 //ni se ingaduie sa privim instantele de tip //I12 ca fiind de tip I1 sau I2, dupa cum //este in interesul nostru class C implements I12 { public C() { System.out.println("CCCC..."); }; public void f3() { System.out.println("f3..."); }; public void f1() {

System.out.println("f1...."); }; public void f2() { System.out.println("f2...."); }; }; public class TestInterf { public static void main(String args[]) { //Crearea unui obiect de tip I1 //utilizand clasa A I1 ob1=new A(); System.out.println("Ura..."); //Utilizarea obiectului de tip I1 ob1.f1(); //Crearea unui obiect de tip I12 //utilizand clasa C I12 ob12=new C(); //Utilizarea obiectului de tip I12 ob12.f1(); ob12.f2(); ob12.f3(); //Crearea unui obiect de tip I1 //utilizand clasa C I1 ob2=new C(); //Utilizare obiect de tip I1 //creat cu suport C ob2.f1(); //Down casting dirijat de //interfete ob2=(I1)ob12; ob2.f1();

} } Exemplul 5.4 //Interfata IConst care declara //doua constante si o metoda interface IConst { int Numar=100; String s="Bine ati venit!"; void ftest(); } //A foloseste interfata IConst //Este obligata sa implementeze ftest() class A implements IConst { A() { System.out.println("Constructor.."); }; public void ftest() { System.out.println("ftest() la lucru..."); }; } public class TInterf1 { public static void main(String s[]) { //Creare variabila obiect //de tip A //se mostenesc constantele //interfetei IConst A va=new A(); System.out.println(va.Numar); System.out.println(va.s); va.ftest(); //Creare variabila obiect //de tip IConst IConst iva=new A();

System.out.println(iva.Numar); System.out.println(iva.s); iva.ftest(); }; } nainte de a pune punct subiectului s mai precizez urmtoarele: Un program nu poate crea instane dintr-o interfat. Toate metodele unei interfee sunt implicit publice i abstracte. Nici un alt tip nu este permis. Toate metodele trebuie s fie implementate de clasa care utilizeaz interfaa.

5.4 Motenirea n C++ C++ propune caracterul : ca separator ntre clasa de baz i superclas pentru a indica compilatorului necesitatea asigurrii logisticii specifice unei operaii de motenire. Sintaxa efectiv a motenirii ne ofer chiar mai mult dect att, dup cum se poate vedea mai jos. class <Nume_clasa>[[:][<Modificator de protecie>] <Lista de superclase>] { <Date membre> <Funcii membre> }; Din sintaxa de mai sus, se poate deduce faptul c o clas A poate moteni o clas B ntr-un mod mai flexibil dect n Java. Modificatorul de protecie, care poate apare, opional, ntre operatorul de derivare : i superclas introduce o serie de combinaii ntre vizibilitatea resurselor n clasa motenit i modificatorul de acces asociat operaiei de motenire, combinaii care pot fi acoperitoare pentru cele mai neateptate semantici ntlnite n diverse probleme, dup cum rezult i din Tabelul 5.1. Tipul de acces al elementului n clasa de baz Private Protected Public Private Protected Public Modificatorul de Accesul n clasa protecie asociat clasei de derivat la element baz la definirea clasei Private interzis Private private Private private Public interzis Public protected Public public Tabelul 5.1 Problematica accesului la membrii unei clase derivate Un exemplu interesant este rspunsul C++ la urmtoarea problem:

Dat clasa A, ce sintax de derivare folosim pentru ca o clas B s moteneasc doar implementarea interfeei clasei A? Mai exact spus, B s moteneasc implementarea interfeei clasei A dar s nu o poat exporta direct ctre nici un tip de client. Evident, n practic, exist numeroase astfel de situaii n care dorim s folosim interfeele unor clase fr a le expune direct clienilor. Soluia tehnic a acestei probleme este motenirea privat a clasei A de ctre clasa B (varianta a 3-a din Tabelul 5.1), dup cum se poate observa i n Exemplul 5.5. Exemplul 5.5 #include <iostream.h> #include <conio.h> //Superclasa creia vrem s-i motenim //doar implementarea interfeei //adic: // constructorul Baza() // metoda setarenr() // metoda citestenr() class Baza { int nr; public: Baza() { nr=0; }; void setarenr(int n) { nr=n; }; int citestenr() { return nr; }; }; //Clasa derivat Urmas care //motenete privat superclasa Baza class Urmas:private Baza

{ public: Urmas() { }; void setaren(int n) { setarenr(n); }; int afisarenr() { return transform(); }; int transform() { return citestenr()*citestenr(); }; }; void main() { Urmas ourm; clrscr(); ourm.setaren(100); cout<<ourm.transform(); ourm.afisarenr(); //Ar fi ilegal enunul ourm.citestenr()n //acest program deoarece ar nsemna s expun clienilor //nsi interfaa clasei Baza //ceea ce contrazice intenia noastr getch(); }; Conform uzanelor din C++, motenirea implicit a unei clase de ctre alt clas este motenirea privat. Prin urmare, cuvntul cheie private n derivarea din Exemplul 5.5 este redundant, fiind pus n scop didactic. Dup cum anun i sintaxa prezentat la nceputul paragrafului 5.4, n C++ se poate face motenire multipl. Extrem de avantajos, cu o condiie: cel ce face motenire multipl s o planifice temeinic nainte de a o folosi efectiv. Discuiile sunt oricum prea vaste pe aceast tem. Pentru un programator la nceput de drum n programarea orientat pe obiecte, motenirea multipl trebuie s fie o soluie extrem, la care se apeleaz dup foarte multe experimente i cercetri asupra unor abloane n materie de motenire multipl. Deranjul provocat de motenirea multipl nu este numai la utilizare ci, mai ales, n procesul de programare. Practic, fiecare clas poate fi considerat ca avnd un API propriu. Combinarea a dou sau a mai multor clase ntro clas nou, prin motenire, nseamn combinarea API-urilor. n cazul proiectelor mari, este uor de prevzut ce se

poate ntmpla dac nu asigurm API-uri dizjuncte din punct de vedere al serviciilor expuse sau, n cel mai fericit caz, care s conduc la suprascriere n clasa colectoare. Alta este situaia n C++ i n ceea ce privete transmiterea de parametri ctre constructorii superclasei. Ceea ce n Java era rezolvat prin apelarea prioritar a constructorului superclasei, referit ca super, n C++ trebuie s respecte sintaxa: <Clasa derivata> ([<Lista parametri>]): <Superclasa 1>(<Lista de argumente 1>), <Superclasa 2>(<Lista de argumente 2>), <Superclasa 3>(<Lista de argumente 3>), : <Superclasa N>(<Lista de argumente N>) { //Corpul constructorului } Acestea sunt deosebirile de abordare ntre C++ i Java n problema motenirii. Cum beneficiem de motenire? Aceasta este o ntrebare cu un rspuns vast, niciodat lmurit pn la capt. Evident, vom avea compatibilitate la atribuire ntre strmoi i descendenii lor (vezi principiul substituiei!), deci vom putea beneficia de casting implicit. Cu riscurile de rigoare se ngduie i casting-ul explicit. n ncheiere, s ne imaginm c am implementat toate metodele claselor care constituie soluia problemei LSI. Presupunnd c am declarat un pointer la clasa Lista, atunci, compatibilitatea acestui pointer cu oricare pointer la descendenii ListaOarecare i ListaOrdonata va permite schimbarea fr probleme a modului de operare asupra unei liste, n anumite circumstane, desigur. O instan de tip ListaOrdonata trebuie s aib un switch care abstractizeaz instaurarea sau nu a relaiei de ordine ntre elementele unei liste. n caz c nu s-a instaurat aceast ordine, se activeaz automat refacerea strii de ordine, ori de cte ori este cazul. Evident, problema apare numai atunci cnd facem conversii de la oarecare la ordonat. Invers nu este nici o dram.

Capitolul 6
Polimorfismul n programarea orientat pe obiecte Perspectiva Java i perspectiva C++

6.1 S reamintim, pe scurt, ce este polimorfismul.


Dup cum stau lucrurile n limbajele de programare orientate pe obiecte, polimorfismul este singurul principiu a crui for se manifest n timpul execuiei programelor. Valoarea principiului motenirii este esenial concentrat n posibilitatea de a reutiliza efortul de dezvoltare a unui sistem soft. ncapsularea este, de asemenea, un principiu a crui manifestare nu este evident dect de pe poziia de programator, n esen. Ar fi, ns, nedrept s nu subliniem c att ncapsularea ct i motenirea trebuie s fie mnuite cu mult abilitate pentru a obine efecte polimorfice de mare subtilitate i utilitate. ncercnd o definiie a polimorfismului, independent de limbajul de programare i din punctul de vedere al programatorului care beneficiaz de el, numim polimorfism posibilitatea ca un apel de funcie (metod , operaie) s genereze rspunsuri diferite n funcie de contextul n care a fost formulat.

6.2 Tipuri de polimorfism la nivelul limbajelor de programare. Exemplificare n C/C++

Nevoia de polimorfism, resimit acut mai ales n programare, este n mare msur sinonim cu nevoia de confort. n stadiul n care se afl, actualmente, realizrile specialitilor n materie de polimorfism la nivelul limbajelor de programare, putem semnala urmtoarele tipuri importante de polimorfism: Polimorfismul orientat pe suprascrierea funciilor n programarea clasic. Polimorfismul orientat pe suprascrierea funciilor n cadrul definiiei unei clase. Polimorfsimul orientat pe suprancrcarea operatorilor n programarea orientat pe obiecte. Polimorfismul orientat pe redefinirea funciilor n programarea orientat pe obiecte, ntr-un lan de derivare.

Indiferent de tipul lui, polimorfismul de calitate cere investiie de timp i creativitate, pe moment, n beneficiul unor viitoare reutilizri, cu minimum de efort din partea clienilor. Polimorfismul orientat pe suprascrierea funciilor n programarea clasic Aceast form de polimorfism este, practic, cea mai veche. Ea presupune posibilitatea de a scrie funcii care au acelai nume, retuneaz acelai tip de dat, dar se pot deosebi prin tipul i numrul parametrilor. Aceast posibilitate este ilustrat n Exemplul 6.1, perfect legal n programarea n limbajele C/C++. Exemplul 6.1 //Suprascrierea funciilor n programarea clasic n C //Sunt specificate i implementate dou versiuni, //diferite prin lista de parametri ale funciei suma() #include <iostream.h> #include <conio.h> //Prima versiune a funciei suma() //Parametrul s este transmis prin referin void suma(float &s,int o1,int o2) { s=o1+o2; }; //A doua versiune a funciei suma() //Parametrul s este transmis prin referin void suma(float &s,int o1, int o2, int o3) { s=o1+o2+o3; }; void main()

{ float st; clrscr(); //Utilizarea versiunii 2 suma(st,12,13,14); cout<<st<<endl; //Utilizarea versiunii 1 suma(st,12,13); cout<<st; getch(); }; Care sunt observaiile care se impun? Mai nti, este de remarcat faptul c trebuie s existe un programator care este suficient de informat cu privire la variaiile de comportament ale unei funcii avnd acelai nume i care returneaz acelai tip de dat. Dei nu excludem posibilitatea de a ntlni o astfel de situaie i n alte contexte, programatorii versai tiu foarte bine ct de mult valoreaz versionarea unei funcii n programarea generic, atunci cnd soluia template-urilor prezint unele inconveniente. n al doilea rnd, dac versionarea este realizat cu sim de rspundere, utilizarea diferitelor versiuni n diferite situaii este extrem de comod i benefic, gsind o soluie de partajare a codului versiunilor ntre mai multe programe. n al treilea rnd, nu putem trece cu vederea faptul c la compilare este realizat legarea unui apel de versiune de codul aferent (acest gen de legare se numete early binding). Polimorfismul orientat pe suprascrierea funciilor n cadrul definiiei unei clase Aceast form de polimorfism satisface unele cerine de versionare a comportamentului operaiilor unei clase, n spiritul celor spuse relativ la suprascrierea n stil clasic a funciilor. Dup cum se anticipeaz n Exemplul 6.2 (de cod C++), acest tip de polimorfism poate fi combinat cu polimorfismul orientat pe supradefinirea metodelor ntr-un lan de derivare. Exemplul 6.2 #include <iostream.h> #include <conio.h> //Clasa de baza class Super { int numar; public: Super(int n) { numar=n; };

//Versiunea 1 a functiei f1() void f1() { cout<<"Super::Functie de test"<<endl; getch(); }; //Versiunea 2 a functiei f1() // Suprascrie prima versiune a lui f1() inauntrul //clasei Super //In raport cu clasa Baza f1()este virtuala //Deci urmeaza sa fie supradefinita virtual void f1(int n) { cout<<"Super::Numar: "<<n<<endl; getch(); }; }; //Clasa derivata class Baza:public Super { public: Baza(int n):Super(n) { }; void f1(int n) { cout<<"Baza::Numar: "<<n<<endl; getch(); }; }; void main() { //Pointer la Super Super *PSuper; //Alocare dinamic a memoriei pentru pointer-ul Psuper //n context Super PSuper=new Super(10);

clrscr(); //Utilizare Psuper; apelare succesiva a doua versiuni ale //functiei f1() PSuper->f1(); PSuper->f1(10); delete PSuper; //Alocare dinamic a memoriei pentru pointer-ul PSuper //n context Baza PSuper=new Baza(12); PSuper->f1(12); delete Psuper; }; Polimorfsimul orientat pe suprancrcarea operatorilor n programarea orientat pe obiecte Subiect ocolit de specificatorii limbajului Java, ns generator de satisfacii deosebite pentru programatorii n C++. Ideea de baz const n faptul c este la latitudinea celor care programeaz orientat pe obiecte n C++ s redefineasc comportamentul unui foarte mare numr de operatori (+, -, *, >>, <<, new, delete, etc.). Atenie! Nu poate fi schimbat nici aritatea nici prioritatea operatorilor predefinii, prin suprancrcare. Protocolul de suprancrcare a unui operator, astfel nct acesta s opereze asupra obiectelor unei clase este urmtorul: 1. Definiia clasei trebuie s conin o funcie operator membru sau o funcie operator prieten, avnd sintaxa special: Varianta funcie membr <Tip returnat> operator # (<Lista de argumente>); sau Varianta funcie friend friend <Tip returnat> operator # (<Lista de argumente>); n aceast sintax, atrag atenia cuvntul cheie operator (care informeaz compilatorul c funcia suprancarc un operator) i caracterul # care semnific un substitut pentru operatorul pe care dorii s-l suprancrcai, altul dect: . , * , :: , ? . De remarcat faptul c, alegnd varianta funcie membr, un operator binar va fi specificat ca o funcie cu un parametru, care va indica operandul din stnga, operandul din dreapta fiind vizibil prin intermediul pointerului this. De asemenea, dac alegem varianta funcie membr, un operator unar va fi implementat ca o funcie

fr parametri, pointerul this permind referirea operandului. Defeciunea n cazul utilizrii unei funcii membru pentru suprancrcarea unui operator este clar: parametrul din stnga trebuie s fie un obiect, nu poate fi o constant. Este evident c, n aceste condiii funciile prietene sunt de preferat. 2. Funciile operator se vor implementa folosind una din sintaxele:

<Tip returnat> <Nume clas>::operator # (<Lista de argumente>) { // Corp funcie operator specificat ca membr }; sau <Tip returnat> operator # (<Lista de argumente>) { // Corp funcie operator specificat ca prieten }; Lucrurile pot fi nelese i mai bine, urmrind Exemplul 6.3 (cod C++). Exemplul 6.3 #include<conio.h> #include<iostream.h> //Clasa complex contine functia operator + ca membru //operatorul + este extins la multimea numerelor complexe //cu ajutorul unei metode membru a clasei complex //Clasa complex contine functia operator - ca functie friend //operatorul - este extins la multime numerelor complexe //cu ajutorul unei metode friend class complex { float x,y; public: complex(){}; complex(float a,float b) { static int i; i++; clrscr(); cout<<"Lucreaza constructorul...Obiectul->:"<<i;

getch(); x=a; y=b; }; void disp_nc(); //prototipul operatorului + complex operator+(complex &op2); //prototipul operatorului friend complex operator-(complex &op1,complex &op2); }; void complex::disp_nc() { cout<<x<<"+i*"<<y; }; //Implementare operator + //Aceasta sintaxa transforma apelul <ob1+ob2> //in +(ob2), ob1 fiind accesibil prin pointerul //special <this> complex complex::operator+(complex &op2) { complex temp; temp.x=op2.x+x; temp.y=op2.y+y; return temp; }; complex operator -(complex &op1,complex &op2) { complex temp; temp.x=op1.x-op2.x; temp.y=op1.y-op2.y; return temp; }; void main() { complex tamp1,tamp2; complex *pod1,*pod2;

complex ob1(10,10),ob2(11,11); clrscr(); gotoxy(20,10);cout<<"Primul numar complex ->:"; ob1.disp_nc(); getch(); gotoxy(20,11);cout<<"Al doilea numar complex->:"; ob2.disp_nc(); getch(); ob1=ob1+ob2; gotoxy(20,13);cout<<"Suma numerelor complexe->:"; ob1.disp_nc(); getch(); pod1=new complex(200,200); pod2=new complex(300,300); tamp1=*pod1; clrscr(); gotoxy(20,10);cout<<"Al treilea numar complex tamp1.disp_nc(); ->:";

tamp2=*pod2; gotoxy(20,11);cout<<"Al patrulea numar complex ->:"; tamp2.disp_nc(); gotoxy(20,14);cout<<"Suma numerelor complexe->:"; tamp1=tamp1+tamp2; tamp1.disp_nc(); tamp1=*pod1; tamp2=*pod2; tamp1=tamp1-tamp2; gotoxy(20,15);cout<<"Diferenta numerelor complexe->:"; tamp1.disp_nc(); getch(); } Polimorfismul orientat pe redefinirea funciilor n programarea orientat pe obiecte, ntr-un lan de derivare Este element suport esenial pentru specializarea claselor ntr-un lan de derivare, specializare care se realizeaz prin redefinirea comportamentului unor metode ale strmoilor. Pentru a se mbina extinderea comportamentului cu

reutilizarea codului, este de dorit ca redefinirea comportamentului s planifice utilizarea comportamentului versiunii din strmo. Exemplul 6.4 ne arat cum se pune problema redefinirii n C++. Exemplul 6.4 #include <iostream.h> #include <conio.h> //Structura suport pentru pastrarea //coordonatelor varfurilor poligoanelor struct Varf { int x,y; Varf *Legs; }; //Clasa Poligon //Clas abstracta->nu are constructor i destructor //Furnizeaz prototipurile metodelor definitie() i arie() //ca metode virtuale pure. //Furnizeaz implementarea pentru metodele: // perimetru() // ElibMem() // setare_pvarfuri() // consultare_pvarfuri() // setare_nrvarfuri() // consulatre_nrvarfuri() class Poligon { Varf *pvarfuri; int nrvarfuri; public: //Metode virtuale pure //Vor fi redefinite n descendeni virtual void definitie()=0; virtual float arie()=0; float perimetru(); void ElibMem(); void setare_pvarfuri(Varf *p); Varf * consultare_pvarfuri(); void setare_nrvarfuri(int nv)

{ nrvarfuri=nv; }; int consultare_nrvarfuri() { return nrvarfuri; }; }; float Poligon::perimetru() { cout<<"perimetru(): "; cout<<"Calculul perim. este neimplem... Poligon" <<endl; getch(); return 0; }; void Poligon::ElibMem() { Varf*pwork; while (pvarfuri!=NULL) { pwork=pvarfuri->Legs; delete pvarfuri; pvarfuri=pwork; }; }; void Poligon::setare_pvarfuri(Varf *p) { pvarfuri=p; }; Varf * Poligon::consultare_pvarfuri() { return consultare_pvarfuri(); }; //Clasa Triunghi //Clas concret avnd ca superclas clasa Poligon //Redefinete comportamentul metodelor:

// definitie(); arie() //Furnizeaz constructor i destructor class Triunghi:public Poligon { public: Triunghi(Varf *pt,int tnrv) { setare_pvarfuri(pt); setare_nrvarfuri(tnrv); cout<<"Constructor Tringhi..."<<endl; }; virtual ~Triunghi() { cout<<"Destructor Triunghi..."<<endl;; ElibMem(); }; //Redefinire metode void definitie(); float arie(); }; void Triunghi::definitie() { cout<<"definitie(): "; cout<<"Triunghiul este poligonul cu trei laturi"<<endl; getch(); }; float Triunghi::arie() { cout<<"arie(): "; cout<<"Neimplementata deocamdata...Triunghi"<<endl; getch(); return 0; }; class Patrulater:public Poligon { public: Patrulater(Varf *pt,int tnrv)

{ setare_pvarfuri(pt); setare_nrvarfuri(tnrv); cout<<"Constructor Patrulater..."<<endl; }; //Destructor virtual virtual ~Patrulater(); void definitie(); float arie(); }; Patrulater::~Patrulater() { ElibMem(); cout<<"Destructor Patrulater..."<<endl; }; void Patrulater::definitie() { cout<<"definitie(): "; cout<<"Patrulaterul este poligonul cu patru laturi"<<endl; getch(); }; float Patrulater::arie() { cout<<"arie(): "; cout<<"Neimplementata deocamdata...Patrulater"<<endl; getch(); return 0; }; class Paralelogram:public Patrulater { public: Paralelogram(Varf *pt,int tnrv):Patrulater(pt,tnrv) { cout<<"Constructor Paralelogram..."<<endl; }; //Destructor virtual

virtual ~Paralelogram() { ElibMem(); cout<<"Destructor Paralelogram..."<<endl; }; //Redefinire metode void definitie(); float arie(); }; void Paralelogram::definitie() { cout<<"definitie(): "; cout<<"Paralelogramul este patrulat. cu laturile paral. doua cate doua"<<endl; getch(); }; float Paralelogram::arie() { cout<<"arie(): "; cout<<"Neimplementata deocamdata...Paralelogram"<<endl; getch(); return 0; }; class Dreptunghi:public Paralelogram { public: Dreptunghi(Varf *pt,int tnrv):Paralelogram(pt,tnrv) { cout<<"Constructor dreptunghi..."<<endl; }; virtual ~Dreptunghi() { ElibMem(); cout<<"Destructor Dreptunghi..."<<endl; }; void definitie(); float arie(); };

void Dreptunghi::definitie() { cout<<"definitie(): "; cout<<"Dreptunghiul este paralelogramul cu un unghi drept"; cout<<endl; getch(); }; float Dreptunghi::arie() { cout<<"arie(): "; cout<<"Neimplementata deocamdata...Dreptunghi"<<endl; return 0; }; void main() { Poligon *RefPol; Patrulater *RefPatr; clrscr(); RefPol=new Triunghi(NULL,3); RefPol->arie(); RefPol->definitie(); RefPol->perimetru(); cout<<endl; delete RefPol; RefPatr=new Patrulater(NULL,4); RefPatr->arie(); RefPatr->definitie(); RefPatr->perimetru(); cout<<endl; delete RefPatr; RefPatr=new Paralelogram (NULL,4); RefPatr->arie(); RefPatr->definitie(); RefPatr->perimetru(); delete RefPatr; }; Pentru o mai bun nelegere a Exemplului 6.4, sunt necesare o serie de precizri n ceea ce privete genul de polimorfism ilustrat.

Mai nti, din punct de vedere sintactic, trebuie s observm faptul c informm compilatorul de intenia de redefinire a unei metode n aval (ntr-un lan de derivare) prin specificarea acesteia n clasa gazd ca metod virtual sau ca metod virtual pur. Prototipul unei metode virtuale are sintaxa: virtual <Tip returnat> <Nume metoda>([<Lista de parametri>]); Prototipul unei metode virtuale pure are sintaxa: virtual <Tip returnat> <Nume metoda>([<Lista de parametri>])=0; Clasele care conin cel puin o metod virtual pur sunt clase abstracte, deci nu pot avea instane directe, neavnd nici constructori. n schimb, clasele abstracte pot fi folosite pentru a declara referine ctre descendeni, ceea ce exte extrem de folositor dac dorim polimorfism. De remarcat c redefinirea se bazeaz pe o restricie important: n procesul de redefinire se conserv signatura (numrul de parametri, tipul lor i tipul returnat). Odat ce o metod a fost declarat virtual sau virtual pur, compilatorul tie c aceast metod este posibil s fie redefinit n descendeni i, de asemenea, compilatorul tie c pentru clasa care conine metode virtuale i pentru toate clasele descendente ei, la crearea primului obiect, constructorul va crea i tabela VMT (Virtual Methode Table), o structur partajat de toate obiectele unei clase, folosit de sistem pentru a realiza genul de legare a unui apel de codul contextual, numit late binding. Prin urmare, atunci cnd se creaz un obiect, al crui tip definitor este undeva ntr-un lan de derivare, dac n amonte a existat intenie de redefinire a unor metode, sistemul va crea, numai n cazul primului obiect de tipul respectiv, o tabel care conine adresele metodelor virtuale ale clasei. Aceste adrese vor fi utilizate n procesul de late binding. S mai observm faptul c, fr a fi prefixai de cuvntul cheie virtual, destructorii sunt apelai pe principiul ntotdeauna lucreaz constructorul tipului definitor al unei variabile obiect sau al unui pointer la un obiect, ceea ce nseamn un gen de legare static a destructorului. Dac dorim legare dinamic, atunci destructorul este declarat ca virtual. Efectul poate fi urmrit n Exemplul 6.4.

6.2 Polimorfismul n context Java


Java implementeaz principiul polimorfismului la scara posibilitilor proprii. n Java nu avem dect programare orientat pe obiecte, oricare ar fi calitatea acesteia. Astfel c se ofer suport pentru polimorfism orientat pe suprascrierea funciilor i polimorfism orientat de supradefinire. Java nu ofer sintax pentru suprancrcarea operatorilor, deci nu este posibil polimorfismul aferent. Merit s remarcm faptul c supradefinirea n Java este mai simpl dect n C++, din punct de vedere sintactic vorbind. Pur i sumplu, dac compilatorul sesizez c n amontele unui lan de derivare exist o metod care este supradefinit n aval, atunci compilatorul genereaz informaii necesare pentru realizarea legrii la execuie. Cerina conservrii signaturii n procesul de supradefinire este prezent i n Java. Un model de utilizare a polimorfismului se poate observa n Exemplul 6.5.

Exemplul 6.5 //Clasa radacina class Poligon { private String definitie; public Poligon(String d) { definitie=new String(d); }; public String citesteDefinitie() { return definitie; }; //Metoda va fi supradefinita in descendenti public void arie() { System.out.println("Poligon...neimplementata!"); }; }; class Triunghi extends Poligon { public Triunghi(String d) { super(d); }; //Supradefinire public void arie() { System.out.println("Triunghi...neimplementata!"); }; }; class Patrulater extends Poligon { public Patrulater(String d) { super(d);

}; //Supradefinire public void arie() { System.out.println("Patrulater...neimplementata!"); }; }; public class Polimorf { public static void main(String[] s) { //Referinta la radacina Poligon PRef; //Alocare in context Poligon PRef=new Poligon("Linie franta inchisa"); System.out.println(PRef.citesteDefinitie()); //Sintaxe la utilizare este aceeasi in cele trei contexte PRef.arie(); System.out.println(""); //Alocare in context Triunghi PRef=new Triunghi("Poligonul cu trei laturi"); System.out.println(PRef.citesteDefinitie()); PRef.arie(); System.out.println(""); //Alocare in context Patrulater PRef=new Patrulater("Poligonul cu patru laturi"); System.out.println(PRef.citesteDefinitie()); PRef.arie(); System.out.println(""); }; }; Nu am motive s reiau discuia pe marginea mecanismului de legare dinamic a metodelor supradefinite n Java. Chiar dac compilatorul folosete alt gen de informaii, la intrare, rezultatul final, pentru programator este acelai. Nu consider o problem deosebit comentarea i exemplificarea suprascrierii n clasele Java.

Capitolul 7
Tratarea structurat a excepiilor n programarea orientat pe obiecte

7.1 O problem, n plus, n programare: tratarea excepiilor


Programatorii adevrai trebuie s ia, obligatoriu, n calcul i posibilitatea de a crea programe robuste, care fac fa att cerinelor specificate dar nerafinate suficient, ct i cerinelor nespecificate dar formulate de utilizator, din diverse motive. Programele care au aceste caliti se numesc robuste. n programarea clasic, soluia acestei probleme se putea numi, destul de exact spus, programare defensiv. Seamn puin cu conducerea preventiv din oferie dac ne gndim c programnd defensiv, n fond punem rul nainte, deci nu ne bazm pe cumsecdenia i buna pregtire a utilizatorului. ncercarea de a trata situaiile de excepie care pot apare la execuia unui program, folosind metode clasice (programarea defensiv) duce la creterea semnificativ a complexitii codului ceea ce afecteaz, n mod direct, lizibilitatea i, n mod indirect, corectitudinea codului Pentru a face fa cerinelor legate de problema tratrii excepiilor (aa se numesc n jargon profesional erorile care apar n timpul execuiei programelor) anumite limbaje de programare ofer suport adecvat. Includem aici limbaje precum: Object Pascal, C++, Java, Visual C++. Nu toate compilatoarele de C++ ofer suport, dar standardul ANSI C++ cere acest lucru n mod explicit. Compilatoarele din familia Borland, ncepnd cu versiunea 4.0 ofer acest suport.

Esenialul din punctul de vedere al programatorului C++ este ca el s-i formeze abilitatea de a scrie, n jurul aplicaiilor, cod C++ care ndeplinete funcia de handler de excepii.

7.2 Mecanisme de baz n tratarea excepiilor n C++


Suportul sintactic C++ pentru tratarea excepiilor se rezum la trei cuvinte cheie, a cror semantic preliminar o prezentm n Tabelul 7.1. Cuvntul cheie try throw catch Semnificaie Delimiteaz o poriune de cod n care se instituie controlul sistemului asupra excepiilor n timpul rulrii. Lanseaz o excepie de un anumit tip Capteaz o excepie lansat Tabelul 7.1 Cuvintele cheie ale limbajului C++ referitoare la tratarea excepiilor

Forma de baz a tratrii excepiilor Aadar, atunci cnd programele dumneavoastr efectueaz prelucrarea excepiilor, trebuie s includei n cadrul unui bloc try instruciunile pe care dorii s le monitorizai n eventualitatea apariiei unei excepii. Dac execuia unei instruciuni se termin cu o eroare, trebuie s lansai o eroare, corespunztoare aciunii funciei n care se afl instruciunea. Programul plaseaz instruciunea throw n cadrul blocului try-catch. Forma generalizat a blocului care capteaz i trateaz erorile este: try { //blocul try //if(eroare) throw valoare_excepie; } catch (Tip_excepie Nume_variabil ){ //Prelucrarea excepiei } n cadrul acestei forme generalizate, valoarea valoare_excepie, aruncat, trebuie s corespund tipului Tip_excepie. Scrierea unui handler de excepii simplu Pentru a nelege mai bine semantica unui handler de excepii, studiai programul prezentat n Exemplul 7.1. Exemplul 7.1 #include <iostream.h>

void main() { cout<<"Start"<<endl; try { cout<<"In interiorul blocului try"<<endl; throw 100; cout<<"Nu se va executaniciodata"; } catch (int i) { cout<<"Am captat o excepie --valoarea este:"; cout<<i <<endl; } cout<<"Sfarsit"; }; Programul de mai sus implementeaz un bloc try-catch simplu. n loc s se atepte ca programul s eueze datorit unei erori, se utilizeaz instruciunea throw pentru lansarea erorii prezumtive. Dup ce blocul try lanseaz eroarea, blocul catch o capteaz i prelucreaz valoarea transmis de instruciunea throw. Este evident i din acest exemplu c mecanismul try-throw-catch ofer suport pentru rezolvarea problemei tratrii excepiilor, dar nu rezolv de la sine aceast problem. Altfel spus, tratarea corect a excepiilor unui program este o problem de atitudine ca proiectant i ca programator. Lansarea excepiilor cu o funcie din cadrul blocului try Atunci cnd programele apeleaz funcii din cadrul blocurilor try , C++ va transmite excepia aprut ntr-o astfel de funcie n afara funciei dac nu exist un bloc try n interiorul funciei. Exemplul 7.2 ne arat cum se petrec lucrurile ntr-o astfel de situaie. Exemplul 7.2 #include <iostream> void XHandler(int test) { cout<<"Inauntrul functiei XHandler, test are valoarea: "<<test<<endl; if(test) throw test; }; void main() { cout<<"Start:"<<endl; try {

cout<<"Inauntrul blocului try"<<endl; XHandler(1); XHandler(2); XHandler(0); } catch(int i) { cout<<"Am captat o exceptie. Valoarea este:"; cout<<i<<endl; }; cout<<"Sfarsit"; }; Plasarea unui bloc try ntr-o funcie Am vzut cum apare un bloc try n funcia principal a unui program. C++ permite blocuri try i n alte funcii ale unui program, diferite de funcia principal. Atunci cnd se plaseaz un bloc try ntr-o funcie, C++ reiniializeaz blocul de fiecare dat cnd se intr n acea funcie. Exemplul 7.3 ilustreaz cele spuse. Exemplul 7.3 #include <iostream.h> void XHandler(int test) { try { if(test) throw test; } catch(int i) { cout<<"Am captat exceptia nr.: "<<i<<endl; } }; void main() { cout<<"Start: "<<endl; XHandler(1); XHandler(2); XHandler(0); XHandler(3); cout<< "Sfarsit"; };

Un comentariu pe marginea celor prezentate pn acum ar fi urmtorul: o instruciune catch se execut numai dac programul lanseaz o excepie n cadrul blocului try situat imediat nainte. n caz c o astfel de excepie nu se lanseaz blocul catch va fi ignorat. Utilizarea mai multor instruciuni catch cu un singur bloc try Pe msur ce tratrile excepiilor devin tot mai complexe, uneori este necesar i posibil ca un singur bloc try s lanseze excepii de mai multe tipuri. n cadrul programelor dumneavoastr putei construi un handler de excepii, astfel nct s accepte captarea mai multor excepii. ntr-o astfel de situaie sintaxa general este: try { //instruciuni } catch (<tip_1> <var_1>) { //tratare excepie 1 } catch(<tip_2> <var_2>) { //tratare excepie 2 } : catch(<tip_n> <var_n>) { //tratare excepie n } Cu acest amendament sintactic, deducem c instruciunile catch pot capta orice tip returnat, nu numai tipurile de baz acceptate de C++. Acest "fenomen" este ilustrat n codul de mai jos (Exemplul 7.4). Exemplul 7.4 #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw "Sir de caractere"; if(test==2) throw 121.25;

} catch(int i) { cout<<"Am captat exceptia #:"<<i<<endl; } catch(char *sir) { cout<<"Am captat exceptia de tip sir de caractere:" <<sir<<endl; } catch(double d) { cout<<"Am captat exceptia #:"<<d<<endl; } }; void main() { XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Blocuri catch generice (utilizarea operatorului puncte de suspensie) Programele scrise de dumneavoastr pot capta excepii din cadrul mai multor blocuri try (de exemplu un bloc try care ncapsuleaz mai multe funcii care lanseaz excepii diferite din blocuri try diferite, sau s utilizeze mai multe instruciuni catch ntr-un singur bloc try. C++ permite, de asemenea, utilizarea operatorului puncte de suspensie () pentru a capta orice tip de eroare care apare ntr-un singur bloc try. Sintaxa care permite captarea tuturor erorilor care apar ntr-un bloc try este prezentat mai jos. try { //Instructiuni } catch() { //tratarea exceptiei } Pentru exemplificare propun codul de mai jos (Exemplul 7.5).

Exemplul 7.5 #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25; } catch() { cout<<"Am captat o exceptie"<<endl; } }; void main() { cout<<"Start:"<<endl; XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Evident, prelucrrile din cadrul blocului catch generic trebuie s fie independente de tipul erorii. Mecanismul captrii excepiilor explicite poate fi combinat cu mecanismul excepiilor generice ca n Exemplul 7.6. Exemplul 7.6 #include <iostream.h> void XHandler(int test) { try { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25;

} catch(int i) { cout<<"Am captat o exceptie de tip intreg"<<endl; } catch() { cout<<"Am captat o exceptie generica"<<endl; } }; void main() { cout<<"Start:"<<endl; XHandler(0); XHandler(1); XHandler(2); cout<<"Sfarsit"; }; Restricionarea excepiilor Pe msur ce programele dumneavoastr devin mai complexe, ele vor apela frecvent funcii din cadrul unui bloc try. Atunci cnd programele dumneavoastr apeleaz funcii dintr-un bloc try, putei restriciona tipurile de excepii pe care funcia apelat le poate lansa. De asemenea, putei preveni lansarea oricrei excepii dintr-o anumit funcie. Sintaxa pentru restricionare este: <tip_returnat> <nume_functie>(<lista_arg>) throw(<lista_tipuri> ) { //Cod functie } Sintaxa care inhib lansarea oricrei excepii este: <tip_returnat> <nume_functie>(<lista_arg>) throw() { //Cod functie } Este bine s subliniem c atunci cnd declarai o funcie cu clauza throw ea poate s lanseze doar acele tipuri precizate n list. Dac funcia lanseaz orice al tip, programul este abortat. Un exemplu n continuare (Exemplul 7.7).

Exemplul 7.7 #include <iostream.h> void XHandler(int test) throw(int, char, double) { if(test==0) throw test; if(test==1) throw 'a'; if(test==2) throw 121.25; } void main() { cout<<"Start:"<<endl; try { XHandler(0); } catch(int i) { cout<<"Am captat un intreg"<<endl; } catch(char c) { cout<<"Am captat un caracter"<<endl; } catch(double d) { cout<<"Am captat un double"<<endl; } cout<<"Sfarsit"; }; Relansarea unei excepii n anumite situaii poate fi necesar s se relanseze o excepie din interiorul unui handler de excepii. Dac relansai o excepie, C++ o va transmite unui bloc try exterior dac acesta exist. Cea mai probabil situaie n care putei opta pentru aceast variant este atunci cnd dorii s tratai o excepie n cadrul a dou programe handler distincte. Pentru mai mult claritate, urmrii exemplul de mai jos (Exemplul 7.8). Exemplul 7.8 #include <iostream.h>

void XHandler(void) { try { throw "Salve"; } catch(char *) { cout<<"Am captat char* in XHandler "<<endl; throw; } void main() { cout<<"Start"<<endl; try { XHandler(); } catch(char *) { cout<<"Am captat char * in main"<<endl; } cout<<"Sfarsit"; }; Mod de utilizare a excepiilor Toate elementele prezentate au ncercat s demonstreze c C++ are o atitudine activ fa de problema tratrii excepiilor. Suportul oferit de C++ l ajut pe programator s defineasc un comportament al programului cnd se produc evenimente anormale sau neateptate. O idee mai pragmatic de utilizare a suportului C++, n situaii efective, o putei desprinde din Exemplul 7.9. Exemplul 7.9 #include <iostream.h> void div (double a, double b) { try { if(!b) throw b; cout<<"a/b="<<a/b<<endl; } catch(double b) {

cout<<"Nu se poate imparti la zero"<<endl; } } void main() { double i,j; do { cout<<Introduceti numaratorul (0 pentru stop):"<<endl; cin i; cout<<Introduceti numitorul :"<<endl; cin j; div(i,j); } while (i!=0); }; Adevrata utilizare a protocoalelor prezentate mai sus, n context orientat pe obiect, se bazeaz pe posibilitatea de a modela, conform necesitilor i cerinelor diferitelor tipuri de aplicaii, excepiile care pot apare. Practica a impus regula, potrivit creia, fiecrei clase care face parte din diagrama claselor s i asociem o clas care i modeleaz excepiile posibile, n comportament. Astfel c diagrama claselor care nsoete soluia unei aplicaii orientate pe obiecte, va fi dublat de o diagram a claselor care structureaz excepiile aplicaiei. n acest mod putem ierarhiza excepiile, le personalizm i, evident, le integrm ntr-o concepie unitar de planificare a robusteii unei aplicaii. Elementele de baz ale unei astfel de abordri sunt ilustrate n Exemplul 7.10, un exemplu de cod C-Builder, n care clasa Poligon este acompaniat de clasa ExPol, care, ar trebui s modeleze excepiile care pot apare n evoluia obiectelor de tip Poligon. Exemplul 7.10 //----------------------------------------------------------#include <vcl.h> #pragma hdrstop #include "UEx1.h" //----------------------------------------------------------#pragma package(smart_init) #pragma resource "*.dfm" //Clasa Poligon //Insigfnifiant din punct de vedere informaional //i comportamental //Expune ctre clieni dou metode: // constructorul Poligon()

// metoda getmes() class Poligon { char *mes; public: Poligon(char m[]) { mes=new char[strlen(m)]; strcpy(mes,m); }; char * getmes() { return mes; }; }; //Clasa ExPol //Are n dotare operaiile strict necesare crerii //i manipulrii unui obiect excepie: // -constructorul, care creaz o excepie (ExPol()) // -metoda de consultare de ctre client a // semnificaiei excepiei (getmes()) class ExPol { AnsiString mes; public: ExPol(char m[]) { mes=m; }; AnsiString getmes() { return mes; }; }; TForm1 *Form1; Poligon *pol; //----------------------------------------------------------_fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner)

{ } //----------------------------------------------------------void _fastcall TForm1::Button1Click(TObject *Sender) { double numar; //Bloc de gard, instituit de cuvntul cheie try try { numar=StrToFloat(Edit1->Text); if(numar) { pol=new Poligon("Testare"); Form1->Caption=pol->getmes(); } else // Creare i aruncare obiect excepie de tip ExPol throw new ExPol("Eroare alocare memorie obiect..."); } //Secven de captare i tratare //a excepiilor de tip ExPol catch(ExPol *e) { Form1->Caption="Exceptie..."+e->getmes(); } } //----------------------------------------------------------Adevrul este c problema tratrii excepiilor n C-Builder este rezolvat mult mai bine dect n C++ standard, programatorul dispunnd deja de o ierarhie de clase care modeleaz un mare numr de excepii care pot apare, ierarhie la care se poate ralia i programatorul pentru a defini i trata propriile lui excepii. Ne apropiem, astfel, destul de mult, de abordarea Java a problemei tratrii excepiilor.

7.3 Maniera Java de tratare a excepiilor


Aa cum am menionat deja, ncercarea de a trata situaiile de excepie care pot apare la execuia unui program, folosind metode clasice (programarea defensiv) duce la creterea semnificativ a complexitii codului, ceea ce afecteaz, n mod direct, lizibilitatea i, n mod indirect, corectitudinea codului. Din aceast cauz cei care au creat Java au gndit un sistem de tratare a excepiilor (n continu evoluie, de la o versiune la alta a limbajului Java) care s permit programatorului:

Tratarea situaiilor de excepie, pe ct posibil, independent de fluxurile de control normale; Tratarea excepiilor, la un nivel superior celui n care apar; Propagarea excepiilor la nivelele superioare n mod ierarhic; Tratarea unitar a excepiilor de acelai tip.

n mare parte, asemntor sistemului C++ de tratare a excepiilor, sistemul Java are, totui, o ofert mai bine pus la punct din acest punct de vedere. Java se bazeaz pe un numr restrns de cuvinte cheie (try, catch, throw, finally, throws) i pe o ierarhie de clase, specializate n tratrarea unor clase de erori. Pentru a nelege mai bine mecanismul tratrii excepiilor n Java, consider c este util o scurt descriere a modului n care apar i sunt procesate excepiile n Java. Astfel, cnd apare o excepie n interiorul unei metode a unei clase Java, se creeaz un obiect excepie (obiect ce caracterizeaz excepia i starea programului n momentul cnd excepia apare). Odat creat acest obiect, el este aruncat utiliznd cuvntul cheie throw. Sarcina crerii i aruncrii obiectului excepie aparine programatorului. Din momentul aruncrii unui obiect excepie, folosind cuvntul cheie throw, maina virtual Java (JVM), prin componenta RuntimeSystem, preia obiectul i l transmite secvenei de cod responsabil de tratarea excepiei respective. n acest scop, RuntimeSystem va cuta un handler al excepiei (=o secven de cod responsabil de tratarea excepiei), ncepnd de la nivelul (nivelul este o metod) n care a aprut excepia i continund la nivelele superioare. Cutarea se face n urm (backward), utiliznd stiva de apel (call stack). Primul handler (un bloc try-catch), corespunztor obiectului excepie, se va ocupa de soluionarea excepiei. Dac RuntimeSystem a epuizat stiva de apel, fr a gsi o metod care s ofere un handler al obiectului excepie aruncat, RuntimeSystem va fi responsabil de tratarea excepiei respective (va afia un mesaj de eroare i va opri firul de execuie). Mecanismul descris mai sus poate fi vizualizat ca n Figura 19. Problema care se afl n faa programatorului este, evident, urmtoarea: cum poate folosi raional mecanismul respectiv? Spun aceasta deoarece, ca orice facilitate a limbajului i suportul oferit pentru tratarea excepiilor poate fi utilizat n mod abuziv. A abuza de tratarea excepiilor nseamn a vedea excepii i unde nu este cazul, fapt care provoac complexificarea artificial a codului i, foarte important, diminueaz performanele programului, deoarece, a cum rezult i din Figura 19, mecanismul tratrii excepiilor consum resurse pentru a se desfura corespunztor. De aceea, este necesar o disciplinare a gndirii programatorului, n ceea ce privete decizia de a considera excepie sau nu un anumit context de prelucrare, n interiorul unei metode i apoi, decizia de a aborda, ntr-un anumit mod, problema tratrii excepiei respective. n esen, programatorul trebuie s acumuleze suficien experien nct s deosebeasc o excepie nerecuperabil de o excepie din care se poate reveni.

RuntimeSuystem - JVM -preia obiectul excepie -caut, ncepnd cu nivelul j, n sus, primul handler corespunztor (=primul handler care rezolv o excepie de tipul celei aruncate) -transfer obiectul excepie handler-ului

Nivel_1

Nivel_i Nivel Tratare Excepie Conine un handler (un bloc try-catch) -preia obiect excepie -trateaz excepie

Nivel_j Nivel apariie excepie -creaz obiectul excepie Exception exc=new Exception(); -arunc excepia throw(exc);

Figura 19. Mecanismul Java de tratare a excepiilor Pentru a crea un obiect excepie, Java pune la dispoziia utilizatorului o ierahie de clase, aflat n pachetul java.lang. Pe lng clasele de tip excepie aflate n java.lang, fiecare pachet Java introduce propriile tipuri de excepii. Utilizatorul nsui poate defini clase de tip excepie, care ns pentru a avea instane compatibile cu sistemul Java, trebuie s fie descendeni ai clasei Throwable, clas care ocup o poziie important n ierarhia simplificat a claselor de tip excepie, prezentat n Figura 20.

Throwable

Exception

Error

RuntimeException

Figura 20. Ierarhia simplificat a claselor de tip excepie din pachetul java.lang Dup cum se poate observa n Figura 20, clasa Throwable are doi descendeni: clasa Error i clasa Exception. Nici una din cele dou clase nu adaug metode suplimentare, dar au fost introduse pentru a delimita dou tipuri fundamentale de excepii care pot apare ntr-o aplicaie Java (de fapt, acest mod de gndire este aplicabil n orice limbaj de programare care ofer suport pentru tratarea sistematic a excepiilor). Clasa Error corespunde excepiilor care nu mai pot fi recuperate de ctre programator. Apariia unei excepii de tip Error impune terminarea programului. Aruncarea unei excepii de tip Error nseamn c a aprut o eroare deosebit de grav n execuia programului sau n maina virtual Java. n marea majoritate a cazurilor, aceste excepii nu trebuie folosite de ctre programator, nu trebuie prinse prin catch, i nici aruncate prin throw de ctre programator. Aceste tipuri de erori sunt utilizate de JVM, n vederea afirii mesajelor de eroare. Clasa Exception este, de fapt, clasa utilizat efectiv de ctre programatori n procesul de tratare a excepiilor. Aceast clas i descendenii ei modeleaz excepii care pot fi rezolvate de ctre program, fr a determina oprirea programului. Prin urmare, regula este simpl: dac Java nu conine o clas derivat din Exception care poate fi utilizat ntr-un anumit context, atunci programatorul va trebui s o implementeze, el nsui, ca o clas derivat din Exception. Exist o mare varietate de clase derivate din Exception care pot fi utilizate. Mai mult, fiecare pachet Java adaug noi tipuri de clase derivate din Exception, clase legate de funcionalitatea pachetului respectiv. Dac astfel lucreaz cei de la SUN, de ce n-ar lucra la fel i un programator oarecare? Din categoria claselor derivate din Exception, se remarc clasa RuntimeException12 i clasele derivate din ea. Din aceast categorie fac parte excepii care pot apare n execuia unui program, n urma unor operaii nepermise de genul: operaii aritmetice interzise (mprire la zero), acces nepermis la un obiect (referin null), depirea index-ului unui tablou sau ir, etc. Nu ne rmne dect s prezentm protocolul de lucru cu excepii n Java. Aruncarea excepiilor Aruncarea unei excepii se face cu ajutorul cuvntului cheie throw, conform sintaxei: throw <obiectExceptie>; unde <obiectExceptie> este o instana a clasei Throwable sau a unei clase, derivat din aceasta. Evident, n locul variabilei <obiectExceptie> poate fi o expresie care returneaz un obiect de tip convenabil. De fapt, n practic, modul de aruncare a unei excepii urmeaz schema: throw new <clasaExceptie>(Mesaj);
12

Detalii cu privire la descendenii clasei RuntimeException se pot gsi n Clin Marin Vduva, Programarea n Java, Editura Albastr, 2001.

Evident, putem avea i cazuri n care o funcie poate arunca n mod indirect o excepie, ceea ce nseamn c funcia nu va conine o expresie throw, ci va apela o funcie care poate arunca o excepie. O metod poate arunca mai multe excepii. Important este s nelegem c prin aruncarea unei excepii se iese din metod fr a mai executa secventele de cod care urmau. n cazul n care o funcie arunc o excepie, fie prin throw , fie prin apelul unei funcii, fr a avea o secven try-catch de prindere atunci aceast funcie trebuie s specifice clar aceast intenie n definiia funciei. Pentru acest caz, sintaxa de definire a funcie este: public void <numeMetoda> throws <clasExcept1>,<clasExcept2>, { throw <obiectExcep1>; throw <obiectExcept2>; }; Prinderea excepiilor Pentru a beneficia de avantajele mecanismului de tratare a excepiilor, odat ce am aruncat o excepie este nevoie s o prindem. Prinderea unei excepii se face prin intermediul unui bloc try-catch, a crui sintax generic este prezentat mai jos. try { //Cod ce poate arunca o excepie } catch(<clasExcept1> <idExcept1>) { //handler exceptie de tip <clasExcept1> } catch(<clasExcept2> <idExcept2>) { //handler exceptie de tip <clasExcept2> } [finally { //secvena de cod executat oricum }]

Dup cum se poate observa, structura de prindere a excepiilor poate fi delimitat n trei blocuri. Blocul try, numit i bloc de gard atrage atenia c secvena de cod inclus n el poate arunca, n anumite condiii, excepii. n cazul n care acest lucru nu se ntmpl, secvena din interiorul blocului de gard se execut n ntregime, controlul fiind predat primei instruciuni de dup construcia try-catch. n cazul n care se arunc o excepie, execuia secvenei din blocul de gard se ntrerupe i se declaneaz procedura de tratare a excepiei. Tratarea excepiei se poate face prin intermeiul blocurilor catch, numite i handlere de excepii. n momentul n care apare o excepie n regiunea de gard, se parcurge lista blocurilor catch n ordinea n care apar n programul surs. n cazul n care excepia aruncat corespunde unui bloc catch, se execut codul eferent blocului i se termin cutarea n list, considerndu-se c excepia a fost rezolvat. Sintaxa ne arat c pot exista mai multe blocuri catch, ceea ce nseamn c n blocul de gard pot fi aruncate excepii de mai multe tipuri. Situaiile deosebite care pot apare n utilizarea blocurilor catch sunt urmtoarele: am putea dori s tratm excepii de tip EC1 i EC2, unde EC2 este o clas derivat din EC1. Datorit faptului c blocurile catch sunt parcurse secvenial este necesar s avem handler-ul clasei EC2 naintea handler-ului clasei EC1, altfel, nu se va ajunge niciodat la secvena catch de tratare a excepiilor de tipul EC2. De asemenea, putem avea situaii n care s dorim tratarea unei excepii pe mai multe nivele. n acest caz, se poate lua n considerare faptul c, odat prins o excepie ntr-un bloc catch, o putem re-arunca cu un apel simplu de tip throw. n sfrit, blocul finally, dac este folosit, cuprinde secvena de cod care se va executa, indiferent dac apare sau nu o excepie, situaie reclamat de nenumrate contexte n care apariia unei excepii, ca i lipsa acesteia, presupun rezolvarea unor probleme care pot scuti sistemul de introducerea unor elemente perturbatoare prin nerezolvarea lor. n Exemplul 7.11 i n Exemplul 7.12 se pot vedea elementele de baz ale tratrii excepiilor ntr-o aplicaie Java. Utilizarea cuvntului cheie finally nu mi se pare o problem deosebit. Exemplul 7.11 //Metoda arunca o exceptie la nivelul superior //Clasa care modeleaza exceptiile clasei NumarReal @SupressWarnings(serial) class ENumarReal extends Exception { public ENumarReal(String s) { super(s); }; } //Clasa NumarReal //o tentativa de modelare a lucrului cu numere reale class NumarReal { private double numar;

public NumarReal(double nr) { numar=nr; }; public double getNumar() { return numar; }; //Metoda div() imparte doua numere reale //Suspecta de a arunca o exceptie la impartirea la zero //Declara acest lucru cu ajutorul cuvantului cheie throws public NumarReal div(NumarReal n) throws ENumarReal { if (n.getNumar()==0) throw new ENumarReal("Exceptie...Impartire la zero..."); else return new NumarReal(this.getNumar()/n.getNumar()); }; } public class Except1 { public static void main(String [] s) { //Blocul de garda care capteaza exceptia aruncata //de metoda div() try { NumarReal onr1=new NumarReal(12); NumarReal onr2=new NumarReal(6); System.out.println(onr1.div(onr2).getNumar()); onr1=new NumarReal(11); onr2=new NumarReal(0); onr1.div(onr2); } catch(ENumarReal e) { System.out.println(e.getMessage());

}; }; }; Exemplul 7.12 //Metoda arunca o exceptie dar o si capteaza //Clasa care modeleaza exceptiile clasei NumarReal class ENumarReal extends Exception { public ENumarReal(String s) { super(s); }; } //Clasa NumarReal //o tentativa de modelare a lucrului cu numere reale class NumarReal { private double numar; public NumarReal(double nr) { numar=nr; }; public double getNumar() { return numar; }; //Metoda div() imparte doua numere reale //suspecta de a genera o exceptie la impartirea la zero //Are bloc try-catch pentru captarea si tratarea //exceptiei public NumarReal div(NumarReal n) { try { if (n.getNumar()==0) throw new ENumarReal("Exceptie...Impartire la zero..."); else

return new NumarReal(this.getNumar()/n.getNumar()); } catch(ENumarReal e) { System.out.println(e.getMessage()); return new NumarReal(0); }; }; } public class Except { public static void main(String [] s) { NumarReal onr1=new NumarReal(12); NumarReal onr2=new NumarReal(6); System.out.println(onr1.div(onr2).getNumar()); onr1=new NumarReal(11); onr2=new NumarReal(0); onr1.div(onr2); }; };

Capitolul 8
Programare generic n C++ i Java

8.1 Ce este programarea generic


Adeseori, programatorul se afl n situaia de a efectua acelai tip de prelucrare asupra unor tipuri de date diferite. Soluia nceptorului este scrierea de cod complet pentru fiecare tip de dat. Vrem s sortm un fiier dup o cheie

ntreag? Scriem o funcie care realizeaz acest lucru, folosind, de exemplu, metoda bulelor. Vrem s sortm un fiier dup o cheie alfanumeric? Scriem o funcie care tie s sorteze fiierul dup o astfel de cheie, folosind tot metoda bulelor. Nu ne va fi greu s observm c, n cele dou rezolvri date de noi exist un element de invarian: codul ablon care efectueaz sortarea. Deosebirile se refer la tipurile de date implicate n procesul de sortare (fiierele pot avea nregistrri de lungime diferit i, evident, cu structur diferit iar cheile de sortare pot fi diferite ca tip). Problema n faa creia ne aflm nu este o problem de algoritmic ci una de tehnic de programare. Programarea care are n vedere specificarea unor structuri de prelucrare capabile s opereze asupra unor tipuri variate de date se numete programare generic. Evident, exist limbaje de programare n specificarea crora au fost prevzute i elemente suport pentru rezolvarea acestui tip de problem. De exemplu, n Object Pascal se poate face programare generic apelnd la tipuri procedurale i la referinele de tip pointer. n C++ se pot utiliza, n scopuri generice, suprascrierea funciilor, conceptul de pointer, funciile ablon sau clasele ablon i pointerii la funcii. n sfrit, n Java, utiliznd cu abilitate motenirea i interfeele putem simula genericitatea de o manier destul de acceptabil. A evidenia, dintre toate tipurile de elemente suport prezentate mai sus, clasele ablon din C++, socotite abstracii foarte puternice, care permit simularea a ceea ce, n ingineria softului, numim metaclase.

8.2 Genericitatea n C++


Aadar, in programare apar nenumrate situaii n care reutilizarea codului presupune o soluie de un anumit tip pentru o problem dat. Situaia la care ne referim n aceast seciune este, potenial vorbind, urmtoarea: Ce putem face pentru a comprima codul surs n situaia n care structuri de date, diferite ca tip, suport prelucrri similare. Soluia acestei probleme de stil de programare o reprezint programarea generic. Exprimndu-ne n termenii limbajului C, o funcie generic definete un set general de operaii care vor fi aplicate unor tipuri de date diferite. Ca un exemplu, o soluie generic pentru modelarea unei stive este un pretext ideal pentru precizarea ideilor principale ale programrii generice. Altfel spus, dac dorim o stiv, n care, de la caz la caz, s putem pstra numere ntregi, numere reale sau iruri de caractere (deci tipuri de date diferite), operaiile fiind aceleai ( push() i pop() ), este clar c ne aflm n situaia n care avem nevoie de suport pentru scrierea de cod cu proprieti generice. Dac n Pascal programarea generic se baza pe tipuri procedurale i programarea la nivel de octet, n C++ exist suport evoluat pentru programare generic, sub forma abloanelor. Cu un ablon, n C++ se poate crea o funcie generic sau o clas generic. S reamintesc cititorului faptul c n Capitolul 4 am ilustrat modul de utilizare a conceptului de pointer la funcie, pentru definirea unui membru al unei structuri. Funcii TEMPLATE O funcie template este o funcie ablon, avnd unul sau mai muli parametri formali de un tip generic. n funcie de nevoile de utilizare a acestei funcii, compilatorul genereaz funcii propriu-zise, nlocuind tipul generic cu un tip concret. Tipul concret poate fi orice tip fundamental, derivat sau clas. Considerm un exemplu. Fie funcia max(x,y) care returneaz valoarea maxim a argumentelor sale. Tipul variabilelor x i y trebuie, obligatoriu, specificat n momentul compilrii. Soluia clasic const n redefinirea (overloading) funciei max pentru fiecare tip al argumentelor x i y. Trebuie, aadar, s definim mai multe versiuni ale funciei max.

int max(int x, int y) { return (x>y) ? x : y; } float max(float x, float y) { return (x>y) ? x : y; } Mecanismul template permite definirea o singur dat a ablonului de funcii, dup care se genereaz automat funciile propriu-zise, n concordan cu necesitile de utilizare, dar, evident, n faza de compilare. Sintaxa la specificare este: template <class Nume_tip_generic_1 [,class Nume_tip_generic_n]> Nume_ablon definiie_ablon De precizat urmtoarele: Caracterele < i > fac parte din sintaxa obligatorie, nu ndeplinesc, aa cum ne-am obinuit, rolul de elemente de metalimbaj. Lista de parametri formali ai unei funcii ablon trebuie s utilizeze toate tipurile de date generice. n cazul funciilor template nu se fac conversii. Funcia care are acelai nume i acelai numr de parametri (Suprancrcarea explicit este prioritar). Sintaxa la utilizare este: Nume ablon(Expresie_1[, ,Expresie_n]); Prezentm, n continuare, definiia funciei ablon max , urmat de o secven client de utilizare. template <class T> T max(T x, T y) { return (x>y) ? x : y; cu o funcie ablon se numete caz exceptat

} Exemplul 8.1 #include <conio.h> #include<iostream.h> //Definire sablon functie template<class T> T max(T x,T y) { return(x>y) ? x:y; } void main() { int i,j; float k,l; clrscr(); i=10; j=2; k=13; l=-7; //Exemple de utilizare sablon gotoxy(20,10); cout<<"Apel max cu param. variabili de tip float..." << max(k,l); gotoxy(20,12); cout<<"Apel max cu param. variabili de tip int ..."<<max(i,j); gotoxy(20,13); cout<<"Apel max cu parametri valoare float ..."<<max(13.,-7.); getch(); } Prezentm, totodat, un exemplu de funcie generic pentru compararea unor date dup valoarea unei chei ncapsulate n aceste date. Exemplul 8.2 #include <conio.h> #include <iostream.h> #include <ctype.h> //Definirea unei <functii generice> pentru compararea

//unor date dupa valoarea unei chei incapsulate //in aceste date template <class T> int comp(T i1,T i2) { if (i1.key<i2.key) return -1; if (i1.key==i2.key) return 0; if (i1.key>i2.key) return 1; }; //Structura aleasa pentru exemplificare //cheia generica incapsulata este campul <key> struct tpers { char np[30]; int key; }; //Instantiere <struct tpers> struct tpers tam,pers[50]; void main() { clrscr(); int i=0; //Citire persoane de la tastatura do { gotoxy(20,12); cout<<"Numele persoanei:";clreol(); cin>>pers[i].np; gotoxy(20,13); cout<<"Matricola:";clreol(); cin>>pers[i].key; gotoxy(20,14); cout<<"Mai aveti(D,N):"; i++; } while(toupper(getch())!='N'); //Listare persoane pe ecranul monitorului //in ordinea citirii de la tastatura

clrscr(); cout<<"Listare pers. in ordinea citirii de la tastatura"<<endl; cout<<"_________________________________________"<<endl; for(int j=0;j<i;j++) { if (wherey()>10) { cout<<"_________________________________________"<<endl; cout<<"Pentru continuare apasati o tasta..."<<endl; getch(); clrscr(); cout<<"Listare pers. in ord. citirii de la tastatura"<<endl; cout<<"_________________________________________"<<endl; }; cout.width(30);cout.setf(ios::left); cout<<pers[j].np<<" "<<pers[j].key<<endl; }; getch(); //Sortare persoane int sortat; do { sortat=1; for(int j=0;j<i-1;j++) { switch(comp(pers[j],pers[j+1])) { case 1: { tam=pers[j]; pers[j]=pers[j+1]; pers[j+1]=tam; sortat=0; }; }; }; } while(!sortat); //Listare persoane dupa sortare in ordinea //crescatoare a matricolelor

clrscr(); cout<<"Listare persoane dupa sortare..............."<<endl; cout<<"_______________________________________"<<endl; for(int k=0;k<i;k++) { if (wherey()>10) { cout<<"_________________________________________"<<endl; cout<<"Pentru continuare apasati o tasta..."<<endl; getch(); clrscr(); cout<<"Listare persoane dupa sortare............"<<endl; cout<<"_________________________________________"<<endl; }; cout.width(30);cout.setf(ios::left); cout<<pers[k].np<<" "<<pers[k].key<<endl; }; getch(); } Clase TEMPLATE O clas template definete un ablon pe baza cruia se pot genera clase propriu-zise. Din acest motiv, o clas template se mai numete i clas generic , clas generator sau metaclas. Astfel c, o clas template devine o clas de clase, reprezentnd cel mai nalt nivel de abstractizare admis de programarea obiect orientat n C++. n cadrul clasei ablon se pot declara atribute informaionale de un tip ambiguu, care sunt particularizate n cadrul clasei generat pe baza ablonului. Evident, i n acest caz, generarea se face n faza de compilare n concordan cu cerinele clientului. Sintaxa la specificarea unei clase ablon, n cazul n care avem un singur tip generic este: template <class T> class nume_clasa { : }; Extinderea la mai multe tipuri generice este imediat. Sintaxa dup care se face implementarea funciilor membre ale unei clase template:

template <class T> Tip returnat nume_clasa <T>::nume_funcie() { : }; Sintaxa la instaniere este: nume_clasa <Tip_concret> Obiect; Pentru mai mult claritate, prezentm i exemplul de mai jos. Exemplul 8.3 #include <iostream.h> #include <stdlib.h> #include <conio.h> const int SIZE = 10; //Definire clasa matrice generica template <class ATip> class genmat { ATip a[SIZE]; public: genmat(); ATip &operator [ ](int i); //Suprancrcare operator [ ] }; //Implementare constructor clasa generica template <class ATip> genmat<ATip>::genmat() { register int i; for(i=0;i<SIZE;i++) a[i]=i; }; //Implementare supraincarcare operator [ ] template <class ATip> ATip &genmat<ATip>::operator[ ](int i) { if(i<0 ||i>SIZE-1) { cerr<<"Valoare indice eronata...";

getch(); exit(1); }; return a[i]; }; //Functia principala void main() { genmat<int> intob; genmat<double> doubob; int i; clrscr(); cout<<"Matrice de intregi..."<<endl; for(i=0;i<SIZE;i++) intob[i]=i; for(i=0;i<SIZE;i++) cout<<intob[i]<<endl; getch(); clrscr(); cout<<"Matrice de reali dubla precizie..."<<endl; for(i=0;i<SIZE;i++) doubob[i]=(double)i/3; for(i=0;i<SIZE;i++) cout<<doubob[i]<<endl; getch(); clrscr(); intob[100]=100; }; Tot pentru exemplificare, s considerm i o situaie deosebit de simpl. Ni se cere s construim o clas template corespunztoare conceptului de stiv, din care, ulterior, s se poat concretiza clase care simuleaz stiva pentru tipuri de date diferite. //Clasa sablon CSTack template <Class T> class CStack { T * v; //pointer la varful stivei T * p; //pointer la pozitia curenta din stiva int dim; //dimensiunea stivei public:

CStack(int sz) { v=p=new T [dim=sz]; //alocare dinamica de memorie pentru p i v } ~CStack() { delete [ ] v; } void push(T a) { *p++=a; }; T pop() { return *-p } } Dup ce o astfel de clas template a fost declarat i definit, se poate trece deja la instanierea de obiecte. Singura deosebire fa de folosirea unei clase obinuite const n faptul c trebuie specificat tipul concret care nlocuiete tipul generic T. Ca un exemplu, s instaniem un obiect stiv (de tip CStack) , n care ncap maximum 100 elemente de tip char. CStack<char> sc(100); n acest caz, compilatorul genereaz clasa ce rezult prin nlocuirea lui T cu char, dup care instaniaz obiectul sc. Constructorul acestuia primete ca argument valoarea 100. Pentru mai multe detalii urmrii exemplul de mai jos. Exemplul 8.4 #include <iostream.h> #include <conio.h> // Definirea clasei stack. Se vede c instanele ei nu sunt //protejate fa de excepii. template <class T> class CStack { T *v; T *top; int dims; public: //Constructor

CStack( int sz) { v=top=new T[dims=sz]; } //Desctructor ~CStack() { delete [ ] v; } //Inserare elemente in stiva void push(T a) { *top++=a; } //Extragere elemente dinstiva T pop() { return *--top; } }; //Functia principala void main() { int i; //Primul exemplu de instantiere a stivei generice ;numere ntregi CStack<int> st1(20); //ncarcare stiva for (i=0;i<=9;i++) { st1.push(i); } //Vizualizare continut stiva clrscr(); for (i=0;i<=9;i++) { gotoxy(35, wherey()+1); cout<<st1.pop()<<"*****";

} getch(); //Al doilea exemplu de instantiere a stivei generice; caractere, CStack<char> st2(20); //ncarcare stiva for (i=65;i<75;i++) { st2.push((char) i); } //Vizualizare continut stiva clrscr(); for (i=0;i<10;i++) { gotoxy(35, wherey()+1); cout<<st2.pop()<<"*****"; } getch(); } Exemplul 8.5 combin puterea abloanelor cu puterea pointerilor pentru a arta cum se poate regsi elementul de pe o poziie indicat, ntr-o colecie omogen de date. Exemplul 8.5 //T este tipul de baz al coleciei //buf conine colecia de elemente de tip T //poz indica pozitia elementului care ne intereseaza, 0-bazata #include <iostream.h> #include <conio.h> //Functia template template <class T> T retkey(T *buf,int poz) { for(int i=0;i<poz;i++) buf++; return *buf; }; void main() //ncepand cu A

{ int nr; int *tab; int *wtab; tab=new int(4); wtab=tab; *tab++=0; *tab++=1; *tab++=2; *tab=3; clrscr(); //Utilizare functie template nr=retkey(wtab,2); cout<<nr; getch(); }; Exemplul 8.6 schieaz soluia C/C++ pentru problema despachetrii unei nregistrri, dac operaiile de citire se fac la nivel de octet. Un astfel de instrument este absolut necesar dac vrem s scriem cod C/C++ pentru sortarea unui fiier oarecare, cu nregistrarea de lungime fix, a crui cheie se specific prin poziie (octetul de nceput, primul cmp ncepnd la octetul 0). Exemplul 8.6 #include <iostream.h> #include <conio.h> #include <alloc.h> struct Stud { int matr; char nume[15]; float bursa; int varsta; }; void *retcamp(void *buf,int poz,int ltot) { unsigned char *rec; rec=(char*)malloc(ltot); memmove(rec,buf,ltot); for(int i=0;i<poz;i++)

rec++; return (void*)rec; }; void main() { //Declarare initializare inregistrare Stud s={12,"Vulcanescu",10000,35}; //Declarare pointer generic void *sir; //Pointer receptor la casting catre float float *nrf; //Pointer receptor la casting catre int int *nri; //Alocare dinamica memorie pentru pointerii de mai sus sir=malloc(15); nrf=(float*)malloc(4); nri=(int*)malloc(2); //Utilizare retcamp() pentru recuperare nume student sir=retcamp((void *)&s,2,21); clrscr(); cout<<(char*)sir<<endl; getch(); //Utilizare retcamp() pentru recuperare bursa student sir=retcamp((void*)&s,17,21); nrf=(float*)sir; cout<<*nrf<<endl; getch(); //Utilizare retcamp() pentru recuperare varsta student sir=retcamp((void*)&s,21,23); nri=(int*)sir; cout<<*nri; getch(); }; Pointeri la funcii

O surs interesant de genericitate, n programarea C++ o reprezint i pointerii la funcii sau la metodele membre ale unor clase. Declararea unei variabile pointer la o funcie sau la o metod membr a unei clase se bazeaz pe o sintax care iese oarecum din cadrul general de declarare a variabilelor n C/C++. n esen, declaraia unei variabile pointer la funcie corespunde sintaxei de mai jos: <Tip returnat> (*<NumeVariabila>)([<Lista parametri>]); Utilizarea acestei sintaxe suport i cteva elemente despre semantica utilizrii pointerilor la funcii n Exemplul 8.7. Exemplul 8.7 #include <iostream.h> #include <conio.h> //Declarare pointer la functie //Numele pointerului este comp //comp este o variabila care poate pastra //adrese catre functii de comparare a doua valori numerice int (*comp)(void*,void*); //Functie de comparare specializata in numere intregi int icom(void *o1,void *o2) { if(*(int*)o1<*(int*)o2) return -1; if(*(int*)o1==*(int*)o2) return 0; if(*(int*)o1>*(int*)o2) return 1; }; //Functie de comparare specializata in numere reale //virgula mobila simpla precizie int fcom(void *o1,void *o2) { if(*(float*)o1<*(float*)o2) return -1; if(*(float*)o1==*(float*)o2) return 0; if(*(float*)o1>*(float*)o2) return 1; }; void main() { int*ip1=new int(100); int*ip2=new int(120); float*fp1=new float(1.75); float*fp2=new float(1.50);

//Utilizare pointer la functie cu versiunea icom comp=&icom; clrscr(); cout<<comp(ip1,ip2); getch(); //Utilizare pointer la functie cu versiunea fcom comp=&fcom; clrscr(); cout<<comp(fp1,fp2); getch(); };

8.3 Genericitatea n Java


Java nu dispune de pointeri i de template-uri. S-ar putea crede c genericitatea este dificil sau aproape imposibil n Java. Adevrul este c lucrurile nu stau chiar aa. n programarea orientat pe obiecte Java, putem combina fora referinelor la clase cu puterea oferit de motenire i interfee pentru a obine un suport interesant pentru programarea generic. Motenirea ajut la crearea cadrului organizat n care putem specifica mai multe tipuri de obiecte asupra crora efectum aceleai prelucrri. Conversiile down, permise ntre rubedeniile unei ierarhii de clase sunt eseniale pentru a implementa genericitatea. Interfeele ajut la specificarea cadrului natural de introducere, n Java, a referintelor la metodele membre ale unor clase. Exemplul 8.8 ilustreaz rolul motenirii n scrierea de cod Java pentru crearea i vizualizarea unei liste simplu nlnuite generice. Exemplul 8.9 ilustreaz simularea pointerului la o metod generic, n Java, cu ajutorul unei instane a unei clase singleton. Exemplul 8.10 ilustreaz simularea pointerului la o metod generic, n Java, cu ajutorul interfeelor. Exemplul 8.8 //Clasa care modeleaza nodul listei //capabil sa pastreze orice tip de data class Nod { private Object inf; Nod legs; public Object read() { return inf; }; public void write(Object x) { inf=x; }; }

//Clasa care modeleaza comportamentul //unei liste simplu inlantuite class Lista { Nod start; Nod prec; public Lista() { start=null; }; public void adaugdupa(Object on) { if(start==null) { Nod tamp=new Nod(); start=tamp; start.legs=null; start.write(on); prec=start; } else { Nod tamp=new Nod(); tamp.write(on); prec.legs=tamp; tamp.legs=null; prec=tamp; } } //Metoda nu respecta cerintele //care i-ar da dreptul sa figureze //in API-ul clasei. //Am specificat-o din motive didactice. //Se poate observa un prilej potrivit pentru utilizarea //enuntului instance of //In intentie, aceasta metoda este un iterator public void PentruToate() { Nod w=start; int tip=0;

Integer i; String s; do { if(w.read() instanceof Integer) tip=1; if(w.read() instanceof String) tip=2; switch(tip) { case 1: { i=(Integer)(w.read()); System.out.println(i);break; } case 2: { s=(String)(w.read()); System.out.println(s);break; } }; w=w.legs; } while(w!=null); } } public class CreLisGen { public static void main(String[] arg) { Nod obiect; Lista lis=new Lista(); for(int i=0;i<8;i++) { obiect=new Nod(); obiect.write(new Integer(i).toString()); lis.adaugdupa(obiect.read()); obiect=new Nod(); obiect.write(new Integer(i)); lis.adaugdupa(obiect.read()); }

lis.PentruToate(); } } Exemplul 8.9 //Clasa pretextInt introduce o strategie concreta de comparare //relativ la numere intregi //Exemplu de clasa SINGLETON class pretextInt { //Constructor privat //pentru a asigura caracterul de singleton private pretextInt() { }; public static final pretextInt INSTANCE=new pretextInt(); public int comp(Object a,Object b) { Integer ia,ib; ia=(Integer)a; ib=(Integer)b; if(ia.intValue()<ib.intValue())return -1; if(ia.intValue()==ib.intValue())return 0; else return 1; }; }; //Clasa pretextInt introduce o strategie concreta de comparare //relativ la numere reale in virgula mobila //Exemplu de clasa SINGLETON class pretextFlo { //Constructor privat //pentru a asigura caracterul de singleton private pretextFlo() { }; public static final pretextFlo INSTANCE=new pretextFlo();

public int comp(Object a,Object b) { Float fa,fb; fa=(Float)a; fb=(Float)b; if(fa.floatValue()<fb.floatValue())return -1; if(fa.floatValue()==fb.floatValue())return 0; else return 1; }; }; public class Simpfunc { public static void main(String[] s) { Integer nri1=new Integer(100); Integer nri2=new Integer(200); Float nrf1=new Float(1.75); Float nrf2=new Float(1.0); System.out.println(pretextInt.INSTANCE.comp(nri1,nri2)); System.out.println(pretextFlo.INSTANCE.comp(nrf1,nrf2)); }; }; Exemplul 8.10 //Interfata prin intermediul careia se va simula //ideea de pointer la metoda comp interface SimPointMet { int comp(Object a,Object b); }; //Clasa gazda a primei versiuni a metodei comp //Va utiliza interfata SimPointMet class pretextInt implements SimPointMet { public pretextInt() { };

public int comp(Object a,Object b) { Integer ia,ib; ia=(Integer)a; ib=(Integer)b; if(ia.intValue()<ib.intValue())return -1; if(ia.intValue()==ib.intValue())return 0; else return 1; }; }; //Clasa gazda a celei de-a doua versiuni a metodei comp //Va utiliza interfata SimPointMet class pretextFlo implements SimPointMet { public pretextFlo() { }; public int comp(Object a,Object b) { Float fa,fb; fa=(Float)a; fb=(Float)b; if(fa.floatValue()<fb.floatValue())return -1; if(fa.floatValue()==fb.floatValue())return 0; else return 1; }; }; public class PointMet { public static void main(String[] s) { //Interfata SimPointMet lucreaza in context pretextInt Integer ni1,ni2; Float nf1,nf2; SimPointMet INSTANCE1=new pretextInt(); //Interfata SimPointMet lucreaza in context pretextFlo SimPointMet INSTANCE2=new pretextFlo();

ni1=new Integer(100); ni2=new Integer(200); nf1=new Float(200); nf2=new Float(100); System.out.println(INSTANCE1.comp(ni1,ni2)); System.out.println(INSTANCE2.comp(nf1,nf2)); }; };

Capitolul 9
Fluxuri orientate pe obiecte. Perspectiva C++

9.1 Noiunea de flux

Una din problemele importante, care trebuie rezolvat ntr-o aplicaie real, este problema asigurrii persistenei datelor. Aceast problem este rezolvat n mod diferit de limbaje diferite. De fapt, este vorba despre imaginarea unor strategii de pstrare, cu ajutorul memoriilor externe, a rezultatelor prelucrrilor. Memoria intern fiind foarte rapid n acces, dar volatil, este mediul ideal pentru reprezentarea unor structuri de date ct mai flexibile i ct mai eficiente la interogarea i n timpul prelucrrii datelor. Prelungirea duratei de via a coleciilor de date (care necesit aceast prelungire a duratei de via), se bazeaz pe utilizarea unei varieti convenabile de memorie extern, mai lent din punct de vedere al accesului, dar avnd capacitatea de a pstra coleciile de date i dup terminarea execuiei programelor. Orict ar prea de ciudat, rezolvarea acestei probleme implic o serie de resurse hard i soft, precum: echipamente periferice adecvate, controllere, sistem de gestiune a fiierelor, driver-e, cod surs adecvat. Putem efectua operaii de intrare-ieire la nivel fizic, eludnd, pur i simplu, drivere i chiar sistemul de gestiune a fiierelor. Nu cred c merit s ne asumm astfel de responsabiliti, deoarece diminum o serie de caliti de baz ale sistemelor soft( portabilitatea, lizibilitatea i extensibilitatea fiind cel mai mult afectate). Dup o evoluie ndelungat a cutrilor n aceast direcie, s-a ajuns la dou mari tipuri de rezolvri elegante ale problemei: soluia fiiere-limbaj de nivel nalt i soluia fiier-SGBD. Prima soluie este tributar ideii de a lsa programatorului suficient libertate de micare n procesul de manipulare a fiierelor. Preul libertii este o dependen prea strns a aplicaiilor de structura fiierelor. A doua soluie este tributar ideii de a oferi aplicaiilor o ct mai mare independen fa de structura fiierelor, precum i soluii predefinite pentru o serie de tipuri standard de operaii specifice manipulrii fiierelor i coleciilor de fiiere (sisteme de fiiere, care mpreun cu aplicaiile aferente formeaz ceea ce numim baze de date). Limbajele de nivel, de regul, nu ofer cuvinte cheie pentru efectuarea de operaii I/O. Obinuina este de a oferi biblioteci de operaii care permit efectuarea operaiilor I/O. n condiiile n care diversitatea echipamentelor care ocazioneaz efectuarea operaiilor I/O este mare, este fireasc preocuparea specificatorilor limbajelor de programare de nivel nalt de a institui protocoale unitare de efectuare a operaiilor I/O. n C/C++, conceptul care permite o abstractizare convenabil a operaiilor I/O, n ideea tratrii unitare, este conceptul de flux (stream). Prin introducerea acestui concept, programatorul lucreaz cu o abstracie logic (fluxul), nu direct cu fiierul extern, care, n funcie de echipament, poate avea diferite forme de manifestare. m esen, un flux este un canal de comunicaie ntre o surs de date i un receptor. Fluxul este o abstracie logic, care asigur un anumit tip de funcionalitate la vedere, dar instituie i reguli, invizibile pentru programator, dar utile pentru optimizarea operaiilor I/O, precum utilizarea zonelor tampon (buffere) n cursul operaiilor I/O (vezi Figura 21).

< <
cout este legat la ecranul consolei Buffer asociat cu fluxul de ieire cout Operatorul << convertete reprezentarea intern n caractere

000 0000 0110 01000101000010

Reprezentarea unei variabile n memorie

Figura 21. Utilizarea buffer-ului n operaiile specifice fluxului cout

Necesitatea zonelor tampon asociate operaiilor I/O este indiscutabil, n condiiile n care exist, n mod sistematic, diferene semnificative de vitez de lucru ntre sursa de date a unui flux i receptorul datelor fluxului. Aadar, programatorul are nevoie de un instrument logic care s simplifice la maximum posibil, operaiile I/O i s le optimizeze n mod rezonabil. Acest instrument, n cazul limbajelor de nivel nalt, este fluxul C++ i Java ofer programatorilor colecii structurate de clase a cror colaborare furnizeaz comportamentul primitiv necesar pentru realizarea de operaii I/O flexibile. 9.2 Fluxurile C++ Sistemul de fiiere din C i C++ este proiectat s lucreze cu o mare varietate de echipamente, care include: terminale, drivere de disc, drivere de unitate de band, etc. Chiar dac echipamentele difer, sistemul de fiiere din C i C++ le transform ntr-un instrument logic numit flux (stream). Toate fluxurile se comport la fel. Deoarece fluxurile sunt independente de echipamente, o funcie care poate s scrie ntr-un fiier de pe hard poate fi folosit cu aceeai sintax pentru a scrie la alt dispozitiv. Sistemul de fiiere C/C+ + recunoate dou tipuri de fluxuri: text i binar. Fluxuri de tip text Un flux de tip text este o secven de caractere. Standardul ANSI C permite (dar nu impune) ca un flux de tip text s fie organizat n linii terminate cu un caracter de linie nou. Totui, caracterul de linie nou din ultima linie este opional, utilizarea sa fiind determinat de modul de implementare a compilatorului (Majoritatea compilatoarelor de C/C++ nu ncheie fluxul de tip text cu un caracter de linie nou. S mai semnalm faptul c, ntr-un flux de tip text pot s apar anumite transformri cerute de mediul de operare gazd (De exemplu, un caracter de linie nou poate fi nlocuit cu perechea nceput de rnd-linie nou. Acesta este motivul pentru care nu exist o relaie biunivoc ntre caracterele care sunt scrise sau citite i cele de la echipamentul extern. Fluxuri binare Un flux binar este o secven de octei ntr-o coresponden biunivoc cu cei de la echipamentul extern. Fiiere n C/C++ un fiier poate s fie: un fiier de pe disc, tastatura, ecranul monitorului, imprimanta, etc. Un flux se asociaz cu un anumit fiier efectund o operaie de deschidere. Odat deschis fluxul, este posibil schimbul de date ntre fiier i programul utilizator care l-a deschis. De observat faptul, trivial pentru cunosctori, c nu toate fiierele au aceleai posibiliti. De exemplu, un fiier de pe disc poate s admit un acces aleator la datele stocate n el, n timp ce imprimanta nu o poate face. Astfel c, putem concluziona, pentru claritate: Pentru sistemul I/O din C/C++ toate fluxurile sunt la fel dar nu i fiierele.

Dac fiierul admite cereri de poziionare, deschiderea fiierului iniializeaz pointerul de fiier la o valoare care indic nceputul fiierului. Pe msur ce se fac operaii de citire/scriere, pointerul de fiier este incrementat corespunztor naturii operaiei. Un fiier se disociaz de un flux n urma operaiei de nchidere. Dac este nchis un fiier deschis n operaii de scriere, coninutul buffer-ului fluxului asociat este scris la dispozitivul extern (acest proces se numete flushing=golire ). Toate fiierele se nchid automat cnd programul se termin normal. n caz de blocaj sau dac programul se termin ca urmare a apelului funciei abort(), fiierele nu se nchid. Cu meniunea c n fiierul antet stdio.h se gsesc structurile de control de tip FILE, indispensabile pentru lucrul cu fiiere n C, prezentm, n continuare contextul C++ referitor la sistemul I/O. Fluxuri C++ relativ la perifericele standard C++ asigur suportul pentru lucrul cu fluxuri standard n fiierul antet iostream.h. n acest fiier antet sunt definite dou ierarhii de clase care admit operaii de I/O. Clasa cu nivelul de abstractizare cel mai nalt se numete streambuf i asigur operaiile de baz de intrare/ieire. Ca programatori, nu folosii streambuf direct dect dac vei deriva propriile clase pentru efectuarea operaiilor I/O. A doua ierarhie pornete cu clasa ios, care accept operaii I/O formatate. Din ea sunt derivate clasele istream, ostream i iostream. Aceste clase sunt folosite pentru a crea fluxuri capabile s citeasc, s scrie, respectiv s citeasc i s scrie date din/ la echipamentele externe. Clasa ios conine o serie de alte ramuri relativ la lucrul cu fiiere pe care nu ne propunem s le studiem n cadrul acestei cri.

ios Stocheaz variabilele de stare ale unui flux i trateaz erorile

streambuf Implementeaz un buffer

istream Realizeaz conversia cu format sau fr format a caracterelor dintr-un streambuf

ostream Realizeaz conversia cu format sau fr format a datelor dintrun streambuf

istream_withassign Flux de intrare care definete operatorul = permind altui flux

sau streambuf s fie atribuit acestui flux

iostream Flux bidirecional att pentru intrare ct i pentru ieire

ostream_withassign
Flux de ieire care definete operatorul = permind altui flux sau streambuf s fie atribuit acestui flux

iostream_withassign Flux bidirecional cu operatorul de atribuire = n dotare

Figura 22. Clasele din biblioteca C++ iostream Se cuvine s observm (cu referire la Figura 22) c biblioteca iostream este unul dintre exemplele de utilizare a motenirii multiple n C++. Dup cum tim, n C++, o instan a unei clase derivate conine o copie a tuturor membrilor din clasa de baz. De aceea, n cazul unei clase ca iostream, care motenete att de la istream ct i de la ostream, fiecare avnd ca strmo comun clasa ios, se poate ajunge la dou copii ale membrilor din ios. In C++ putei evita acest efect prin declararea claselor istream i ostream ca avnd pe ios clas de baz virtual, astfel: class istream: virtual public ios {}; class ostream: virtual public ios {}; n acest fel, ne asigurm n iostream de o singur copie a resurselor motenite de la ios.

Fluxuri standard n C++ Cnd i ncepe execuia un program C++, se deschid automat patru fluxuri predefinite, pe care le prezentm n tabelul de mai jos. Flux
cin cout cerr clog

Semnificaie
Intrare standard Ieire standard Ieire standard pentru eroare Versiune cu memorie tampon pentru cerr

Echipament implicit
Tastatura Ecran Ecran Ecran

Tabelul 9.1. Fluxurile predefinite C++ Fluxurile cin (consol input), cout (consol output), cerr (consol error) corespund fluxurilor stdin, stdout, stderr din C. Implicit, fluxurile standard sunt folosite pentru a comunica cu consola. ns, n mediile care admit redirecionarea I/O, fluxurile standard pot fi redirecionate spre alte echipamente sau fiiere. I/O formatate n C++ relativ la fluxurile standard Sistemul de I/O din C++ v permite s formatai operaiile I/O, aa cum se ntmpla i n cazul utilizrii funciilor C pentru operaii I/O, precum: printf, cprintf, scanf,etc. De exemplu, se poate specifica mrimea unui cmp, baza unui numr, numrul de cifre dup punctul zecimal,etc. Operatorii din C++ utilizai pentru introducerea informaiilor de formatare sunt >> (operator de extracie dintr-un flux) i << (operator de inserie intr-un flux). Exist dou ci nrudite, dar conceptual diferite, prin care se pot formata datele. n primul rnd, putem avea acces direct la diferii membri ai clasei ios. n al doilea rnd, putem folosi funcii speciale numite manipulatori, care pot fi incluse n operaiile I/O. Prezentm, n continuare, modul de utilizare a manipulatorilor de formate, datorit accesibilitii mai mari a acestora. Manipulatorii standard sunt prezentai n tabelul de mai jos. Manipulator
dec endl ends flush hex oct resetiosflags(long f) setbase(int baza)

Exemplu de folosire
cout<<dec<<intvar; cout<<endl cout<<ends cout<<flush cout<<hex<<intvar; cin>>hex>>intvar cout<<oct<<intvar; cin>>oct>>intvar; cout<<resetioflags (ios::dec); cout<<setbase(10);

Efect
Convertete ntregi n cifre zecimale; corespunde formatului %d din C Trimite o nou linie n ostream i descarc bufferul Insereaz un caracter nul ntr-un flux Descarc bufferul fluxului ostream Conversie hexazecimal corespunztoare formatului %x din ANSI C Conversie octal (formatul %o din C) Reiniializeaz biii de formatare specificai de argumentul ntreg de tip long Stabilete baza de conversie la

cin>>setbase(8); setfill(int ch) setiosflags(long f) setprecision(int p) setw(int w) ws cout<<setfill(.); cin>>setfill( ); cout<<setiosflags(ios::dec); cin>> setiosflags(ios::hex); cout<<setprecision(6); cin>>setprecision(10); cout<<setw(6)<<var; cin>>setw(24)>>buf Cin>>ws;

argumentul ntreg (trebuie s fie 0,8,10 sau 16). Valoarea 0 este baza implicit. Stabilete caracterul folosit pentru completarea cmpurilor de mrime specificat Stabilete biii de formatare specificai de argumentul ntreg de tip long Stabilete precizia conversiei n virgul mobil la numrul specificat de zecimale Stabilete mrimea unui cmp la numrul specificat de caractere Elimin spaiile libere din fluxul de intrare

Tabelul 9.2. Manipulatori de formatare a operaiilor I/O n C++ Toi aceti manipulatori au prototipul n fiierul antet iomanip.h. Pentru a utiliza manipulatorii setiosflags() i resetiosflags() trebuie cunoscui indicatorii de formatare din tabelul de mai jos. Nume indicator
ios :: skipws ios :: left ios :: right ios :: scientific ios :: fixed ios :: dec ios :: hex ios :: oct ios :: uppercase ios :: showbase ios :: showpoint ios :: showpos ios :: unitbuf

Ce efect are utilizarea indicatorului


Elimin spaiile goale din intrare Aliniaz ieirea la stnga n interiorul limii cmpului Aliniaz ieirea la dreapta Folosete notaia tiinific pentru numerele n virgul mobil. Folosete notaia zecimal pentru numere n virgul mobil Folosete notaia zecimal pentru ntregi Folosete notaia hexazecimal pentru ntregi Folosete notaia octal pentru ntregi Folosete litere mari pentru ieire Indic baza sistemului de numeraie n cadrul ieirii (prefixul 0x pentru hexazecimal i prefixul 0 pentru octal Include un punct zecimal pentru ieiri n virgul mobil Include i semnul + la afiarea valorilor pozitive Golete toate fluxurile dup inserarea caracterelor ntr-un flux

Tabelul 9.3. Indicatori de formatare a operaiilor I/O n C++ Notaia ios::<Nume_indicator> este folosit pentru a identifica indicatorul ca pe un membru al clasei ios. Exemplificm cele spuse mai sus prin codul C++ de mai jos. Exemplul 9.1 #include<iostream.h> #include<iomanip.h> #include<conio.h>

#include<stdio.h> void main() { double nr; clrscr(); gotoxy(10,6); nr=7./3.; gotoxy(5,6); cout<<"Afisare nr. in virg. mobila format implicit..."; gotoxy(10,7); cout<<nr; gotoxy(5,9); cout<<"Afisare nr. in virg. mobila cu precizia specificata..."; gotoxy(10,10); cout<<setprecision(10)<<nr; gotoxy(5,12); cout<<"Afisare nr. in virg. mobila format virgula fixa..."; gotoxy(10,13); cout.setf(ios::fixed); cout<<setprecision(10)<<nr; gotoxy(5,15); cout<<"Afisare nr. in virg. mobila format exponenial..."; gotoxy(10,16); cout.setf(ios::scientific); cout<<setprecision(10)<<nr; gotoxy(5,18); cout<<"Afisare nr. in virg. mobila format exponential..."; cout<< cu afisare de semn; gotoxy(10,19); cout.setf(ios::scientific|ios::showpos); cout<<setprecision(10)<<nr; getch(); } Fluxuri asociate cu fiiere utilizator n C++ Chiar dac abordarea operaiilor I/O din C++ formeaz un sistem integrat, operaiile cu fiiere (altele dect cele predefinite), sunt suficient de specializate pentru a fi necesar s la discutm separat.

Pentru a efectua operaii I/O cu fiiere conform paradigmei C++, trebuie s includei n programul Dvs. fiierul antet fstream.h. Acesta definete mai multe clase, printre care ifstream, ofstream i fstream. Aceste clase sunt derivate din istream i, respectiv, din ostream la care ne-am referit i mai sus. Deschiderea i nchiderea unui fiier Un fiier se deschide, n C++, legndu-l de un flux. nainte de a putea s deschidei un fiier, trebuie, pentru nceput, s avei un flux. Exist trei tipuri de fluxuri: de intrare, de ieire i de intrare/ieire. Pentru a crea un flux de intrare, trebuie s-l declarai ca fiind din clasa ifstream. Pentru a crea un flux de ieire, trebuie s-l declarai ca fiind din clasa ofstream. Fluxurile care efectuiaz att operaii de intrare ct i operaii de ieire, trebuie declarate ca fiind din clasa fstream. Odat declarat fluxul, o modalitate de a-i asocia un fiier extern o reprezint utilizarea funciei open() avnd prototipul: void open(const char *nume_fisier , int mod, int acces=filebuf::openprot); n sintaxa de mai sus nume_fisier este un nume extern de fiier, care poate include i specificarea cii de acces. Valoarea parametrului mod determin modul de deschidere a fiierului. Parametrul mod poate avea una sau mai multe din valorile prezentate n tabelul de mai jos. Nume mod ios::app ios::ate ios::binary ios::in ios::nocreate Ios::noreplace Ios::out Ios:trunc Operaie Adaug date n fiier Cnd se deschide pentru prima dat, opereaz poziionarea n fiier la sfritul fiierului (ate nseamn la sfrit) Deschide fiierul n mod binar, inhibnd interpretarea caracterelor <CR> <LF> Deschide fiierul pentru citire Nu efectueaz deschiderea fiierului dac acesta nu exist deja Dac fiierul exist, ncercarea de a-l deschide pentru ieire eueaz, cu excepia cazului n care ios::app sau ios::ate sunt operate Deschide fiierul pentru scriere Trunchiaz fiierul dac el exist deja Tabelul 9.4. Diferite valori ale parametrului care stabilete modul de deschidere a unui fiier

Putei specifica mai mult de un mod de lucru pentru un fiier, folosind operatorul pe bii SAU cu modurile respective. De exemplu, pentru deschiderea unui fiier pentru ieire i poziionarea pointerului la sfritul lui se folosesc modurile ios::out i ios::ate astfel: ofstream oflux(o_fisier,ios::out | ios::ate);

ceea ce ne arat al doilea procedeu de deschidere a unui fiier, utiliznd constructorul clasei ofstream sau, de ce nu, ifstream, dac este cazul. Pentru a nchide un fiier, folosii funcia membru close(). Aceast funcie nu preia nici un parametru i nu returneaz nici o valoare. De analizat utilizarea funciei close() n exemplele care vor urma. Scrierea i citirea fiierelor n mod text Sunt dou operaii foarte uoare, realizate apelnd la operatorii >> i << ntr-un mod asemntor operaiilor referitoare la consola sistemului, cu deosebirea c n loc s folosii cin i cout apelai la un flux legat de un fiier . Codul de mai jos arat cum poate fi afiat pe ecranul monitorului coninutul unui fiier text. Exemplul 9.2 #include <fstream.h> #include <stdlib.h> #include<conio.h> //Functia principala a programului //citeste linia de comanda a programului void main(int argc,char *argv[]) { //Linia de comanda a programului //trebuie sa contina doi parametri if (argc!=2) { cerr<<"Mod de utilizare : lisfis <nume fisier CPP>\n"; exit(0); } // Deschidere fisier text de nume specificat in argv[1] ifstream in(argv[1],ios::in); if (!in) { cerr<<"Fisierul nu poate fi deschis!"; exit(0); } char c; clrscr(); while (in.get(c)) { if (wherey()>20) {

gotoxy(20,24); cout<<"Press any key to continue..."; getch(); clrscr(); } cout<<c; } in.close(); } n cazul n care dorim o copie pe un suport de memorie extern a unui fiier text, procedm ca n Exemplul 9.3. Exemplul 9.3 #include <iostream.h> #include <stdlib.h> #include <fstream.h> //Copierea unui fisier text void main(int argc, char **argv) { //Buffer-ul de citire char buffer[1]; //Deschiderea fluxului input in mod text //pentru operatii de citire ifstream input(argv[1], ios::in); //Verificare esuare if (input.fail()) { cout << "Eroare deschidere fisier..." << argv[1]; exit(1); } //Deschidere flux output in mod text //pentru operatii de scriere ofstream output(argv[2], ios::out); //Verificare esuare if (output.fail()) {

cout << "Error opening the file " << argv[2]; exit(1); } //Copierea efectiva do { input.read(buffer, sizeof(buffer)); if (input.good()) output.write(buffer, sizeof(buffer)); } while (!input.eof()); //Inchiderea fluxului input input.close(); //Inchiderea fluxului output output.close(); } Scrierea i citirea fiierelor n mod binar Exist dou modaliti de a scrie i citi date binare ntr-un fiier. Prima modalitate se refer la utilizarea funciilor get() i put(). Aceste funcii sunt orientate pe octei, ceea ce nseamn c get() va citi un octet de date iar put() va scrie un octet de date. Funcia get() are mai multe forme; o prezentm, n continuare, mpreun cu omoloaga ei put(), pe cea mai des folosit: istream &get(char &ch); ostream &put(char ch); Funcia get() citete un singur caracter din streamul asociat i memoreaz valoarea sa n ch. De asemenea, se mai observ c funcia returneaz o referin ctre flux. Funcia put() scrie ch n flux i returneaz fluxului o referin. Un exemplu de utilizare a funciei get n Exemplul 9.4. n help-ul kit-ului cu care lucrai putei gsi informaii relativ la alte forme ale acestor dou funcii (suprascrieri, de fapt). Exemplul 9.4 #include <iostream.h> #include <fstream.h> int main(int argc, char *argv[]) { char ch;

if(argc!=2) { cout << "Utilizare: DISPFT <nume_fisier>" << endl; return 1; } ifstream in(argv[1], ios::in | ios::binary); if(!in) { cout << "Cannot open file."; return 1; } while(in) { in.get(ch); cout << ch; } } A doua modalitate de a citi i scrie blocuri de date n binar este folosirea funciilor din C++ read() i write(). Prototipurile lor sunt: istream &read(unsigned char *buf, int numar); ostream &write(const unsigned char *buf, int numar); Funcia read() citete numar octei din fluxul asociat i l pune n buffer-ul indicat de buf . Funcia write() scrie n fluxul asociat numar octei citii din buffer-ul spre care indic buf. Modul de utilizare a acestor dou funcii n contextul unor fluxuri binare se poate urmri n Exemplul 9.5. Exemplul 9.5 #include <iostream.h> #include <stdlib.h> #include <fstream.h> //Exemplu de copiere binara a unui fisier //Copierea se face octet cu octet void main(int argc, char **argv) { //Buffer-ul utilizator char buffer[1]; //Declarare flux input si deschidere in citire binara

ifstream input(argv[1], ios::in | ios::binary); //Verificare terminare operatie de deschidere flux if (input.fail()) { cout << "Eroare deschidere fisier..." << argv[1]; exit(1); } //Deschidere flux output in operatii de scriere binara ofstream output(argv[2], ios::out | ios::binary); //Verificare terminare operatie de deschidere flux if (output.fail()) { cout << "Eroare deschidere fisier..." << argv[2]; exit(1); } //Copierea propriu zisa do { input.read(buffer, sizeof(buffer)); if (input.good()) output.write(buffer, sizeof(buffer)); } while (! input.eof()); input.close(); output.close(); } Detectarea EOF Putei s detectai sfritul fiierului folosind funcia membru eof() ,care are acest prototip: int eof(); Ea returneaz o valoare nenul cnd a fost atins sfritul fiierului; altfel, returneaz zero. Utilizarea funciei eof() i alte elemente legate de lucrul cu fiiere, prezentm n codul de mai jos, care realizeaz afiarea coninutului unui fiier att n hexazecimal ct i n cod ASCII, atunci cnd codul asociat este printabil. Exemplul 9.6

#include <fstream.h> #include <ctype.h> #include <iomanip.h> #include <stdlib.h> #include<conio.h> void main(int argc,char *argv[]) { if (argc!=2) { cerr<<"Mod de utilizare : lishex <nume fisier CPP>\n"; exit(0); } ifstream in(argv[1],ios::in|ios::binary); if (!in) { cerr<<"Fisierul nu poate fi deschis!"; exit(0); } register int i,j; int count=0; char c[16]; cout.setf(ios::uppercase); clrscr(); while(!in.eof()) { for(i=0;i<16 && !in.eof();i++) { in.get(c[i]); } if (i<16) i--; for(j=0;j<i;j++) cout<<setw(3)<<hex<<(int) c[j]; for(;j<16;j++) cout<<"\t"; for(j=0;j<i;j++) if(isprint(c[j])) cout<<c[j]; else cout<<"."; cout<<endl; count++; if(count==16) { count=0;

cout<<"Press ENTER to continue!"; getch(); clrscr(); cout<<endl; } } in.close(); } Fluxuri asociate cu fiiere avnd structura definit de utilizator Pentru a simula, n C++, lucrul cu fiiere cu tip, uzual n limbaje precum Pascal, Object Pascal sau n SGBD-uri, calea de urmat este artat n Exemplul 9.7. Exemplul 9.7 #include <iostream.h> #include <fstream.h> #include <string.h> #include <stdlib.h> #include <conio.h> //Declarare structura inregistrare struct cont { char nume[40]; float sold; unsigned long numar_cont; }; void main(void) { //Instantiere structura struct cont c; //Setare campuri inregistrare strcpy(c.nume, "Pavaloaie Matei"); c.sold = 2500000; c.numar_cont = 98765432; //Deschide flux binar in opeartii de scriere ofstream outsol("sold.nsn", ios::out | ios::binary); if(!outsol)

{ cout << "Nu se poate deschide fisier in scriere." << endl; exit (1); } //Scrie inregistrare in flux outsol.write((unsigned char *) &c, sizeof(struct cont)); //Inchide flux outsol.close(); //Deschide flux binar in citire ifstream insol("sold.nsn", ios::in | ios::binary); if(!insol) { cout << "Nu se poate deschide fisierul in citire" << endl; exit (1); } //Citeste inregistrare din flux insol.read((unsigned char*) &c, sizeof(struct cont)); clrscr(); //Afisare formatata campuri pe ecran cout << c.nume << endl; cout << "Numar de cont: " << c.numar_cont << endl; cout.precision(2); cout.setf(ios::fixed); cout << "Sold (LEI):" << c.sold << endl; insol.close(); getch(); } Accesul aleator n fiiere n sistemul de I/O din C++, accesul aleator se efectueaz folosind funciile seekg() i seekp(). Formele lor cele mai uzuale sunt: istream &seekg(streamoff offset, seek_dir origine); ostream &seekp(streamoff offset, seek_dir origine); unde streamoff este un tip definit n iostream.h, capabil s conin cea mai mare valoare valid pe care o poate avea offset, iar seek_dir este o enumerare care are una din valorile:

ios::beg ios::cur ios::end Sistemul de I/O din C++ opereaz cu doi pointeri asociai unui fiier. Unul este pointerul de get , care specific unde va aprea urmtoarea operaie de intrare n fiier. Cellalt este pointerul de put i specific unde va avea loc urmtoarea operaie de ieire. Dup fiecare operaie de intrare sau ieire, pointerul corespunztor este avansat automat, secvenial. Dar, folosirea funciilor seekg() i seekp() permite un acces nesecvenial la datele din fiier. Funciile seekg() i seekp() deplaseaz pointerul de nregistrare corespunztor cu offset octei fa de origine. C++ pune la dispoziie i dou metode care permit determinarea poziiei curente dintr-un flux: tellp() i tellg(). Cu meniunea c lucrul cu fluxuri, n C++, are nenumrate alte faete pentru a cror prezentare nu dispunem de timpul i spaiul necesar, ncheiem aceast scurt excursie n problematica fiierelor. n fine, pentru curioi reamintim i faptul c, nsui btrnul C are propria filozofie, extrem de puternic, n ceea ce privete lucrul cu fluxuri. Fluxurile de tip ir de caractere Fluxurile pot fi folosite i pentru operaii asupra irurilor de caractere, cu rezultate asemntoare celor prezentate n Exemplul 9.8. nc odat, n Exemplul 9.8 se observ importan practic a caracterului unitar al abordrii operaiilor I/O n C++. Exemplul 9.8 //Aplicatie la fluxuri de siruri de caractere #include <iostream.h> #include <strstrea.h> #include <conio.h> #include <string.h> void main(void) { //Sirul de caractere care va fi asociat cu un flux char in_string[] = "Ionescu Valentin 1200000 Zizinului 14"; //Deschidere flux de siruri de caractere istrstream ins(in_string); char nume[20]; char prenume[20]; float salar; char strada[30]; int nr; clrscr();

//Extragere de date din fluxul asociat cu sirul de caractere //si afisare formatata pe ecran ins >> nume; ins >> prenume; cout.setf(ios::left); cout.width(15); cout <<"Nume angajat:"; cout.width(30); strcat(nume," "); strcat(nume,prenume); cout<<nume<< endl; cout.width(15); ins >> salar; ins >> strada; ins >> nr; strcat(strada," nr."); cout.setf(ios::fixed); cout.width(15); cout<<"Strada:"<<strada<<" "<<nr<<endl; cout.width(15); cout<<"Salariu: "<<salar<<endl; getch(); } Suprancrcarea operatorilor << i >> relativ la fluxurile standard Operatorii de inserie (<<), respectiv, extracie (>>) pot fi instruii s lucreze i asupra altor tipuri de date dect cele fundamentale. n spe, este vorba de faptul c parametrii lor pot fi obiecte utilizator, obiecte a cror clas definitoare suprancarc, prin una din metodele cunoscute, aceti operatori. Astfel c devine perfect plauzibil codul prezentat n Exemplul 9.9. Exemplul 9.9 #include <iostream.h> #include <string.h> #include <conio.h> class Angajat { public: Angajat(void) {}; Angajat(char *name, char sex, int age, char *phone) { strcpy(Angajat::name, name);

Angajat::sex = sex; Angajat::age = age; strcpy(Angajat::phone, phone); }; friend ostream &operator<<(ostream &cout, Angajat ang); friend istream &operator>>(istream &stream, Angajat &ang); private: char name[40]; char phone[20]; int age; char sex; }; ostream &operator<<(ostream &cout, Angajat ang) { cout << "Nume: " << ang.name <<endl; cout<< "Sex: " << ang.sex<<endl; cout << "Varsta: " << ang.age <<endl; cout<< "Telefon: " << ang.phone << endl; return cout; } istream &operator>>(istream &stream, Angajat &ang) { cout << "Nume : "; stream >> ang.name; cout << "Sex: "; stream >> ang.sex; cout << "Varsta: "; stream >> ang.age; cout << "Telefon: "; stream >> ang.phone; return stream; } void main(void) { Angajat persoana; clrscr(); cout<<"Preluare date...."<<endl; cin >> persoana; getch();

clrscr(); cout<<"Afisare date...."<<endl; cout << persoana; getch(); } Suprancrcarea operatorilor << i >> relativ la operaiile cu fiiere De la cele prezentate n Exemplul 9.9 i pn la a spune c obiectele pot fi scrise n fiiere i, respectiv, citite din fiiere, nu mai este dect un pas. Prin urmare, o modalitate de a asigura persistena coleciilor de obiecte este s redefinim operatorii de inserie i extracie astfel nct acetia s tie s lucreze n contextul obiectelor respective. Mai mult, putem s ne imaginm colecii heterogene de obiecte pstrate n fiiere, cu condiia s avem strategie de parcurgere a fiierului respectiv, ntr-o astfel de ipotez. Exemplul 9.10 ilustreaz suprancrcarea operatorilor << i >> astfel ca acetia s lucreze n contextul unui flux asociat cu un fiier a crui nregistrare are structur dat. Exemplul 9.10 #include <iostream.h> #include <string.h> #include <fstream.h> #include <conio.h> #include <ctype.h> //Clasa care supraincarca operatorii << si >> class Agenda { public: char nume[40]; char numar[6]; Agenda(){}; Agenda(char *n,char *nr) { strcpy(nume,n); strcpy(numar,nr); }; //Metode friend care supraincarca operatorii << si >> friend ostream &operator<<(ostream &stream,Agenda a); friend istream &operator>>(istream &stream,Agenda &a); }; //Implementare supraincarcare operator << ostream &operator<<(ostream &stream,Agenda a) {

stream<<a.nume; stream<<" "; stream<<a.numar<<endl; return stream; }; //Implementare supraincarcare operator >> istream &operator>>(istream &stream,Agenda &a) { stream>>a.nume; stream>>a.numar; return stream; }; void main() { Agenda ag; char car; ofstream oat("telefon",ios::out); clrscr(); do { cout<<"Nume :"; cin>>ag.nume; cout<<endl<<"Numar:"; cin>>ag.numar; cout<<endl; oat<<ag; cout<<"Continuati(D,N):"<<endl; } while(toupper(getch())!='N'); oat.close(); ifstream iat("telefon",ios::in); clrscr(); while(!iat.eof()) { strcpy(ag.nume,""); strcpy(ag.numar,""); iat>>ag; cout<<ag.nume<<" "; cout<<ag.numar<<endl; };

getch(); }; Problema asigurrii persistenei coleciilor de obiecte nu este, ntotdeauna, att de simpl cum rezult din cele spuse mai sus. O astfel de situaie este cea n care obiectele agreg recursiv alte obiecte, fapt care complic problema asigurrii persistenei, att sub aspectul scriere obiecte ct i sub aspectul citire obiecte. Acest lucru se va sublinia n continuare.

Capitolul 10
Fluxuri obiect orientate i serializare n Java

10.1 Scurt introducere


Dei mai tnr dect C++, Java a acumulat deja o experien apreciabil n ceea ce privete rezolvarea problemei persistenei datelor. El propune mai multe ierarhii de clase, care pun n valoare conceptul, deja clasic, de flux i propune i elemente suport pentru serializarea coleciilor de obiecte. La fel ca n C++, stream-urile Java ofer posibiliatea tratrii unitare a interfeelor de comunicare ntre entitile unui sistem informatic, fie ele entiti soft sau hard. Un stream este un canal de comunicaie generalizat, definit n mod unic prin capetele sale: sursa i destinaia. De cele mai multe ori, unul din capete este chiar programul n care se declar stream-ul. i n Java, exist dou tipuri fundamentale de stream-uri: input stream-urile, utilizate pentru citirea datelor din diferite surse i output stream-urile, utilizate pentru scrierea datelor n diferite destinaii. Mai putem observa i alte asemnri ntre perspectiva Java i perspectiva C++, n ceea ce privete persistena: exist fluxuri standard i alte fluxuri dect cele standard (relativ la fiiere, relativ la iruri de caractere, relativ la buffe-re de octei), exist filtre de diferite tipuri. Programatorul care vrea s nvee s lucreze eficient cu fluxurile n Java, se izbete de o situaie oarecum asemntoare celei din C++, dac nu cumva mai rea: Instrumentele puse la dispoziie de Sun sunt extrem de diversificate i se promoveaz chiar filozofii diferite de lucru cu fluxurile, datorit

faptului c prima ierarhie de clase care fundamenta lucrul cu fluxuri era orientat pe 8 bii (dou ierarhii avnd drept clase rdcin clasele InputStream i OutputStream) iar din raiuni de implementare a conceptului de Internationalization s-a dezvoltat o soluie alternativ care este orientat pe 16 bii (dou ierarhii avnd drept clase rdcin clasele Reader i Writer)13. Astfel c, programatorul se confrunt cu dou ierarhii de clase, ntre care exist destule asemnri pentru a nu dispera cu totul dar i destule deosebiri pentru a nu putea renuna la nici una dintre ele deocamdat. Cert este c soluia Java pentru lucrul cu fluxuri este puternic orientat pe obiecte, ca soluie tehnic. n sfrit, s mai precizm faptul c oferta C++ pentru salvarea-restaurarea obiectelor i gsete n Java un rspuns mai ndrzne, sub forma serializrii. Despre toate acestea n cele ce urmeaz.

10.2 Stream-uri standard n Java


Java pune la dispoziia utilizatorului, n ideea comunicrii cu consola, trei stream-uri standard: Standard Input Standard Output StandardError.

Stream-ul Standard Input este utilizat pentru preluarea datelor, n timp ce celelalte dou sunt utilizate pentru afiarea datelor i a mesajelor de eroare. Implicit, Standard Input preia datele de la tastatur iar celelalte dou afieaz datele la monitor. Unul dintre avantajele utilizrii stream-urilor, n comunicarea cu utilizatorul, l reprezint i posibilitatea de a redirecta stream-urile standard spre alte periferice. n Java, toate stream-urile standard sunt accesate prin clasa System: pentru Standard Input avem System.in, pentru Standard Output avem System.out, pentru Standard Error avem System.err. System.in este un membru static al clasei System i este de tipul InputStream, o clas abstract din pachetul java.io. O parte dintre funciile clasei InputStream i aspecte relativ la redirectare n cele ce urmeaz. Funcii de citire i de control al poziiei la citire: public abstract int read() throws IOException public int read(byte b[]) throws IOException public int read(byte b[], int off, int len) throws IOException public long skip(long n) throws IOException Funcii de repetare citire, funcii de gestiune buffer:
13

Pentru mai multe detalii relativ la structura acestor ierarhii se poate consulta Clin Marin Vduva, Programarea n Java, Editura Albastr, ClujNapoca, 2001

public synchronized void mark (int readlimit) public synchronized void reset() throws IOException public boolean markSuported() Funcii de informare: public int available() throws IOException Funcia de nchidere stream: public void close() throws IOException. De fapt, aceste metode ale clasei InputStream prefigureaz elementele fundamentale ale strategiei Java de lucru cu fluxurile. Funcia read(), fr nici un parametru, citete octetul curent din stream i l returneaz sub forma unui ntreg ntre 0 i 255. Dac s-a ajuns la captul stream-ului, se returneaz valoarea -1. Funciile read, avnd ca parametru un tablou de octei, citesc de la poziia curent din stream un numr de octei egal cu len sau cu lungimea tabloului b i l ncarc n tabloul b, la poziia off dac aceasta este specificat. Ele returneaz numrul de octei citii n buffer-ul b sau -1 dac s-a ajuns la captul stream-ului. Funcia skip este utilizat pentru a muta poziia citirii peste un anumit numr de octei. Toate aceste metode blocheaz firul de execuie n care ne aflm, pn cnd toate datele care se cer sunt disponibile, s-a ajuns la sfritul stream-ului sau s-a aruncat o excepie. Redirectarea stream-urilor standard se poate realiza cu ajutorul urmtoarelor trei funcii, disponibile n clasa System: public static void setIn(InputStream in) public static void setOut(PrintStream out) public static void setErr(PrintStream err) n Exemplul 10.1 sunt artate elementele de protocol fundamentale pentru lucrul cu stream-uri n Java, cu referire la stream-urile standard. Este vorba despre urmtoarele elemente invariabile: Asocierea fluxului cu un fiier, echipament standard sau alt structur de date. Efectuarea de operaii de tipul citire sau scriere de date. Poziionarea n flux, cnd acest lucru este posibil nchiderea fluxului

Aa cum se va vedea i n exemplele care vor urma i cum, de altfel, era previzibil din signatura metodelor pe care le-am anunat ca fcnd parte din structura clasei InputStream, tratarea excepiilor n cazul operaiilor I/O este imperativ. n Exemplul 10.2 se arat cadrul Java pentru redirectarea stream-urilor standard. Exemplul 10.1 //Utilizare stream-uri standard //Acestea sunt asociate implicit cu echpamentele periferice //Tastatura Sistem.in //Ecranul monitorului Sistem.out / Sistem.err import java.io.*; import java.util.*; public class IO1 { public static void main(String[] s) { boolean exit=false; System.out.println("Incerc IO\n "+ " Informatii despre sistem"); while(!exit) { System.out.println("Optiuni...."); System.out.println("\t (D) Data"); System.out.println("\t (P) Proprieteti sistem"); System.out.println("\t (T) Terminare"); try { char readChar=(char)System.in.read(); int avlb=System.in.available(); System.in.skip(avlb); switch(readChar) { case 'D': case 'd': System.out.println("Data:"+ new Date().toString()); break; case 'P': case 'p': Properties prop=System.getProperties(); prop.list(System.out); break; case 'T': case 't': System.out.println("La revedere..."); exit=true;

break; } } catch(IOException e) { System.err.println(e.getMessage()); } } } } Exemplul 10.2 // Exemplific redirectarea stream-urilor standard import java.io.*; public class Redirect { // Arunca exceptii IOException la consola public static void main(String[] args) throws IOException { //Flux de intrare cu buffer asociat cu fisierul //text care contine programul BufferedInputStream in = new BufferedInputStream( new FileInputStream("Redirect.java")); //Filtru asociat cu fluxul definit mai sus PrintStream out =new PrintStream( new BufferedOutputStream( new FileOutputStream("test.out"))); //Redirectare fluxuri standard System.setIn(in); System.setOut(out); System.setErr(out); //Filtrarea stream-ului standard cu ajutorul clasei //BufferedReadre pentru a permite utilizarea metodei readLine() //versiune ne-deprecated. //Deschidere flux BufferedReader br = new BufferedReader(new InputStreamReader String s; //Citire flux pana la terminare (System.in));

while((s = br.readLine()) != null) System.out.println(s); //Inchidere flux out.close(); } }

10.3 Clasa File n lucrul cu stream-uri


Clasa File, din biblioteca I/O Java, furnizeaz o abstractizare independent de platform pentru obinerea informaiilor despre fiiere, ca de exemplu: numele de cale, dimensiunea fiierului, data modificrii, etc. Pentru a obine astfel de informaii despre fiier trebuie ca, mai nti, s creai un obiect File utiliznd unul din constructorii de mai jos: File (String cale); File (String cale, String nume); File (File dir, String nume); Parametrul cale din prima versiune de constructor conine calea ctre fiier, n timp ce acelai parametru, din cea de-a doua versiune, conine calea directorului. Paramerul nume specific numele fiierului. Parametrul dir, din ce-a de-a treia versiune permite utilizarea unui alt obiect File, ca director. Utilitatea clasei File poate fi desprins, ca un nceput, i din Exemplul 10.3 i Exemplul 10.4. Exemplul 10.3 //Listarea tuturor fiierelor din directorul curent import java.io.*; public class TestFile { public static void main(String[] sir) { File dc=new File("."); String listaf[]=dc.list(); for(int i=0;i<listaf.length;i++) { if(i % 23==0) { try { System.in.read(); System.in.read(); } catch(IOException e) {}

}; System.out.println(listaf[i]); } } } Exemplul 10.4 //Listarea tuturor fisierelor din directorul curent //avand o extensie data import java.io.*; class JavaFileFilter implements FilenameFilter { public boolean accept(File dir, String nume) { return nume.endsWith(".java"); } } public class FiltruF { public static void main(String[] sir) { File dc=new File("."); String listaf[]=dc.list(new JavaFileFilter()); for(int i=0;i<listaf.length;i++) { if(i % 23==0) { try { System.in.read(); System.in.read(); } catch(IOException e) {} }; System.out.println(listaf[i]); } } }

10.4 Citirea datelor dintr-un stream


Aa cum, probabil c s-a neles, exist dou grupuri mari de stream-uri, n funcie de obiectivul lor: scrierea sau citirea datelor. Pentru citirea datelor dintr-un flux avem clasele derivate din clasele abstracte InputStream sau Reader. Amndou aceste clase sunt clase abstracte care furnizeaz metode care permit operaii asemntoare celor pe care leam prezentat deja n discuia referitoare la stream-urile standard. Referindu-ne la InputStream, fiind o clas abstract nu poate fi utilizat n instanierea unui obiect stream. Pentru crearea obiectelor de tip stream, pornind de la clasa InputStream, s-au derivat mai multe clase. Aceste clase le-am putea mpri, la rndul lor, n dou grupuri importante: clase stream conectate la diferite tipuri de surse; clase stream care se conecteaz la cele de mai sus, adugnd noi operaii i funcionnd ca filtre aplicate operaiilor de citire.

Clasele din prima categorie sunt derivate direct din clasa InputStream. Pentru a putea utiliza efectiv interfaa anunat de clasa InputStream a fost nevoie de construirea unor clase derivate din aceasta, clase care s poat fi conectate la diferite tipuri de surse reale. Dintre aceste clase remarcm ca fiind cel mai mult folosite: ByteArrayInputStream Este o clas care permite conectarea unui stream la un tablou de octei. Operaiile de citire din stream vor permite citirea datelor din tabloul de octei, gestiunea operaiilor fiind asumat de ctre instana stream. StringBufferInputStream Permite conectarea unui stream la un ir de caractere. Aceast clas este considerat deprecated, recomandndu-se utilizarea clasei StringReader. FileInputStream Este una dintre cele mai utilizate clase de tip stream i ne ofer posibilitatea conectrii cu un fiier pentru a citi datele nregistrate n acesta. Dup cum se poate vedea, la analiza atent a definiiei clasei FileInputStream, aceasta conine mai multe versiuni de constructori, care permit asocierea stream-ului cu un fiier n diferite moduri: numele specificat ca o variabil sau constant String, numele specificat ca o variabil File, numele specificat ca o variabil FileDescriptor. O categorie important de clase derivate din InputStream o formeaz clasele de tip filtru, derivate din clasa FilterInputStream, la rndul ei, derivat din clasa InputStream. Dintre clasele din aceast categorie se cuvine s remarcm cteva utilizate intens: DataInputStream Este una dintre cele mai utilizate clase dintre cele de tip filtru. Aceast clas conine mai multe funcii, care permit citirea unor tipuri fundamentale de date (int, float, double, char, etc) ntr-un mod independent de main. De regul,

aceast clas este utilizat mpreun cu clasa DataOutputStream, clas care are operaii de scriere n stream, orientate pe tipurile fundamentale. mpreun, aceste dou clase, ofer o soluie elegant la problema gestiunii fiierelor a cror nregistrare are structura definit de utilizator. Este momentul s remarcm c, n principiu, n Java, la fel ca n C++, putem avea fluxuri de octei, fluxuri de caractere i fluxuri de date cu structur cunoscut. Revenind la clasa DataInputStream, prezentm, n continuare, cteva dintre metodele mai mult folosite. Metoda boolean readBoolean() byte readByte Int readUnsignedByte() short readShort() char readChar() int readInt() long readLong() float readFloat() double readDouble() String readLine() String readUTF() Rolul Citete o dat boolean Citete un octet Citete un octet unsigned Citete un short (16 bii) Citete un caracter Unicode Citete un ntreg pe 32 bii Citete un long pe 64 bii Citete un numr real n virgul mobil simpl precizie Citete un numr real n virgul mobil dubl precizie Citete o linie Citete un ir de caractere n format UTF (Unicode Text Format) Tabelul 10.1. Metode ale clasei DataInputStream remarcm i clase precum: BufferedInputStream,

Dintre clasele de tip filtru merit s mai LineNumberInputStream, ZipInputStream, etc.

10.5 Scrierea datelor ntr-un stream


Pentru scrierea datelor ntr-un stream avem clasele derivate din clasele abstracte OutputStream sau Writer. Amndou aceste clase sunt clase abstracte, care furnizeaz metode care permit operaii de scriere a datelor n streamuri, complementare celor de citire, ca funcionalitate. Referindu-ne la clasa OutputStream, fiind o clas abstract nu poate fi utilizat n instanierea unui obiect stream. Totui, ea este o ocazie de a specifica o interfa general valabil n operaiile de scriere n fluxuri, avnd urmtoarea definiie: public abstract class OutputStream { public abstract void write(int b) throws IOException

public void write(byte b[] ) throws IOException public void write(byre[], int off, int len) throws IOException public void flush()throws IOException public void close()throws IOException } Pentru crearea obiectelor de tip stream, pornind de la clasa OutputStream, s-au derivat mai multe clase. Aceste clase le-am putea mpri, la rndul lor, n dou grupuri importante: clase stream conectate la o destinaie; clase stream care se conecteaz la cele de mai sus, adugnd noi operaii i funcionnd ca filtre aplicate operaiilor de scriere.

Clasele din prima categorie sunt derivate direct din clasa OutputStream. Pentru a putea utiliza efectiv interfaa anunat de clasa OutputStream a fost nevoie de construirea unor clase derivate din aceasta, clase care s poat fi conectate la diferite tipuri de destinaii reale. Dintre aceste clase remarcm ca fiind cel mai mult folosite: ByteArrayOutputStream Este o clas care permite conectarea unui stream la un tablou de octei. Operaiile de scriere n stream vor permite adugare de date n tabloul de octei, gestiunea operaiilor fiind asumat de ctre instana stream. FileOutputStream Este clasa pereche a clasei FileInputStream, dintre cele mai utilizate clase de tip stream i ne ofer posibilitatea conectrii cu un fiier pentru a scrie date n acesta. Dup cum se poate vedea, la analiza atent a definiiei clasei FileOutputStream, aceasta conine mai multe versiuni de constructori, care permit asocierea stream-ului cu un fiier n diferite moduri: numele specificat ca o variabil sau constant String, numele specificat ca o variabil File, numele specificat ca o variabil FileDescriptor. O categorie important de clase derivate din OutputStream o formeaz clasele de tip filtru, derivate din clasa FilterOutputStream, la rndul ei, derivat din clasa OutputStream. Dintre clasele din aceast categorie se cuvine s remarcm cteva utilizate intens: DataOutputStream Este una dintre cele mai utilizate clase dintre cele de tip filtru. Aceast clas conine mai multe funcii care permit citirea unor tipuri fundamentale de date (int, float, double, char, etc) ntr-un mod independent de main. De regul, aceast clas este utilizat mpreun cu clasa DataInputStream, clas care are operaii de citire n stream, orientate pe tipurile fundamentale. mpreun, aceste dou clase ofer o soluie elegant la problema gestiunii fiierelor, a cror nregistrare are structura definit de utilizator.

Clasa DataOutputStream, are o serie de metode folosite, dup caz, la realizarea operaiilor de scriere n streamuri. Metoda void writeBoolean(boolean v) void writeByte(int v) void writeBytes(String s) void writeShort(int v) void writeChar(int v) void writeInt(int v) void writeLong(long v) void writeFloat(float v) void writeDouble(double v) void writeChars(String s) void writeUTF(String S) Rolul Scrie o dat boolean Scrie un octet Scrie un ir de caractere ca o secven de octei Scrie un short (16 bii) Scrie un caracter Unicode Scrie un ntreg pe 32 bii Scrie un long pe 64 bii Scrie un numr real n virgul mobil simpl precizie Scrie un numr real n virgul mobil dubl precizie Scrie un ir de caractere ca o secven de 16 bii Scrie un ir de caractere n format UTF (Unicode Text Format) Tabelul 10.2. Metode ale clasei DataOutputStream

Dintre clasele de tip filtru merit s mai remarcm i clase precum: BufferedOutputStream, PrintStream, ZipOutputStream, etc. Relativ la lucrul cu fiiere, un rol important l joac clasa RandomAccessFile, care nu este subclas nici a clasei InputStream, nici a clasei OutputStream. ns, cu ajutorul instanelor ei, putei efectua n acelai timp att operaii de scriere ct i de citire. n plus, dup cum arat i numele, un obiect RandomAccessFile furnizeaz acces aleator la datele dintr-un fiier, ceea ce instanele descendenilor claselor InputStream sau OutputStream nu pot. Pentru compatibilitate, la utilizare, cu clasele DataInputStream i DataOutputStream, clasa RandomAccessFile implementeaz interfeele DataOutput i DataInput, interfee pe care le implementeaz i clasele DataInputStream i DataOutputStream. O discuie asemntoare se poate purta relativ la ierarhiile de clase ale cror rdcini sunt clasele Reader i Writer, iearhii care implementeaz alternativa I/O Java pe 16 bii. Funcionalitatea lor, ns, nu elimin cu totul utilitatea ierarhiilor pe care le-am prezentat mai sus, pe scurt. nelegerea exact a modului de lucru cu oricare dintre ierarhiile menionate mai sus poate fi realizat consultnd documentaia aferent kit-urilor jdk1.o sau jdk1.1. Jungla protocoalelor de lucru cu stream-uri n Java este, dup cum se vede, mult mai diversificat dect oferta C+ +. Programatorul din lumea real trebuie s se acomodeze cu elementele fundamentale relativ la stream-urile Java, rmnnd ca n situaii excepionale s nvee utilizarea unor procedee excepionale de manevrare a stream-urilor.

Exemplele care urmeaz ncearc s evidenieze elemente de protocol socotite uzuale n lucrul cu stream-uri n Java. Exemplul 10.5 //Situatii tipice de utilizarea fluxurilor in Java import java.io.*; public class IOStreamDemo { // Metoda ridica exceptii la consola public static void main(String[] args) throws IOException { //1a. Citirea orientata pe linii intr-un fisier text BufferedReader in = new BufferedReader( new FileReader("IOStreamDemo.java")); String scit; String sImRAM = new String(); //s2 pastreaza continutul fisierului IOStreamDemo.java //ca imagine RAM while((scit = in.readLine())!= null) sImRAM += scit + "\n"; in.close(); // 1b. Citire de la tastatura: BufferedReader stdin =new BufferedReader( new InputStreamReader(System.in)); System.out.print("Enter a line:"); System.out.println(stdin.readLine()); System.in.read(); // 2. Citire din memorie //Se va folosi sImRAM, creat la 1a StringReader in2 = new StringReader(sImRAM); int c; //Afisare imagine memorie a continutului //fisierului IOStreamDemo.java while((c = in2.read()) != -1) System.out.print((char)c); System.in.read(); System.in.read();

// 3. Preluare date formatate in memorie //Din nou se apeleaza la imaginea memorie a //fisierului IOStreamDemo.java try { DataInputStream in3 =new DataInputStream( ByteArrayInputStream(sImRAM.getBytes())); while(true) System.out.print((char)in3.readByte()); } catch(EOFException e) { System.err.println("End of stream"); } System.in.read(); System.in.read(); // 4. Creare fisier format output try { BufferedReader in4 =new BufferedReader( new StringReader(sImRAM)); PrintWriter out1 =new PrintWriter( new BufferedWriter( new FileWriter("IODemo.out"))); int lineCount = 1; while((scit = in4.readLine()) != null ) out1.println(lineCount++ + ":" + scit); out1.close(); } catch(EOFException e) { System.err.println("End of stream"); } String sir; BufferedReader inper = new BufferedReader( new FileReader("IODemo.out")); System.out.println("################################"); while((sir = inper.readLine())!= null) System.out.println(sir); new

inper.close(); System.in.read(); System.in.read(); // 5. Salvare si consultare date cu tip try { DataOutputStream out2 =new DataOutputStream( new BufferedOutputStream( new FileOutputStream("Data.txt"))); out2.writeDouble(3.14159); out2.writeBytes("Acesta este numarul PI\n"); out2.writeDouble(1.41413); out2.writeUTF("Radacina patrata a lui 2"); out2.close(); DataInputStream in5 =new DataInputStream( new BufferedInputStream( new FileInputStream("Data.txt"))); BufferedReader in5br =new BufferedReader( new InputStreamReader(in5)); // Trebuie sa folositi DataInputStream pentru date: System.out.println(in5.readDouble()); // Numai metoda readUTF() va recupera // sirul Java-UTF corect: // Cu readLine() se citesc corect date // scrise cu writeBytes. System.out.println(in5br.readLine()); System.out.println(in5.readDouble()); System.out.println(in5.readUTF()); } catch(EOFException e) { System.err.println("End of stream"); } // 6.Citire/scriere fisier in acces aleator RandomAccessFile rf =new RandomAccessFile("rtest.dat", "rw"); for(int i = 0; i < 10; i++) rf.writeDouble(i*1.414); rf.close();

rf =new RandomAccessFile("rtest.dat", "rw"); rf.seek(5*8); rf.writeDouble(47.0001); rf.close(); rf =new RandomAccessFile("rtest.dat", "r"); for(int i = 0; i < 10; i++) System.out.println("Value " +i+":"+rf.readDouble()); rf.close(); } } Ca observaii finale la cele discutate pn acum relativ la problema persistenei datelor n Java, a meniona: Puternica orientare pe obiecte a soluiilor Java la problema persistenei. Flexibilitatea cu care putem utiliza diferitele varieti de fluxuri (filtrare, redirectare) Obligativitatea tratrii excepiilor I/O n codul Java, ceea ce sporete coeficientul de robustee al codului Java afectat operaiilor I/O.

10.6 Serializarea obiectelor


Java 1.1 introduce un nou concept, numit serializarea obiectelor. Utiliznd serializarea, un obiect poate fi transformat ntr-o secven de octei ( care poate fi transmis n reea sau care poate fi stocat ntr-o specie de memorie), secven care poate fi folosit pentru refacerea complet a obiectului iniial. Avantajul serializrii este evident, prin faptul c simplific procedura de transmitere a unui obiect ntre dou entiti ale unui sistem informaional automatizat. Prin utilizarea serializrii, programatorul a scpat de grija transformrii obiectului ntr-o succesiune de octei, de ordonarea lor, de problema diferenei de reprezentare pe diferite platforme, toate acestea fcndu-se automat. n rezumat, putem spune c serializarea introduce un alt nivel de transmitere a datelor, la care unitatea fundamental de transfer este obiectul. n practic, serializarea obiectelor este impus de situaii precum: RMI (Remote Method Invocation) Comunicare de obiecte ntre aplicaii aflate pe calculatoare diferite, ntr-o reea. Lightweight persistence Posibilitatea stocrii unui obiect, ca ansamblu unitar, n vederea utilizrii lui n cadrul ulterioare. Tehnologia Java Beans Tehnologie Java de lucru cu componente.

unei

execuii

Procedeul Java de serializare/deserializare a unui obiect este simplu, dac acesta ndeplinete anumite condiii. Procedeul Java de serializare/deserializare Serializarea unui obiect este o opereaie relativ simpl, care implic lucrul cu clasa ObjectOutputStream, care nfoar un stream de tipul OutputStream, conectat la o destinaie. Conexiunea cu un stream OutputStream se face prin intermediul constructorului: public ObjectOutputStream(OutputStream out) throws IOException; Clasa ObjectOutputStream este derivat din clasa OutputStream i implementeaz interfaa ObjectOutput (derivat din DataOutput). Interfaa ObjectOutput declar metodele specifice serializrii unui obiect. Dintre aceste metode, cea mai important este metoda writeObject avnd signatura: public final void writeObject(Object obj) throws IOException Prin intermediul clasei ObjectOutputStream, se pot scrie att date primitive (cu ajutorul metodelor declarate de interfaa DataOutput) ct i obiecte, folosind metoda writeObject. Deserializarea (adic reconstituirea unui obiect dintr-un stream) este, de asemenea, simpl i implic utilizarea clasei ObjectInputStream. Aceast clas nfoar un stream de tipul InputStream, stream transmis ca i parametru n constructorul clasei: public ObjectInputStream(InputStream in) throws IOException, StreamCorruptedException Clasa ObjectInputStream este derivat din clasa InputStream i implementeaz interfaa ObjectInput (derivat din interfaa DataInput). Interfaa ObjectInput declar metodele specifice deserializrii unui obiect. Dintre acestea, cea mai important este metoda readObject, definit astfel: public final Object readObject() throws OptionalDataException, ClassNotFoundException, IOException Excepia OptionalDataException apare n cazul n care, n locul unui obiect n stream se afl un tip de dat primitiv. Excepia ClassNotFoundException apare atunci cnd clasa obiectului din stream nu poate fi gsit, n contextul actual de execuie. O nelegere mai bun a serializrii / deserializrii poate fi obinut urmrind exemplele de mai jos. Exemplul 10.6 // Exemplificarea serializrii obiectelor import java.io.*;

class Data implements Serializable { private int i; Data(int x) { i = x; } public String toString() { return Integer.toString(i); } } public class Serializ implements Serializable { //Generare numar aleator intreg private static int r() { return (int)(Math.random() * 10); } private Data[] d = {new Data(r()), new Data(r()), new Data(r())}; private Serializ next; private char c; // Valoarea lui i indic numarul de elemente din lista Serializ(int i, char x) { System.out.println(" Serializ constructor: " + i); c = x; if(--i > 0) next = new Serializ(i, (char)(x + 1)); } Serializ() { System.out.println("Constructor implicit"); } public String toString() { String s = ":" + c + "("; for(int i = 0; i < d.length; i++) s += d[i].toString(); s += ")";

if(next != null) s += next.toString(); return s; } // Ridica exceptii la consola public static void main(String[] args) throws ClassNotFoundException, IOException { Serializ w = new Serializ(6, 'a'); System.out.println("w = " + w); ObjectOutputStream out =new ObjectOutputStream( new FileOutputStream("serializ.out")); out.writeObject("Serialize storage"); out.writeObject(w); out.close(); // Also flushes output ObjectInputStream in =new ObjectInputStream( new FileInputStream("serializ.out")); String s = (String)in.readObject(); Serializ w2 = (Serializ)in.readObject(); System.out.println(s + ", w2 = " + w2); ByteArrayOutputStream bout =new ByteArrayOutputStream(); ObjectOutputStream out2 =new ObjectOutputStream(bout); out2.writeObject("Serializ storage"); out2.writeObject(w); out2.flush(); ObjectInputStream in2 =new ObjectInputStream( new ByteArrayInputStream(bout.toByteArray())); s = (String)in2.readObject(); Serializ w3 = (Serializ)in2.readObject(); System.out.println(s + ", w3 = " + w3); } } Exemplul 10.7 import java.io.*; import java.util.*; class House implements Serializable {} class Animal implements Serializable

{ String name; House preferredHouse; Animal(String nm, House h) { name = nm; preferredHouse = h; } public String toString() { return name + "[" + super.toString() + "], " + preferredHouse + "\n"; } } public class Serializ1 { public static void main(String[] args) throws IOException, ClassNotFoundException { House house = new House(); ArrayList animale = new ArrayList(); animale.add(new Animal("Grivei -cainele", house)); animale.add(new Animal("Coco papagalul", house)); animale.add(new Animal("Vasile -motanul", house)); System.out.println("animale: " + animale); ByteArrayOutputStream buf1 =new ByteArrayOutputStream(); ObjectOutputStream o1 =new ObjectOutputStream(buf1); o1.writeObject(animale); //Inca odata o1.writeObject(animale); // Scriem si intr-un stream diferit ByteArrayOutputStream buf2 =new ByteArrayOutputStream(); ObjectOutputStream o2 =new ObjectOutputStream(buf2); o2.writeObject(animale); // Acum le citim ObjectInputStream in1 =new ObjectInputStream( new ByteArrayInputStream(buf1.toByteArray()));

ObjectInputStream in2 =new ObjectInputStream( new ByteArrayInputStream(buf2.toByteArray())); ArrayList animale1 =(ArrayList)in1.readObject(); ArrayList animale2 =(ArrayList)in1.readObject(); ArrayList animale3 =(ArrayList)in2.readObject(); System.out.println("animale1: " + animale1); System.out.println("animale2: " + animale2); System.out.println("animale3: " + animale3); } } Exemplele 10.6 i 10.7 arat, printre altele, i condiiile pe care trebuie s le ndeplineasc un obiect pe care vrem s l serializm. n spe, este vorba de faptul c pentru ca obiectul s poat fi serializat, clasa lui definitoare trebuie s respecte una din urmtoarele condiii: S implementeze interfaa Serializable n aceast situaie transformrile obiect-stream i stream-obiect se pot face automat. S implementeze interfaa Externalizable i s suprascrie metodele acesteia writeExternal i readExternal. n acest caz, programatorul este responsabil (prin specificarea metodelor anterior amintite) de transformarea obiectului n secvene de octei i invers. Evident, spaiul de care dispunem nu ne permite s tratm exhaustiv problemele pe care le pune asigurarea persistenei datelor n aplicaiile Java. Scopul acestei cri a fost de a realiza o deschidere asupra universului problematic dezvoltat n Java n jurul ideii de persisten. St n puterea fiecrui cititor n parte s se aplece cu temeinicie asupra aspectelor de detaliu sau , de ce nu, filosofice, neelucidate nc.

Capitolul 11
Programare concurent cu suport obiect orientat. Perspectiva Java

11.1 Noiuni de programare concurent


Programarea concurent presupune existena mai multor sarcini care trebuie s fie executate n paralel, ceea ce implic, ntr-o form sau alta, partajarea resurselor comune ale sistemului pe care se deruleaz execuia sarcinilor n cauz. Fcnd abstracie de arhitectura hard14 care o susine, programarea concurent poate fi materializat prin suport pentru programarea multitasking, suport pentru programarea multifir i suport pentru programarea distribuit. Atunci cnd o aplicaie este implementat pe un sistem multiprocesor, se spune c avem o aplicaie multiprocesat. n situaia n care aplicaia este implementat pe o reea de calculatoare, spunem c aplicaia este distribuit. n acest curs nu ne vom interesa de programarea distribuit i nici de programarea multitasking. Pentru a fi posibil programarea multitasking, mai nti sistemul de operare trebuie s fie capabil de execuia simultan a mai multor programe. n cazul n care aa ceva este posibil, la nivelul limbajelor de programare ne putem pune probleme de
14

Sistem monoprocesor, sistem multiprocesor, sistem vectorial, sistem distribuit, etc.

sincronizare a accesului la resursele comune. Din acest punct de vedere, relaia dintre Java i Windows, ca sistem de operare, nu este extrem de cordial. n schimb Object Pascal, limbajul de programare n mediul de programare vizual Delphi, gndit pentru a realiza aplicaii avnd ca int platforma Windows, posed nveli sintactic specific pentru rezolvarea problemelor de partajare a resurselor critice, n spiritul WIN32API15. Pe de alt parte, programarea distribuit este posibil, n Java, apelnd la tehnologii care susin corespunztor acest stil de programare(RMI, CORBA, etc.). n aceast carte ne vom interesa de posibilitile pe care le ofer Java pentru a face programare multifir. Majoritatea compilatoarelor de C++, legate de maina MSDOS, nu ofer suport nativ sau nveli sintactic corespunztor pentru programarea multifir, ci doar rudimente sintactice pentru simularea greoaie a multitasking-ului. n sfrit, s mai observm c programarea multifir la care ne referim va fi asociat cu posibilitile unei maini monoprocesor, ceea ce nseamn, iari, c sistemul de operare este arbitrul care stabilete regulile de baz care trebuie urmate pentru ca un fir de execuie s poat accesa reursele partajabile ale mainii (ndeosebi timpul UC). Arbitrajul exercitat de sistemul de operare (n relaia cu timpul UC) se reduce, practic, la acordarea unor cuante de timp UC tuturor firelor de execuie active la un moment dat, eventual, n funcie de prioritile asociate acestora la creare. Toate celelalte probleme care decurg din execuia simultan a mai multor fire de execuie sunt de competena programatorilor. Aceste probleme se regsesc, generic, n sintagma comunicare i sincronizare, pentru care limbajele ofer mijloace a cror ntrebuinare este la latitudinea programatorilor. S mai observm c noiunea de fir de execuie se refer la o unitate de prelucrare, asociat cu noiunea de proces, n sensul c fiecare fir de execuie este gzduit n spaiul de adrese al unui proces. n fine, trebuie spus c, referindu-ne la Java, funcia main() a unui program Java este, ea nsi, un fir de execuie, care se numete firul principal de execuie. O situaie asemntoare apare i n Object Pascal unde programul principal, aflat n fiierul cu extensia .dpr este asimilat cu noiune de fir de execuie principal. Lucrul cu fire de execuie este o necesitate, n foarte multe situaii de programare. Dac, de exemplu, ne gndim la o aplicaie care simuleaz calculul tabelar, nu este greu de priceput necesitatea mai multor fire de execuie: unul care se ocup de interactivitatea cu utilizatorul, unul care gestioneaz implicaiile modificrii coninutului celulei curente asupra coninutului altor celule, etc. Vom ncerca, n continuare, s fixm, ct mai clar posibil, bazele utilizrii firelor de execuie n Java.

11.2 Fire de execuie (thread-uri) n Java


Pentru ca programatorul Java s poat realiza aplicaii multifir, Java ofer n pachetul java.lang, deci chiar n java core, dou clase i o interfa: clasa Thread clasa ThreadGroup interfaa Runnable Clasa Thread i interfaa Runnable ofer suport pentru lucrul cu fire de execuie, ca entiti separate ale aplicaiei iar clasa ThreadGroup permite crearea unor grupuri de fire de execuie, n vederea tratrii acestora ntr-un mod unitar.

15

Interfaa de Programare a Aplicaiilor sub sistemul de operare Windows.

Notaie UML pentru

interfa
Runnable Relaie de realizare

Thread

ThreadGroup

Figura 23. Resurse Java predefinite pentru programarea multifir. Relaiile dintre ele. Dup cum se poate observa, n Figura 23, clasa Thread implementeaz interfaa Runnable iar clasa ThreadGroup se compune din mai multe obiecte Thread. Simbolurile folosite pentru a indica relaia de compunere dintre ThreadGroup i Thread, precum i relaia de realizare dintre Thread i Runnable sunt de provenien UML. Deoarece discuia referitoare la grupuri de fire se bazeaz pe nelegerea lucrului cu fire independente, n continuare ne vom ocupa de problema utilizrii firelor de execuie independente. Pentru a crea un fir de execuie n Java avem dou posibiliti: Definirea unei clase derivate din clasa Thread Definirea unei clase care implementeaz interfaa Runnable

Crearea unui fir de execuie derivnd clasa Thread


Alegnd aceast variant, avem de efectuat un numr redus de operaii: Definirea unei clase derivate din clasa Thread. Derivarea se face, dup cum se tie, cu o sintax de tipul: class FirulMeu extends Thread { //Date membre //Funcii membre } Suprascrierea funciei public void run(), motenit de la clasa Thread, n clasa derivat. Aceast metod trebuie s implementeze comportamentul firului de execuie. Aa cum metoda main() este metoda apelat de Java Runtime System n momentul n care se execut o aplicaie Java, metoda run() este metoda apelat cnd se execut un fir. De fapt, trebuie s subliniem c atunci cnd se pornete maina virtual Java (JVM), odat cu ea se pornete un fir de execuie care apeleaz metoda main(). JVM i va nceta execuia n momentul in care nu mai exist fire n execuie sau a fost apelat metoda exit() a clasei System. Instanierea unui obiect fir, folosind operatorul new:

FirulMeu firulmeu=new FirulMeu(); Pornirea firului instaniat, prin apelul metodei start(), motenit de la clasa Thread.

firulmeu.start(); Acestea sunt operaiile strict necesare pentru a ncepe lucrul cu fire de execuie n Java, utiliznd clasa Thread. Exemplul 11.1 //Clasa care modeleaza firul class TFirPers extends Thread { static int id=0; int[] vect=new int[10]; //Constructorul clasei public TFirPers() { }; //Metoda run(), care modeleaza comportamentul firului public void run() { id++; System.out.println("Lucreaza TFirPers.... "+id); for(int j=0;j<10;j++) vect[j]=j; }; }; //Clasa care modeleaza aplicatia public class Fire { public static void main(String sir[]) { int[] vecmain=new int[20]; //Declarare referinte TFirPers fir1,fir2;

//Alocare referinte-fir de executie fir1=new TFirPers(); fir2=new TFirPers(); //Lansarea in executie a firelor fir1.start(); fir2.start(); try { //Intarzierea firului principal pentru //a lasa ragaz firelor derivate din Thread //sa lucreze Thread.currentThread().sleep(2000); } catch(InterruptedException e) {} //Valorificarea rezultatelor furnizate de //cele doua fire de executie for(int k=0;k<10;k++) vecmain[k]=fir1.vect[k]; for(int l=0;l<10;l++) vecmain[l+10]=fir2.vect[l]+10; for(int i=0;i<20;i++) System.out.println(vecmain[i]); }; }; Codul Java, prezentat n Exemplul 11.1, face apel la un mic subterfugiu (adormirea firului principal de executie timp de 2 secunde pentru a lsa timp firelor de executie, paralele firului principal, s-i ndeplineasc atribuiile. Dac nu se acord acest rgaz, se va observa c firul principal va accesa datele corespunztoare firelor secundare nainte ca acestea s fie conforme ateptrilor noastre. Altfel spus, paralelismul specific lucrului cu mai multe fire de execuie este efectiv i, prin Exemplul 11.1, se atrage deja atenia asupra modificrii atitudinii programatorului fa de problema organizrii structurilor de prelucrare.

Crearea unui fir de execuie utiliznd interfaa Runnable


O alt modalitate de a crea fire de excuie este utilizarea interfeei Runnable. Aceast modalitate devine interesant n momentul n care se dorete ca o clas de tip Thread, pe care o implementm, s moteneasc capabiliti disponibile n alte clase. Operaiile specifice crerii unui fir de execuie utiliznd interfaa Runnable sunt urmtoarele:

Definirea unei clase care implementeaz interfaa Runnable. Aceasta se face utiliznd sintaxa adecvat i implementnd cel puin metodele interfeei Runnable (de fapt, doar metoda public void run()). class FirRunnable extends Exemplu implementes Runnable { //Definitie } Clasa care implementeaz interfaa Runnable trebuie s suprascrie funcia public void run().

public void run() { //Cod aferent } Se instaniaz un obiect al clasei de mai sus, cu o sintax de tipul:

FirRunnable obiectRunnable=new FirRunnable(); Se creaz un obiect de tip Thread, utiliznd un constructor care are ca i parametru un obiect de tip Runnable. n acest mod se asociaz un fir cu o metod run().

Thread firulMeu=new Thread(obiectRunnable); n sfrit, se pornete firul, la fel ca n metoda derivrii din Thread a firului.

Paii precizai mai sus se pot vedea i n Exemplul 11.2 Exemplul 11.2 //Clasa care modeleaza aplicatia public class FirRunn { public static void main(String s[]) { System.out.println("Creare obiect Runnable..."); classRunnable obiectRunn=new classRunnable(); System.out.println("Creare fir..."); Thread fir=new Thread(obiectRunn); System.out.println("Start fir..."); fir.start();

System.out.println("Din nou in main()..."); } } //Clasa auxiliara class Display { public void display(String mesaj) { System.out.println(mesaj); } } //Clasa care implementeaza interfata Runnable si mosteneste //clasa Display class classRunnable extends Display implements Runnable { public void run() { int nrpasi=3; display("Run are "+nrpasi+" pasi de facut..."); for(int i=0;i<3;i++) display("Pasul: "+i); display("Run si-a terminat munca..."); } }

Controlul unui fir de execuie


Problema controlului unui fir de execuie este legat de cunoaterea strilor posibile ale firelor de execuie. n spe, pe timpul execuiei unui program Java multifir, o instan Thread poate s se afle n una din urmtoarele patru stri: new, runnable, blocked i dead. Atunci cnd crem un fir de execuie, acesta intr n starea new. n aceast stare firul de execuie ateapt apelarea metodei start() a firului. Nici un cod nu ruleaz nc. n starea runnable un fir execut codul prezent n metoda sa run(). Pentru ca firul s treac din starea new n starea runnable trebuie executat metoda start(). Nu se recomand apelarea direct a metodei run() deoarece face acest lucru, n locul dumneavostr, metoda start(). Cnd un fir de execuie este inactiv despre el se spune c este n starea blocked sau not runnable. Un fir poate deveni inactiv dac apelm metode precum sleep(), suspend() sau wait(), ori dac trebuie s atepte dup anumite metode I/O pn la finalizarea execuiei acestora. Dup cum se va vedea, fiecare dintre aceste metode are un mecanism propriu de refacere a strii runnable pentru fir. n Exemplul 11.1 am folosit deja metoda sleep() pentru a rezolva o problem banal de sincronizare ntre firul principal de execuie i firele secundare.

Suspendarea i reluarea execuiei unui fir

Am vzut deja cum putem suspenda execuia unui fir pentru o perioad de timp prin utilizarea metodei sleep() a clasei Thread. Putem suspenda execuia unui fir i pn la apariia unor condiii obiective de reluare a execuiei. n acest sens putem utiliza metoda suspend() a clasei Thread, care pune un fir n starea not runnable, pn la apelarea metodei resume(). Ca un exemplu, utilizarea metodei suspend() permite oprirea unei secvene de animaie la apsarea butonului mouse-ului i reluarea animaiei la ridicarea degetului de pe butonul mouse-ului. De asemenea, un gen special de suspendare/reluare a execuiei unui fir se realizeaz i cu ajutorul perechii de metode wait()/notify() asupra creia vom reveni mai jos.

11.3 Sincronizarea firelor


Dac sunt executate asincron, mai multe fire care partajeaz anumite date s-ar putea s fie obligate s-i sincronizeze activitile pentru a obine rezultate corecte. Pe lng posibilitile pe care le ofer metode precum sleep() sau suspend()/resume(), n Java a fost introdus modificatorul synchronized, tocmai pentru a introduce un cadru adecvat atomizrii activitilor, n condiii de concuren la resurse. Ideea de baz a modificatorului synchronized este ct se poate de simpl: primul fir care intr n posesia unui obiect marcat de modificatorul synchronized rmne proprietar al obiectului pn cnd i termin execuia. n acest mod se creaz un cadru simplu pentru evitarea coliziunilor n timpul accesului concurent la resurse. Ceea ce este simplu nu este ntotdeauna i eficient. Uneori, preul sincronizrii s-ar putea s fie mai mare dect poate suporta clientul aplicaiei (timpii de execuie pot fi diminuai drastic).

Sincronizare bazat pe modificatorul synchronized


Modifcatorul synchronized poate fi utilizat pentru a realiza sincronizarea firelor. Orice fir are propia sa memorie de lucru, unde i ine copii proprii ale variabilelor pe care le utilizeaz. Cnd este executat un fir, acesta opereaz numai asupra acestor copii. Memoria principal (main memory), asociat firului principal, conine copia master a fiecrei variabile. Exist reguli care condiioneaz modul n care se poate efectua schimb de coninut ntre cele dou tipuri de copii ale variabilelor. Important pentru sincronizare este, ns, faptul c memoria main conine i zvoare, care pot fi asociate obiectelor sau metodelor declarate synchronized. Firele pot intra n competiie pentru achiziionarea zvoarelor. Aciunile de zvorre i dezvorre (dac un astfel de cuvnt exist!) sunt atomice, asemenea aciunilor de citire sau scriere. Aceste zvoare pot fi utilizate pentru sinconizarea activitilor unui program multifir. Declararea ca synchronized a unui obiect sau a unei metode determin asocierea acestora cu un zvor. Important este c un singur fir, la un moment dat, poate s nchid zvorul, altfel spus, un singur fir poate deine obiectul asociat cu zvorul. Dac un fir vrea s acceseze un obiect sau o metod sincronizat, dar gsete zvorul nchis, el trebuie s atepte ntr-o coad, pn cnd zvorul va fi deschis de ctre proprietarul lui circumstanial. Astfel c, n aplicaiile Java multifir, putem ntlni: sincronizare cu metode, sinconizare pe blocuri, sincronizare cu obiecte, pentru a introduce la anumite nivele, disciplina de utilizare mutual exclusiv a acestor trei categorii de concepte. Elemente de sintax i aspecte referitoare la modul de utilizare a acestor tehnici se pot urmri n Exemplul 11.3, Exemplul 1.4 i Exemplul 11.5. Modelele teoretice utilizate n limbajele care ofer suport pentru programarea multifir ofer i alte soluii la problema sincronizrii. Java ofer suport pentr majoritatea acestor modele, remarcndu-se, fa de alte limbaje, prin

aducerea problemei concurenei n interiorul limbajului, spre deosebire de alte soluii, care se bazeaz pe enunuri sistem pentru implementarea prelucrrilor multifir. Pentru informarea cititorului, dou mari direcii de rezolvare a problemelor de sincronizare sunt: monitoarele (introduse de C.A.R. Hoare) i semafoarele (introduse de Dijkstra). Fiecare dintre aceste soluii pune n discuie concepte precum seciunea critic, prin care se nelege o poriune de cod la care accesul concurent trebuie monitorizat, pentru a evita disfunciile n utilizarea anumitor resurse. Atrag atenia cititorului i asupra ofertei limbajului Java n ceea ce privete posibilitatea de a defini grupuri de fire, a cror manevrare unitar poate constitui un avantaj, n anumite situaii. De asemenea, n Java exist i posibilitatea de a defini nite fire speciale, numite daemon-i, fire a cror destinaie este asigurarea de servicii pentru celelalte fire de execuie. Exemplul clasic de daemon, n Java este firul care asigur funcia de garbage collector. Exemplul 11.3 //Ilustreaza sincronizarea bazata pe obiecte //Obiectul monitor este balanta. class unFir extends Thread { //Obiectul monitor static Integer balanta = new Integer(1000); static int cheltuieli=0; public void run() { int vol; for(int i=0;i<10;i++) { try { sleep(100); } catch(InterruptedException e){} int bon=((int)(Math.random()*500)); //Accesul la blocul de cod de mai jos este //monitorizat cu ajutorul obiectului balanta synchronized(balanta) { if(bon<=balanta.intValue()) { System.out.println("Verif:"+bon); balanta=new Integer(balanta.intValue()-bon); cheltuieli+=bon; System.out.print("Balanta: "+balanta.intValue());

System.out.println("Cheltuieli: "+cheltuieli); } else { System.out.println("Respins: "+bon); } } } } } public class Lacat { public static void main(String s[]) { new unFir().start(); new unFir().start(); } } Exemplul 11.4 //Ilustreaza sincronizarea cu obiecte sinchronized //apeland la wait() si notify() class Fir1 extends Thread { Object ob; Fir1(Object obi) { ob=obi; } public void run() { while(true) { System.out.println("Firul "+getName()); try { synchronized(ob) { ob.wait(); }

} catch(InterruptedException e) {} } } } class Fir2 extends Thread { Object ob; Object obman=new Object(); Fir2(Object obi) { ob=obi; } public void run() { while(true) { System.out.println("Firul "+getName()); try { synchronized(ob) { ob.notify(); } } catch(Exception e) { System.out.println("Exceptie: "+e); } try { synchronized(obman) { obman.wait(2000); } } catch(InterruptedException e) {} }

} } public class WaitNoti { public static void main(String[]s) { //Obiect pretext pentru sincronizare Object obiect=new Object(); //Obiect pe care se face asteptarea Object obman=new Object(); Fir1 fir1=new Fir1(obiect); Fir2 fir2=new Fir2(obiect); fir1.start(); fir2.start(); try { synchronized(obman) { obman.wait(35000); } } catch(InterruptedException e) {} } } Exemplul 11.5 //Ilustreaza sincronizarea cu metode synchronized class Distribuitor { int marfa=0; //Metoda atomizata cu ajutorul modificatorului //synchronized public synchronized int consuma() { int temp; while(marfa==0) { try

{ wait(); } catch(InterruptedException e) {} } temp=marfa; marfa=0; System.out.println("Consumat :"+temp); notify(); return temp; } //Metoda atomizata cu ajutorul modificatorului //synchronized public synchronized void produce(int vol) { while(marfa!=0) { try { wait(); } catch(InterruptedException e) {} } marfa=vol; notify(); System.out.println("Produs :"+marfa); } } class unFir extends Thread { boolean producator=false; Distribuitor distr; public unFir(Distribuitor d,String t) { distr=d; if(t.equals("Producator")) producator=true; }

public void run() { for(int i=0;i<20;i++) { try { sleep((int)(Math.random()*1000)); } catch(InterruptedException e) {} if(producator) distr.produce((int)(Math.random()*6)+1); else distr.consuma(); } } } public class ProdCons { public static void main(String s[]) { Distribuitor dis=new Distribuitor(); new unFir(dis,"Consumator").start(); new unFir(dis,"Producator").start(); } } Exemplul 11.5 ne arat cum putem combina sincronizarea bazat pe metode synchronized cu posibilitile oferite de sincronizarea bazat pe ateptare. Eseniale, n sincronizarea bazat pe ateptare, sunt metodele wait() i notify().

Capitolul 12
Spiritul orientrii pe obiecte n dou aplicaii C++

12.1 Consideraii introductive


Orict de mare ar fi emoia pe care o provoac o teorie printre curioi, aceasta nu este suficient pentru a o menine n atenia practicienilor, fie i vremelnic. De aceea, n acest capitol voi ncerca s art cum se pun n micare o parte din ideile orientrii pe obiecte, n sperana c, n acest fel, voi oferi motive n plus cititorilor de a ncerca, pe viu, gustul orientrii pe obiecte. Vom redescoperi, la sfritul acestui capitol, ceea ce am semnalat la nceputul crii: programarea orientat pe obiecte, n adevratul sens, este o problem de atitudine fa de ntreg travaliul de realizare a unui sistem soft sau chiar program. Pentru a nu ne abate de la spiritul orientrii pe obiecte, trebuie s utilizm, cu rbdare, conceptele i principiile pe care le-am prezentat deja, precum i experiena acumulat de ali specialiti n realizarea de sisteme orientate pe obiecte. Prima problem pe care o supun ateniei cititorului este relativ simpl, dar foarte potrivit pentru a ilustra spiritul orientrii pe obiecte n aciune.

12.2 Aplicaia 1
Enunul S se scrie codul C++ care simuleaz vizualizarera unui fiier text, n regim de scroll, pe ecranul unui calculator, utilizat n mod text. Efectul de scroll se va urmri numai pe vertical.

12.2.1 Observaii introductive


Enunul pe care ni-l asumm are, n mod intenionat, o serie de elemente care ar putea fi taxate drept scpri de ctre programatorii cu sim critic dezvoltat. Astfel, acetia se pot ntreba: de ce n mod text, ntr-o lume n care modul grafic a inundat fiecare aplicaie i de ce efectul de scroll numai pe vertical? Pur i simplu, din raiuni didactice. Nimeni nu va putea opri elanul creator al cititorului s se manifeste cu puteri nzecite dup ce va fi neles mesajul pe care vreau s-l transmit n aceast carte.

12.2.2 Soluia problemei Cteva consideraii relativ la cerinele fa de cod


Codul va fi orientat pe obiecte i va fi gndit n respect fat de principiul ncapsulrii. Doar fa de principiul ncapsulrii, deoarece nu vom avea o ierarhie de clase i prin urmare nici motenire, nici polimorfism dinamic. Evident, c am fi putut gndi o soluie ceva mai savant, care s ia n considerare derivarea clasei care modeleaz vizualizarea unui fiier text dintr-o clas fereastr, capabil s asigure un context vizual adecvat vizualizrii i o gestiune flexibil a evenimentelor adresate acestei ferestre. Se poate reflecta la aceast posibilitate ca la un exerciiu. Rmnnd la exigenele pe care ni le-am asumat iniial, va trebui s specificm o clas C++ care s funcioneze ca un nveli comportamental pentru un fiier text. Acest nveli va trebui s permit: asignarea la un fiier cu nume extern specificat, i simularea efectului de scroll. Interfa acestei clase, din punct de vedere al utilizatorului, trebuie redus la minimum. Totodat, pentru a da un exemplu de organizare a unui proiect n C++, vom pstra n fiiere diferite: interfaa, implementarea ei i programul care exemplific modul de utilizare. Pentru a asigura condiii optime de efectuare a

scroll-ului (vitez i evitarea uzurii capului de citire al unitii de memorie extern), vom asocia fiierului text o imagine n RAM, ca list dublu nlnuit. Modelul de rezolvare a problemei este, practic, schematizat n Figura 24, unde se poate vedea c, din punct de vedere algoritmic, trebuie doar s gsim o rezolvare elegant la problema maprii coninutului fiierului text pe ecranul monitorului, conform dorinelor utilizatorului. S mai menionm c problema poate fi abordat i altfel: dimensiunea ferestrei n care se face scroll s fie configurabil. n acest caz, datele problemei se modific semnificativ, dar soluia este evident superioar calitativ alegerii noastre. Se poate privi ca un exerciiu posibil i aceast abordare.
Ecranul monitorului aple

curent

aule

aple - adresa primei linii care se vede pe ecran aule - adresa ultimei linii care se vede pe ecran curent - adresa elementului care se afl n rezonan cu cursorul. Imagine memorie fiier (IMF)

Figura 24. Relaia ecran-Imagine Memorie Fiier

Specificarea UML a clasei care modeleaz comportamentul obiectului scroll


Pornind de la contextul problematic schiat n Figura 24 i innd cont de cerinele asumate mai sus, putem propune definiia UML a clasei, s-i spunem, scroll. Aceast definiie va urmri: Furnizarea unei interfee simple i stabile, n acord cu cerinele formulate mai sus. Separarea interfeei de implementare, asigurnd protecia necesar operaiilor interne ale clasei, prin declararea lor ca private. Securizarea accesului la datele membre ale clasei, prin declararea lor ca private. S mai menionm c rmne deschis problema fiabilizrii codului pe care l propunem, ceea ce ar nsemna furnizarea de suoort structurat pentru tratarea excepiilor. nainte de a prezenta specificarea i implementarea C++ a clasei scroll, prezentat n notaie UML n Figura 25, s mai atragem atenia cititorului asupra unui amnunt important: metoda insert(), gndit ca fcnd parte din definiia clasei scroll, putea foarte bine s fie membru ntr-o clas specializat n lucrul cu liste dublu nlnuite.

De exemplu, o clas de genul celor pe care le-am pus n discuie n Capitolele 3, 4 i 5 sau orice instrument orientat pe scroll obiecte echivalent.
-char *numef; -struct Nod *start; -struct Nod *prec; -struct Nod *aple, -struct Nod *aule, -struct Nod *curent; -ifstream fin; -void -void -void -void -void -void insert(char *el); loadfis(); initscreen(); scrollup(); scrolldown(); runscroll();

+scroll(char *nf); +Nod * getstart();

Figura 25. Definiia UML a clasei scroll

Specificarea clasei scroll n C++


Potrivit practicilor legate de gestiunea proiectelor C++ de complexitate mai mare, definiiile claselor se pstreaz n fiiere antet cu extensia .h. Ilustrm aceasta practic prin fiierul scroll.h. de mai jos. //scroll.h //Fisierul antet care contine definitia clasei scroll //si alte definitii ajutatoare //Structura nodului listei dublu inlantuite //in care se pastreaza imaginea memorie a //fisierului(IMF) struct Nod { char *linie; struct Nod* legs,*legd; }; //Clasa care modeleaza un fisier scroll-abil class scroll {

//Sectiunea membrilor privati char *numef; struct Nod *start,*prec; struct Nod *aple,*aule,*curent; ifstream fin; //Inserare element in lista dublu inlantuita void insert(char *el); //Creare imagine memorie fisier void loadfis(); //Initializare ecran pentru scroll void initscreen(); //Derulare scroll void runscroll(); //Efectuare scroll-up void scrollup(); //Efectuare scroll-down void scrolldown(); //Sectiunea resurse publice public: //Constructor scroll(char *nf); //Selector adresa de start IMF Nod * getstart(); };

Implementarea clasei scroll n C++


Din nou, potrivit practicilor legate de gestiunea proiectelor C++ de complexitate mai mare, implementarea claselor se pstreaz n fiiere antet cu extensia .cpp. Ilustrm aceasta practic prin fiierul scroll.cpp. de mai jos. //scroll.cpp //Fisier antet care contine implementarea clasei

//din scroll.h #include <iostream.h> #include <conio.h> #include <fstream.h> #include <string.h> #include <stdlib.h> #include "scroll.h" scroll::scroll(char *nf) { numef=new char[strlen(nf)]; strcpy(numef,nf); fin.open(numef); start=NULL; loadfis(); initscreen(); gotoxy(1,1); curent=start; runscroll(); }; //Selector adresa de start IMF Nod * scroll::getstart() { return start; }; //Implementare scrollup() void scroll::scrollup() { if (wherey()>1) { gotoxy(1,wherey()-1); curent=curent->legs; } else { if (aple->legs!=NULL) { gotoxy(1,1); insline(); gotoxy(1,1);

aple=aple->legs; aule=aule->legs; cout<<aple->linie<<endl; gotoxy(1,1); } }; }; //Implementare scrolldown() void scroll::scrolldown() { if ((wherey()<21)&(curent!=aule)) { gotoxy(1,wherey()+1); curent=curent->legd; } else { if (aule->legd!=NULL) { gotoxy(1,1); delline(); gotoxy(1,21); aule=aule->legd; aple=aple->legd; cout<<aule->linie<<endl; } } }; //Implementare element in lista void scroll::insert(char *el) { Nod *w; if (start==NULL) { start=new Nod; start->linie=new char[strlen(el)+1]; strcpy(start->linie,el); start->legs=NULL; start->legd=NULL; prec=start;

} else { w=new Nod; w->linie=new char[strlen(el)+1]; strcpy(w->linie,el); prec->legd=w; w->legs=prec; w->legd=NULL; prec=w; }; }; //Implementare loadfis() void scroll::loadfis() { char lin[80]; clrscr(); while(!fin.eof()) { fin.getline(lin,80); insert(lin); } }; //Implementare initScreen() void scroll::initscreen() { Nod *aloc; aple=aloc=aule=start; clrscr(); while((aloc!=NULL)&(wherey()<22)) { cout<<aloc->linie<<endl; aule=aloc; aloc=aloc->legd; }; }; //Implementare runscroll() //Se raspunde la: // sageata sus

// sageata jos // ESCAPE void scroll::runscroll() { int tasta,tasta1; do { do { tasta=getch(); } while((tasta!=27)&(tasta!=0)); switch(tasta) { case 0: { tasta1=getch(); switch(tasta1) { case 72: scrollup();break; case 80: scrolldown();break; };break; } case 27: exit(1); }; } while(1); }; n sfrit, prezentm, mai jos, un exemplu de context C++ n care se poate vedea modul de utilizare a unui obiect de tip scroll. Se poate vedea simplitatea modului de utilizare, datorat simplitii interfeei. Se pot, de asemenea, anticipa, n urma anlizei implementrii pe care am furnizat-o mai sus, evenimentele neplcute care pot apare la o utilizare greit a interfeei clasei scroll. De exemplu, intenia de scroll asupra unui fiier inexistent sau inabordabil din variate motive. Rmne ca exerciiu, pentru cititor, fiabilizarea codului sau mbuntirea modului de comunicare cu clienii clasei scroll, n caz de apariie a unor excepii. #include "scroll.cpp" void main() { scroll sco("SCroll.cpp");

};

12.3 Aplicaia 2 Enunul


S se scrie codul C++ care simplific operaiile de intrare/ieire, relativ la fiiere, care au n vedere tipurile de date fundamentale.

12.3.1 Observaii introductive


De data aceasta se dorete rezolvarea unei probleme, de real interes pentru un numr mare de utilizatori: programatori care consider c flexibilitatea pe care le-o ofer C++ n gestiunea coleciilor de date pstrate pe suporturi de memorie extern este o provocare care nu poate fi ocolit. Nu toi programatorii C++ tnjesc dup un instrument care simplific, i prin aceasta ngrdete, operaiile I/O. Exist, ns suficiente argumente pentru a ncerca s furnizm un nveli convenabil soluiei C++ la problema operaiilor cu datele pstrate n fiiere. Acest nveli va permite, n final, un protocol de lucru cu tipurile de date fundamentale, asemntor celui ntlnit n Pascal, Object Pascal, Basic, Visual Basic, etc.

12.3.2 Soluia problemei Cteva consideraii relativ la cerinele fa de cod


Codul va fi orientat pe obiecte i va fi gndit n respect fat de principiul ncapsulrii. Doar fa de principiul ncapsulrii, deoarece nici n acest caz nu vom avea o ierarhie de clase i prin urmare nici motenire, nici polimorfism dinamic. Din experiena lucrului cu fluxuri n C++ se tie c acestea pot fi utilizate n citire sau n scriere. Atenie la interfa i la funcionalitatea expus de aceasta. Utilizatorii interfeei sunt programatori. Soluia pe care o propunem trebuie s simplifice operaiile I/O cu tipuri fundamentale de date i, totodat, trebuie s conserve spiritul liberal n care se programeaz n C++.

Specificarea UML a nveliului pentru operaiile I/O


Decizia pe care am luat-o este, probabil, una din o mie posibile. Serviciile pe care le ateptm de la framework vor fi expuse prin intermediul a dou clase: clasa FileIn, specializat n simularea operaiilor de citire a tipurilor fundamentale de date din fiiere i clasa FileOut, specializat n simularea operaiilor de scriere a tipurilor fundamentale de date n fiiere. Impreun, aceste clase rezolv problema accesului la tipurile fundamentale de date dintr-un fiier, cu meniunea c nu s-a luat n calcul posibilitatea de a avea, simultan, acces i n citire i n scriere. Cititorul poate studia problema regndirii soluiei n ideea c se dorete suport i pentru situaiile n care dorim, simultan, acces I/O. Oricare ar fi situaia, ntre fiierul care conine datele i programator se afl, n imediat apropiere, clasele framework-ului i, disimulate de aceste clase, fluxurile C++. Figura 26 arat, n notaie UML, cele dou clase discutate mai sus.

Specificarea claselor nveli n C++

n cele ce urmeaz, voi prezenta codul C++ care realizeaz definiia claselor FileIn i FileOut, menionate mai sus. Atrag atenia cititorului asupra faptului c extensia dat noiunii de tip fundamental de date este discutabil. Discutabil este i lipsa unei strategii specifice de tratare a excepiilor care pot apare n timpul lucrului cu obiectele, avnd aceste clase drept clase definitoare. Aadar, subiect de reflexie pentru cititor, care ilustreaz o idee deja menionat n aceast carte: n industria de soft, orice problem, nainte de a fi o problem de programare este o problem de modelare n spiritul unei anumite paradigme. Deciziile care se iau n faza de specificare a cerinelor fa de un sistem soft, de exemplu, influeneaz n mod hotrtor, arhitectura soluiei sistemului soft n cauz.

FileIn
-ifstream iflux; -char * numef; +FileIn(); +FileIn(char *numef); +void setNumeF(char *numef); +char *getNumeF(); +void fileOpen(); +void fileClose(); + int readInt(); +float readFloat(); +double readDouble(); +long int readLInt();

FileOut -ofstream oflux; -char * numef; +FileOut(); +FileOut(char *numef); +void setNumeF(char *numef); +char *getNumeF(); +void fileOpen(); +void fileClose(); +void writeInt(int nr); +void writeFloat(float nr); +void writeDouble(double nr); +void writeLInt(long int nr);

Figura 26. Specificarea UML a claselor FileIn i FileOut //Fiierul file.h #include <fstream.h> //Fisierul contine definiia claselor wrapper pentru operatiile I/O //cu tipuri predefinite, relativ la fisiere //****************************************************** //Simulare operatii INPUT class FileIn { //Atribute informationale private //Flux input ifstream iflux; //Nume fisier extern asociat fluxului char * numef; //Interfata clasei public: //Constructori FileIn(); FileIn(char *numef); //Setare nume fisier extern void setNumeF(char *numef); //Consultare nume fisier extern char *getNumeF(); //Deschidere flux in cazul utilizarii constructorului File() void fileOpen(); //Inchidere flux void fileClose(); //Citire intreg din fisier int readInt();

//Citire real simpla precizie din fisier float readFloat(); //Citire real dubla precizie din fisier double readDouble(); //Citire long int din fisier long int readLInt(); }; //Simulare operatii OUTPUT class FileOut { //Atribute informationale private //Flux output ofstream oflux; //Nume fisier extern asociat fluxului char * numef; //Interfata clasei public: //Constructori FileOut(); FileOut(char *numef); //Setare nume fisier extern void setNumeF(char *numef); //Consultare nume fisier extern char *getNumeF(); //Deschidere flux in cazul utilizarii constructorului File() void fileOpen(); //Inchidere flux void fileClose(); //Scriere intreg in fisier

void writeInt(int nr); //Scriere real simpla precizie in fisier void writeFloat(float nr); //Scriere real dubla precizie in fisier void writeDouble(double nr); //Scriere long int in fisier void writeLInt(long int nr); };

Implementarea claselor nveli n C++


//Fiierul iofile.cpp //Fisierul contine implementarea metodelor claselor FileIn si FileOut //****************************************************** #include "file.h" #include <string.h> #include <alloc.h> //Implementarea metodelor clasei FileIn FileIn::FileIn(char *nf) { numef=(char*)malloc(strlen(nf)+1); strcpy(numef,nf); iflux.open(numef,ios::in|ios::binary); }; FileIn::FileIn() { }; int FileIn::readInt() { int n; iflux.read((signed char*)&n,sizeof(int));

return n; }; float FileIn::readFloat() { float n; iflux.read((signed char*)&n,sizeof(float)); return n; }; double FileIn::readDouble() { double n; iflux.read((signed char*)&n,sizeof(double)); return n; }; long int FileIn::readLInt() { long int n; iflux.read((signed char*)&n,sizeof(long int)); return n; }; void FileIn::setNumeF(char *nf) { numef=(char*)malloc(strlen(nf)+1); strcpy(numef,nf); }; char *FileIn::getNumeF() { return numef; }; void FileIn::fileOpen() { iflux.open(numef,ios::in|ios::binary); }; void FileIn::fileClose() {

iflux.close(); }; //Implementarea metodelor clasei FileOut FileOut::FileOut(char *nf) { numef=(char*)malloc(strlen(nf)+1); strcpy(numef,nf); oflux.open(numef,ios::out|ios::binary); }; FileOut::FileOut() { }; void FileOut::writeInt(int nr) { oflux.write((signed char*)&nr,sizeof(int)); }; void FileOut::writeFloat(float nr) { oflux.write((signed char*)&nr,sizeof(float)); }; void FileOut::writeDouble(double nr) { oflux.write((signed char*)&nr,sizeof(double)); }; void FileOut::writeLInt(long int nr) { oflux.write((signed char*)&nr,sizeof(long int)); }; void FileOut::setNumeF(char *nf) { numef=(char*)malloc(strlen(nf)+1); strcpy(numef,nf); }; char *FileOut::getNumeF() {

return numef; }; void FileOut::fileOpen() { oflux.open(numef,ios::out|ios::binary); }; void FileOut::fileClose() { oflux.close(); };

Un exemplu de utilizarea a claselor FileIn i File Out


//Fisierul contine un program de test pentru clasele FileIn si FileOut //****************************************************** #include <fstream.h> #include <conio.h> #include "filein.cpp" #include <iostream.h> void main() { int nri=1024; double nrrd=12.75; long int nrli=10000000; clrscr(); //Creare fisier de test FileOut of("fis.dat"); cout<<"Obiect FileOut creat in urma apelului vers. 1 a constructorului"<<endl; of.writeInt(nri); of.writeDouble(nrrd); of.writeLInt(nrli); of.fileClose(); //Prima utilizare statica a clasei FileIn FileIn fis("fis.dat"); cout<<"Obiect FileIn creat in urma apelului vers. 1 a constructorului"<<endl; nri=fis.readInt(); nrrd=fis.readDouble();

nrli=fis.readLInt(); cout<<nri<<" "<<nrrd<<" "<<nrli<<endl; fis.fileClose(); //A doua utilizare statica a clasei FileIn //Deschiderea fluxului este realizata in mai multi pasi FileIn fis1; fis1.setNumeF("fis.dat"); fis1.fileOpen(); cout<<endl; cout<<"Obiect FileIn creat in urma apelului vers. 2 a constructorului"<<endl; nri=fis1.readInt(); nrrd=fis1.readDouble(); nrli=fis1.readLInt(); cout<<nri<<" "<<nrrd<<" "<<nrli<<endl; getch(); }; Expediia OO se oprete aici, n aceast carte. Ea trebuie s continue, pentru fiecare cititor n parte, n toate direciile n care aceasta se manifest din ce n ce mai insistent: mediile vizuale de programare, SGBD-urile orientate pe obiecte, dezvoltarea orientat pe componente, etc. n orice direcie ar porni cititorul, n esen se va ntlni cu aceleai probleme dificile: Modelarea respectnd spiritul orientrii pe obiecte, care are numeroase avantaje dar se nva mai greu dect orice manier de programare artizanal. Rezolvarea problemelor de supravieuire a obiectelor, ntre dou execuii ale aplicaiilor orientate pe obiecte. Rezolvarea problemelor de transmitere i partajare a obiectelor ntre aplicaii aflate pe acelai calculator sau pe calculatoare diferite, ntr-o reea. Soluionarea elegant a problemei tratrii excepiilor care pot apare ntr-o aplicaie orientat pe obiecte. Armonizarea cerinelor orientrii pe obiecte cu exigenele realizrii aplicaiilor multifir, etc.

Fr s fie un secret prea mare, deprinderea de a programa orientat pe obiecte vine dup mult exerciiu i efort constant de informare cu privire la abloanele aplicate de comunitatea programatorilor OO pentru rezolvarea unor probleme tip.

Bibliografie esenial
[1] Eckel, B., Thinking in Java, 2nd edition, Revision 12, format electronic. [2] Jamsa & Klander, C i C++ (Manualul fundamental de programare n C

i C++), Editura Teora. [3] Joshua Bloch, Java. Ghid practic pentru programatori avansai, Editura Teora, 2002. [4] Lemay, L., Cadenhead, R., Java 2 fr profesor n 21 de zile, Editura Teora, 2000. [5] Mark C. Chan, .a., Java. 1001 secrete pentru programatori, Editura Teora [6] Negrescu,L., Limbajele C i C++ pentru nceptori, Limbajul C++ (volumul II), Editura Albastr, Cluj-Napoca [7] Teixeira, S., Pacheco, X., Delphi 5 Developers Guide, SAMS Publishing, 2000

S-ar putea să vă placă și