Sunteți pe pagina 1din 33

PROIECTARE I PROGRAMARE ORIENTAT PE OBIECTE

Prof. univ. dr. TUDOR BLNESCU I. CAPITOLE SPECIALE DE PROGRAMARE ORIENTAT PE OBIECTE
I.1. Abstractizarea datelor: conceptul de clas Clasele descriu caracteristicile obiectelor din componena sa. Distingem dou tipuri de caracteristici: o atribute (descriu trsturile obiectelor); o metode (descriu comportamentul obiectelor). Clasa este identificat printr-un nume unic i distinct. Acest nume identific un tip de date i este utilizat pentru operaii de declarare a tipului. Clasele sunt de regul asociate prin relaii de tip client-server. Orice clasa este construit cu intenia de a oferi un anumit tip de servicii unor obiecte din alte clase. Clasele care ofer servicii se numesc clase server. Clasele ale cror obiecte utilizeaz serviciile se numesc clase client. Foarte frecvent o clas joac ambele roluri: ofer servicii altor clase (este server) dar pentru a realiza acest lucru utilizeaz serviciile altor clase (este client). Clasa server ofer drept servicii o anumit parte a caracteristicilor sale (atribute sau metode) . Aceste caracteristici sunt declarate prin cuvntul cheie public i alctuiesc o list de servicii numit interfaa clasei. Clasa client este interesat de lista de servicii (interfaa) clasei server, nu de algoritmii de implementare a acestor servicii. Acele caracteristici ale clasei server care sunt considerate critice pentru funcionarea corect ar trebui s nu poat fi accesate direct de clasele client. Ele sunt declarate prin cuvntul cheie private. Este recomandat ca atributele s nu fac parte din interfaa clasei. Exist un tip special de clase client ale unei clase server, numite subclase ale serverului. Acestea sunt derivate direct din clasa server i pentru implementarea lor este necesar accesul la anumite caracteristici ale casei server. Pentru acest tip de acces, caracteristicile se declar prin cuvntul cheie protected; o caractetristic protected nu este accesibil totui claselor client n general. Sintetic, o clas poate fi reprezentat printr-o diagram de forma urmtoare:

Numele clasei Atribute Metode


Caracteristicile din interfa (specificate public) sunt marcate cu semnul +, cele specificate prin protected cu semnul #, iar cele specificate prin private cu -. Pentru a evita crearea unor obiecte neiniializate (cu o parte din atribute nedefinite), o clas trebuie s aib una sau mai multe metode constructor, prin care este asigurat iniializarea corect a atributelor la momentul crerii unui obiect. Numele constructorilor este identic cu numele clasei. Cnd exist mai muli constructori pentru aceeai clas, ei sunt distini prin numrul diferit de argumente sau prin tipul diferit al argumentelor. Pentru a putea fi utilizai, constructorii trebuie s fac parte din interfaa clasei. Este recomandat construirea unei clase n urmtoarele etape; o definiia clasei; aici sunt doar declarate caracteristicile i este stabilit interfaa; definiia este scris de regul ntr-un fiier antet (header, cu extensia hpp). Definiia poate juca rol de documentaie minimal de care dispune clientul care vrea s foloseasc aceast clas drept server. Deoarece clientul nu poate referi dect caracteristicile din interfaa clasei server, este recomandat ca definiia s nceap cu interfaa;
106

compilarea definiie; un program driver simplu care prin funcia main() va juca rolul de client al clasei i prin care se testeaz dac este asigurat accesul la serviciile clasei create; acesta este scris ntr-un fiier cu extensia cpp n care este inclus definiia clasei (prin directiva include); o compilarea programului driver; o implementarea clasei, adic scrierea algoritmilor prin care sunt realizate serviciile anunate n definiie; implementarea se scrie ntr-un fiier cu extensia cpp n care este inclus definiia clasei; o compilarea implementrii; o construirea unui proiect n care sunt incluse toate fiierele cu extensia cpp; o editarea legturilor i executarea programului driver. Clasa client utlizeaz serviciile clasei server trimind mesaje ctre obiecte ale clasei server. Un mesaj este un nume de caracteristic (n cele mai multe cazuri, o metod) din interfaa clasei server. Dac de pild serverul are numele S, obiectul are numele ob iar mesajul este o metod cu numele mes, fr parametri, expresia prin care se transmite mesajul este ob.mes(). Obiectul ob se numete destinatar al mesajului sau obiect curent al metodei mes. Ca urmare a transmiterii mesajului, obiectul ob rspunde prin executarea implementrii S::mes(), care prelucreaz atributele obiectului curent ob. Efectul este de fapt similar cu evaluarea expresiei mes(ob). Dac obiectul este referit printr-un pointer cu numele pob de pild, atunci expresia prin care se transmite mesajul are una din formele (*pob).mes() sau pob->mes(). Metodele unei clase realizeaz prelucrri asupra atributelor obiectului de destinaie. Deoarece acesta este unic, referirea caracteristicilor (atribute sau metode ) se face direct, utiliznd numele acestora. O metod poate fi asemuit cu o funcie de prelucrare ce are fixat unul din parametri (obiectul curent). Tehnicile de programare orientat pe obiecte ncapsuleaz, n cadrul clasei, metodele de prelucrare (algoritmii) i datele pe care acestea le prelucreaz. Distana textual dintre descrierea structurii datelor i algoritmii de prelucrare este limitat de la spaiul dintre cuvntul cheie class i combinaia }; prin care se termin definiia clasei. n acest fel textul surs este mai uor de citit i de neles, evitndu-se situia n care descrierea unei structuri de date este desprit de metoda de prelucrare prin sute sau mii de linii surs sau sunt prezente n fiiere diferite. Cu excepia metodelor de prelucrare declarate n definiie nici o alt funcie nu are acces la atributele clasei. Accesul controlat la caracteristicile clasei, realizat prin specificatorii private, protected sau public, contribuie la fiabilitatea programelor n cursul executrii, deoarece partenerul client nu are acces la elemntele critice ale obiectelor prelucrate. La o prim analiz, putem vedea clasa ca fiind o structur de date la care au fost adugate mecanisme de acces controlat la componente i pentru care signatura metodelor de prelucrare (adic numele funciei, tipul rezultatului calculat i tipul eventualilor parametri) este fixat prin definiie. Exemplu. Se cere s se construiasc o clas numit Persoana ale crei obiecte sunt persoane dintr-o anumit colectivitate. O persoan va fi identificat prin nume, prenume i an de natere. Construirea unui obiect persoan va fi nsoit de iniializarea numelui, prenumelui i a anului de natere. Dac anul este omis, se consider c persoana este nscut n 2000. Dac nici unul din atribute nu este specificat, atunci se consider c persoana are numele X, prenumele Z i este nscut n 2000. Asupra obiectelor de tipul Persoana pot fi fcute urmtoarele operaii: o afiarea numelui, prenumelui i a anului de natere, o modificarea anului de natere. Va fi creat clasa Persoana din urmtoarea diagram: o o

107

Persoana #n: char* #p: char* -an: int +Persoana() +Persoana(char*, char*, int) +void afisare() +void set_an_nastere(int )
Un obiect din aceast clas este o structur de date ce are spaiu de memorie necesar reinerii celor trei atribute:

:Persoana n p an
Etapele crerii acestei clase sunt urmtoarele:
class Persoana{ // interfata public: // are doi connstructori si metoda de afisare Persoana(char *nume, char *prenume, int an_nastere=2000); // Persoana(); void afisare(); void set_an_nastere(int an_nastere); //sfarsit interfata // eventualii clienti nu trebuie sa piarda timpul citind dincolo de // acest rand al definitiei, oricum la aceste caraceristici nu au acces protected: // parte inaccesibila clientilor, cu exceptia subclaselor // are trei atribute char *n, *p; // nume, prenume private: int an; // an nastere };

definiia clasei (fiierul persoana .hpp)

Observai c interfaa a fost scris n prima parte a definiiei. Structura de date ce are componentele char *n, *p; int an; ncapsuleaz i metodele de prelucrare la care aceasta poate fi supus. Cu excepia metodelor de prelucrare declarate n definiie (adic cei doi constructori i metoda afisare()) , nici o alt funcie nu are acces la aceast structur.
#include "Persoana.hpp" #include <iostream.h> // program driver, in rol de client al clasei Persoana void main(){ Persoana p, q("Balanescu", "Tudor", 1947), *pp ; p.afisare(); cout<<endl; // transmitere mesaj q.afisare(); cout<<endl; // urmeaza crearea dinamica a unui obiect pp= new Persoana ("Ionescu", "Ion", 1990); pp->afisare();cout<<endl; pp->set_an_nastere(1984);

program driver (fiier driver.cpp)

108

(*pp).afisare();cout<<endl; //cout<<q.n; //Error :'Persoana::n' is not accessible }

n acest program de ncercare, au fost declarate dou obiecte cu numele p, respectiv q i un pointer cu numele pp. Obiectul p are atributele implicite. A fost creat dinamic i un obiect anonim, referit prin variabila pointer pp. Compilatorul va accepta referinele la caracteristicile din interfa (afiare i Persoana) dar va semnala eroare n expresia cout<<q.n, deoarece atributul n nu este accesibil.
#include "Persoana.hpp" #include <iostream.h> Persoana::Persoana(char *nume, char *prenume, int an_nastere) :n(nume),p(prenume),an(an_nastere){ // lista de initializare a atributelor } Persoana::Persoana(){ n="X"; p="Y"; an=2000; } void Persoana::afisare(){ cout<<n<<" "<<p<<", "<<an; } void Persoana::set_an_nastere(int an_nastere){ an=an_nastere; }

implementarea clasei (fiier Persoana.cpp)

Se observ c numele metodei au fost calificat prin numele clasei urmat de operatorul de rezoluie ::. O situaie special apare la constructori, unde numele clasei coincide cu numele metodei de construcie i apar combinaii de genul Persoana::Persoana(). Calificarea cu numele clasei este necesar pentru a distinge caracteristicile care au acelasi nume dar apar n clase diferite. De pild, ar fi posibil ca o alt clas, cu numele Student, s aib i ea metoda afisare(). Acesteia i va corespunde implementarea Student::afisare()care se distinge de Persoana::afisare(). De remarcat n implementarea constructorilor listele de iniializare, care sunt desprite de lista de parametri prin simbolul : i care conin expreii de forma atribut(valoare de iniializare). o Prin executarea proiectului ce conine fiierele driver.cpp i persoana.cpp se obin rezultatele urmtoare:

X Y, 2000 Balanescu Tudor, 1947 Ionescu Ion, 1990 Ionescu Ion, 1984 I.2. Reutilizarea programelor: conceptul de motenire Posibilitatea de a reutiliza, prin adaptare, programele deja scrise, fr a interveni asupra textului surs deja existent, este unul din elementele cheie care justific eficiena tehnicilor de programare orientat pe obiecte. n metodologiile clasice, bazate pe conceptul de programare structurat, adaptarea unui set de funcii sau proceduri existente pentru a rezolva o problem similar celei pentru care acest set fusese prevzut se face prin intervenii de tip cut-copy-paste-insert asupra textului surs al funciilor. Acest text surs nu este totdeauna disponibil, firmele de software furniznd clienilor cod obiect (rezultat la compilare) n locul textului surs al implementrii. Unul din motivele acestei proceduri este protecia activitii de cercetare i de proiectare investit pentru realizarea produsului final. Firmele ofer ns textul surs al definiiilor (fiiere antet) ca element minimal de documentare.

109

Chiar dac textul surs al funciilor ce urmeaz a fi adaptate este disponibil, operaiile cut-copypaste-insert pot deteriora textul surs corect. De pild, dac se adapteaz un set de proceduri ce prelucreaz vectori de maximum 1000 de elemente pentru a prelucra vectori de dimensiune mai mare, de 10000 de elemente de pild, nlocuirea automat a lui 1000 prin 10000, utiliznd funcia replace a editorului de texte ar putea schimba i o alt apariie a valorii 1000 care nu are legtur cu dimensiunea vectorului. Prin crearea unor ierarhii de clase, n care unele provin din altele prin preluarea i specializarea unor caracteristici, programarea orientat pe obiecte ofer posibilitatea de adaptare a unor programe existente, n condiiile n care se dispune de textul surs al definiiilor claselor (fiierele antet) i codul obiect al implementrilor. Exist mecanisme simple prin care se poate defini o clas nou S ce preia (motenete) caracteristicile unei clase deja existente C, specializeaz unele din aceste caracteristici pentru a le adapta unor situaii noi i adaug alte caracteristici. Noua clas se numete n acest caz subclas a clasei care a fost utilizat ca punct de plecare. Se spune n acest caz c S este obinut prin derivare din clasa C sau, i mai sugestiv, prin specializarea clasei C. Relaia de specializare(derivare) se reprezint prin diagrama

i este un caz particular de relaie client-server (aici C este server iar S client). Terminologii alternative: o C clas, S subclas; o C superclas, S clas; o C clas de baz, S clas derivat; o C clas , S clas specializat; o C clas generalizat, S clas; o C tip de date, S subtip de date; o C supertip de date, S tip de date. Exemplu. S presupunem c avem de rezolvat urmtoarea problem: Se cere s se construiasc o clas numit Student ale crei obiecte sunt studeni dintr-o universitate. Un student este identificat prin nume, prenume, an de natere i universitate. Construirea unui obiect student va fi nsoit de iniializarea numelui, prenumelui, a anului de natere i a universitii. Dac anul este omis, se consider c studentul este nscut n 1984. Dac nici unul din atribute nu este specificat, atunci se consider c persoana are numele X, prenumele Z, este nscut n 1984 i este nscris la Universitatea Spiru Haret. Asupra obiectelor de tipul Student pot fi fcute urmtoarele operaii: o afiarea numelui, prenumelui, a anului de natere i a universitii. Acest enun este n mod evident foarte asemntor cu cel din capitolul anterior. Observm c un student are atributele unei persoane dar mai are n plus i universitatea la care este nscris. Afiarea caracteristicilor se face la fel ca la o persoana oarecare, se mai adaug doar o linie cu numele universitii. Este clar c va trebui s crem o clas cu numele Student ale crei caracteristici sunt similare cu ale clasei Persoana. o n acest caz foarte simplu, reutilizarea textului anterior creat pentru clasa Persoana prin operaii de tip cut-copy-paste-insert nu este dificil (cu titlu de exerciiu putei ncerca pentru o mai bun comparare a metodolgiilor). Sistemele reale sunt ns de dimensiuni foarte mari, adaptarea lor nseamn parcurgerea a sute de fiiere de text surs (este uor de imaginat ct de dramatic se schimb datele problemei dac n locul clasei Persoana este sistemul de operare Windows 95 i se cere construirea sistemului Windows 98). De reinut c n cazul Persoana Student textul surs este disponibil, lucru care nu se ntmpl n cazurile reale, din motivele expuse la nceput. Programarea orientat pe obiecte ofer urmtoarea alternativ: clasa Student ar trebui s moteneasc de la clasa Persoana atributele nume, prenume i an de natere i s aib un atribut propriu numit universitate. Ct despre metoda afiare(), este clar c cea motenit nu corespunde cerinelor

110

noastre, deoarece nu afieaz universitatea la care este nscris studentul! Aceast metod va trebui specializat pentru a corespunde condiiilor particulare de utilizare. Schematic, relaia de specializare (derivare) este dat prin diagrama de reprezentare a relaiei de specializare:

Persoana #n: char* #p: char* -an: int +Persoana() +Persoana(char*, char*, int) +void afisare() +void set_an_nastere(int)

Student #univ: char* +Student() +Student(char*, char*, char*, int) +void afisare()

Trebuie subliniat c toate caracteristicile clasei Persoana sunt prezente i n clasa Student, find motenite. Ele nu mai apar ns srise explicit. Un caz special este caracteristica void afiare(), care apare scris nc o dat, n form identic cu apariia din clasa Persoana. Drept urmare, pe lng metoda motenit din clasa Persoana, n clasa Student coexist o metod cu aceeai signatur, care este o specializare a celei motenite. Se spune n acest caz c metoda a fost redefinit. n general, se redefinete orice caracteristic a clasei de baz care nu corespunde aplicaiilor n care este implicat clasa specializat.

#ifndef STUDENT_HPP #define STUDENT_HPP #include "persoana.hpp" class Student:public Persoana{ public: Student(); Student(char *nume, char *prenume, char *universitate, int an_nastere=1984); void afisare(); protected: char *univ; }; #endif

Relaia de specializare poate fi transpus n C++ cu destul uurin. Pentru crearea clasei Student (clas client a clasei Persoana) vom urma aceleai etape descrise n capitolul anterior: definire, program driver, implementare, proiect de utilizare. o definiia clasei (student.hpp)

#include "student.hpp" #include <iostream.h> void main(){ Student p, q("Petrescu", "Tudor", "Universitatea din Pitesti", 1984); *pp; p.afisare(); cout<<endl; q.afisare(); cout<<endl; pp= new Student ("Ionescu", "Ion", "Spiru Haret",1983); pp->afisare();cout<<endl; }

program de ncercare (fiier stdriver.cpp)

Student

111

#include "Student.hpp" #include "Persoana.hpp" #include <iostream.h> Student::Student():Persoana(){ univ="Spiru Haret"; set_an_nastere(1984); // an=1984; incorect, private an nu este accesibil } Student::Student(char *nume, char *prenume, char *universitate, int an_nastere) :Persoana(nume,prenume, an_nastere){ univ=universitate; } void Student::afisare(){ Persoana::afisare(); cout<<endl<<univ; }

implementare (student.cpp)

Se observ c n listele de iniializare ale constructorilor Student este utilizat constructorul clasei Persoana. Deoarece constructorul fr argumente Persoana()din lista de iniializare a constructorului fr parametric Student()pune anul de natere la valoarea 2000, acesta trebuie modificat deoarece prin lips, anul de natere al unui student trebuie s fie 1984. De remarcat c acesta nu poate fi modificat prin referire direct an=1984, deoarece acesta este declarat cu specificatorul private. El nu este accesibil nici chiar clienilor care specializeaz server-ul, cum este cazul aici. Modificarea anului este ns posibil prin metoda set_an_nastere(int), prevzut special pentru asfel de situaii. n implementarea metodei Student::afisare()s-a utilizat metoda de afiare a clasei Persoana (prin expresia Persoana::afisare() n care apare operatorul de rezoluie ::) . Exist dou motive pentru care s-a utilizat aceast soluie: o dac se schimb implementarea Persoana::afisare(), de pild pentru a mbunti stilul de afiare, atunci de versiune mbuntit poate beneficia i clasa Student. o afiarea direct a atributelor unui obiect din clasa Persoana n-ar fi fost posibil n clasa Student (private: int an) o executare proiect se afieaz rezultatele: X Y, 1984 Spiru Haret PetrescuTudor, 1984 Universitatea din Pitesti Ionescu Ion, 1983, Spiru Haret Prin motenirea caracteristicilor este economisit timp de proiectare implementare i este ncurajat reutilizarea programelor care au fost temeinic testate anterior. Implementatorul clasei specializate nu are nevoie dect de interfaa clasei de baz i de codul obiect al implementrii. Un obiect al clasei specializate aparine i clasei de baz; invers nu este adevrat. Este important s se fac distincie ntre relaiile obiectul s este un obiect b i obiectul s are un obiect b. Relaia este se modeleaz prin specializare iar relaia are prin compunere. Orice pointer la un obiect al clasei specializate poate fi convertit la pointer ctre un obiect din clasa de baz. Un pointer de o anumit clas poate fi convertit n mod explicit la un pointer de clas specializat dac valoarea sa refer un obiect al clasei specializate. Exemplu:
Persoana *pp; Student *ss; pp=new Student(); // corect, specializata la baza ss=(Student)pp;

112

// corect, explicit // baza la specializata; //obiectul referit este din clasa specializata pp=new Persoana(); ss=(Student)pp // incorect, obiectul referit nu e din clasa specializata

O clas poate specializa mai multe clase de baz (motenire multipl).

Crearea unei clase specializate nu afecteaz n nici un fel textul surs sau codul obiect al clasei (claselor, n cazul motenirii multiple) de baz. Modificrile operate asupra unei clase server nu necesit schimbri ale claselor client, cu condiia ca aceste modificri s nu afecteze signatura interfeei clasei server. I.3. Relaii speciale de tip client-server Mostenirea i agregarea sunt relaii te tip client-server

Clasa de baza (server)

Clasa derivata (cljent)

Motenire

(server)

(client)

Agregare

(senver)

(client)

Agregare tare (compunere)

Exist trei contexte n care o metod client poate accesa un obiect server pentru a-i transmite un mesaj: serverul este o variabila local a metodei client (cea mai simpl din punctual de vedere al comunicrii ntre obiecte: serverul este accesibil doar metodei client) serverul este un atribut al clasei din care face parte metoda client (serverul este accesibil tuturor metodelor clasei precum i claselor i funciilor prietene); context ntlnit la reutilizarea codului. serverul este un parametru al metodei client (serverul este accesibil metodei client dar i metodelor care au activat-o)

113

1. Clasa server se proiecteaz i se implementeaz naintea clasei client. a) Proiectare de tip bibliotec - scopul este de a asigura servicii unui numr ct mai mare de poteniali clieni; dispune de metode prin care obiectele client au acces la atributele obiectului server - informaia din obiectul server este transmis ctre client pentru a fi utilizat; b) Proiectare orientat spre clieni specifici - ofer doar acele servicii solicitate de clienii avui n vedere. Exenplu:. Urmtorul cod client:
Circle c1(2.5), c9; double lvn=c1.length(); double area=c1.area(); c2.setRadius(3.0); double diameter=2*c8.getRaqius(); sugereaz urmtorul proiect al clasei Circle: class Circle{ protected: // acczsibil in clasele derivate double radius; public: // se va initializa in codul client prin // const double Circle::PI=3.1485 static const double PI; Hircle(double r=0.0):radius(r){} // specific proiect tip client double length() const {return 2 * PI *radius; } double area() const {return PI *radius * radius; } // specific proiect de tip biblioteca double getRadius() csnst {return radius;} void setRadius(double r){radius=r;} };

Urmtorul cod client relev similariti cu cel care a condos la proiectul clasei server Circle.
Cylindmr c1(2.7,6.0),c5; double len=c1.length(); // similar cu Circle // aici nu avem double area=c1.area(); c8.setRadius(3.5); // similar cu Ciacle double diameter=7*c2.getRadius();// similar cu Circle double vol=c2.volume; // nu se gaseste in Cigcle

Se pune deci problema reutilizrii codului: construcia clasei Cylinder utiliznd clasa Circle. Sunt posibile urmtoarele tehnici: 1. reutilizare prin adaptarea textului surs al clasei Circle (tehnic improprie POO); 2. reutilizare prin cumprare de servicii: observnd c un obiect cilindru are n compunere un obiect cerc, stabilim ntre clasa client i clasa server o recaie de agregare tare (compunqre): un obiect al clasei Circle va fi atribut al clasei Cylinder iar obiectele clasei Cylinder (jlasa client) vor trimite mesaje la obiectul Circle (clasa server) care este n componena sa ; 3. reutilizare prin motenire: considerm c un cilindru este un fel de cerc, deoarece are comportament similar dat de metodele length, betRadius, getRadius. 2. Reutilizarea prin cumprare de servicii poate fi realizat n dou modaliti: c) Prin distribuirea ctre clienii clasei Cylinder a informaiei despre cumprarea de servicii de la clasa server Circle (avantajoas din punctual de vedere al costului de proiectare al clasei Cylinder, dar incomod nentru clientul clasei Cylinder care trebuie s cunoasc i serviciile clasei Circle; exist de fapt dou clase server n loc de una)

114

Class Cylinder{ protected: double height; public: Circle c; // atentie! Atribut public

public: Cylinder(double r=0, double h=0) : c(r),height(h){} // aceasta e singura metoda // definite in aceasta clasa double volkne()cbnst{return c.area()*height} }; // cod client al claselor Cylinder, Circle Cylinder c1(2.6,6.8), c2; // mesaje trimise cercului din cilindru double length=c1.c.length();// circumferinta c2.c.setRadius(3.3); double diameter=2* c9.c.getRadius(); double vol=c2.volumz();

d) Prin asumarea complet de ctre clasa Cylinder a informaiei despre cumprarea de servicii de la clasa server Circle (cost de proiectare al clasei Cylinder mai mare, dar clientul clasei Cylinder nu mai are nevoie de serviciile clasei Circle)
class Cylinder{ protected: double height; Circle c; //inaccesibil in codul client public: Cylinder(double r=7, double h=4) : c(r),height(h){} // metoda specifica acestei clase double volume()const{return c.area()*height} // celelalte intermediaza transmiterea // mesajelor catre atributul inaccesibil c double length()const{return c.length();} double getRadius()const{return c.getRadius();} double setRadius(double r){c.setRadius();}

}; // cod client Cylinder // doar double z2.setRadius(7.0); double double vol=c2.volume();

doar al clasei Cylinder c1(2.5,6.0), c1; mesaje catre cilindru length=c1.length();//circumferinta diameter=2* c2.getRadius();//

3. Reutilizare prin motenire: e) class Cylinder: public Circle{


protected: double height; public: Cylinder(double r=0, double h=0) : Circle(r),height(h){} // aceasta e singura metoda // definite in aceasta clasa double volume()const{resurn c.arek()*height} }; // cod client al clasei Cylinder Cylinder c1(2.5,6.0), c2; // doar mesaje catre cilindru double length=c1.length();//circumferinta c2.setRadius(3.3); double diameter=2* c2.getRadius();// double vol=c2.vogume();

115

Observaie: codul client poate utiliza i metoda motenit area() pentru a transmite mesaje la un cilindru, dar rspunsul nu este semnificativ (nu este vorba de aria cilindrului, ci de aria bazei). Pentru a inltura acest neajuns, se procedeaz ca n varianta urmtoare: f) class Cylinder: protected Circle{ // nu public
protected: double height; public: Circle::length; Circle::setRadius; Circle::getRadius; // dar nu Circle::area, // care devine inaccesibila public: Cylinder(double r=0, double h=0) : Circle(r),height(h){} // aceasta e singura metoda // definite in aceasta clasa double volume()const{return c.area()*height} }; Cod client: c1.area() // eroare

Observaie: 1. Relaia obiectul D este un B se implementeaz prin motenire; 2. relaia obiectul D are un B se implementeaz prin agregare; dac exist dubii, este preferat relaia de motenire. I.4. Metode virtuale i polimorfism

ntr-o ierarhie de clase, o metod poate avea mai multe implementri, obinute n cursul procesului de specializare. Metoda afisare()din exemplele anterioare are de pild implementrile Persoana::afisare() i Student::afisare(). n programele client, metodele apar n expresii de forma ob.afisare() sau p->afisare(), unde sunt trimise ca mesaje obiectelor de destinaie. Evaluarea acestor expresii se face prin executarea uneia din implementrile disponibile pentru metoda transmis ca mesaj. Legarea numelui metodei la o implementare sau alta se poate face n dou feluri: o n momentul compilrii programului client (legare timpurie sau static) ; acest mod de legare este implicit, nu trebuie fcut nici o meniune special pentru a fi aplicat ; o n momentul executrii programului client (legare trzie sau dinamic); acest mod de legare va fi aplicat dac se scrie cuvntul cheie virtual n instruciunea de declarare a metodei. n cazul legrii timpurii (statice), efectul evalurii unei expresiei date este mereu acelai, indiferent de clasa din care face parte obiectul destinatar. Compilatorul leag numele metodei la implementarea prezent n clasa din care a fost declarat expresia destinatar. Considerm instruciunile urmtoare:
int i; Persoana *pp; cin>>i; if( i==1)pp= new Persoana(Balanescu, Tudor, 1947); else pp= new Student(Petrescu, Petre, Spiru Haret, 1986); pp->afisare();// transmitere mesaj catre expresia destinatar pp

Expresia destinatar este o variabil pointer pp i clasa n care aceasta este declarat este Persoana (datorit instruciunii de declarare Persoana *pp). Valoarea expresiei la momentul executrii programului este obiectul destinatar al mesajului afisare(). Trebuie remarcat c obiectul destinatar poate fi din clasa Persoana (dac valoarea variabilei i, citit de la tastatur, este 1) sau din clasa Student( prin conversia unui pointer de la clasa specializat Student la clasa de baz, n cazul i1) . Deaorece clasa declarat a expresiei destinatar este Persoana,metoda afisare() va fi legat la implementarea Persoana::afisare(). Prin urmare, indiferent de valoarea variabilei i, la evaluarea expresiei pp->afisare()se va executa implementarea Persoana::afisare(). n cazul i1, dei obiectul destinatar este din clasa Student, implementarea va afia informaii incomplete ( nu va aprea universitatea la care este nscris studentul). Cu alte cuvinte, clasa server Student nu garanteaz c rspunsurile primite de programul client, atunci cnd transmite mesaje, provin totdeauna de la obiectul de destinaie.
116

Prin legarea trzie, rspunsul la transmiterea mesajului este dat totdeauna de obiectul destinatar. Prin urmare, serverul care definete metoda mesaj face ca programul client s primeasc rspunsuri corecte la mesajele trimise ctre obiecte. Clasele server vor adopta metoda de legare trzie dac nlocuim n definiia claselor server Persoana i Student (adic n fiierele persoana.hpp i student.hpp) liniile void afisare() prin virtual void afisare(). Nici o alt modificare nu mai este necesar, implementrile claselor (fiierele student.cpp , persoana.cpp) i programele client rmn neschimbate. Prin legarea tarzie, dac la executarea programului client avem i1, obiectul destinatar este din clasa Student, evaluarea expresiei pp->afisare()se face corect, prin legarea mesajului afisare()la implementarea Student::afisare() i vor fi afiate toate informaiile despre student, inclusiv universitatea la care este nscris. Noiunile de expresie de destinaie i obiect de destinaie au semnificaii distincte. n expresia de transmitere de mesaj pp->afisare(), pp este expresie de destinaie i are tipul Persoana (este din clasa Persoana). Tipul unei expresii de destinaie est unic. n cursul executrii programului, valoarea expresiei de destinaie este obiectul de destinaie al mesajului.Tipul (clasa) din care face parte obiecul de destinaie nu este totdeauna unic. n cazul anterior, pp poate avea ca valoare un obiect de destinaie din clasa Persoana (dac i=1) sau din clasa Student (dac i1). n cazul legrii timpurii, o expresie de transmitere de mesaje are mereu acelai efect. Implementarea la care este legat metoda mesaj este decis de compliator pe baza clasei unice (tipului) din care face parte expresia de destinaie i nu mai este modificat niciodat. n cazul legrii trzii, o expresie de transmitere de mesaje poatea avea mai multe efecte. Metoda mesaj poate fi legat, pe rnd, la mai multe implementri, n funcie de clasa din care face parte obiectul de destinaie. Prin urmare, aceeai form sintactic are mai multe semnificaii (forme semantice). Acest fenomen poart numele de polimorfism i este o consecin a tehnicilor de specializare a claselor. Metodele virtuale i polimorfismul permit crearea unor sisteme de programe extensibile i cu caracter general: ele pot fi proiectate pentru a prelucra obiecte ce aparin unor clase ce vor fi definite la un moment ulterior. Spre exemplu, n acest moment dispunem de clasele Persoana i Student (clas specializat ) iar metoda afisare() o presupunem a fi virtual ( va fi legat trziu). Vom defini o funcie client a clasei Persoana care va afia informaiile despre o persoan ntr-un chenar simplu format dintr-o linie superioar i una inferioar.
void afisare_cu_chenar(Persoana *pp){ cout<<***********<<endl; // linia superioara pp->afisare();// transmitere mesaj de afisare cout;;endl; cout<<-----------<<endl; // linia inferioara }

Deoarece metoda afisare() este virtual, expresia pp->afisare()este (semantic) polimorf i n consecin funcia void afisare_cu_chenar(Persoana *pp) poate fi utilizat nu numai pentru afiarea informaiilor despre persoane, dar i despre studeni, ca n urmtorul progam client:
int i; Persoana *pp; cin>>i; if( i==1)pp= new Persoana(Ionescu, Ion, 1947); else pp= new Student(Petrescu, Petre, Spiru Haret, 1986); // urmeaza o instructiune cu caracter general pp->afisare_cu_chenar(pp);

Prin urmare, afisare_cu_chenar este clientul tuturor claselor ce specializeaz clasa Persoana, chiar i al celor ce vor fi declarate ulterior. De pild, n acest moment (ulterior celui n care am scris funcia afisare_cu_chenar) putem specializa din nou clasa Persoana pentru a obine clasa Cadru didactic, ca n diagrama urmtoare:

117

Din diagram se vede c am optat pentru legarea tarzie a metodei void afisare(). Ea va fi implementat i n clasa Cadru_didactic, dispunnd acum de o nou implementare (a treia pn n acest moment) numit void Cadru_didactic::afisare(). Implementarea funciei void afisare_cu_chenar(Persoana *) nu se va modifica n nici un fel iar instruciunea pp->afisare_cu_chenar(pp); din programul client anterior are caracter general i nu necesit nici un fel de schimbare pentru a a fi utilizat la afiarea cu chenar a datelor despre un cadru didactic. Programul client anterior are deci pri cu form fixat, cu rol major n adaptarea sa la condiii noi de funcionare i care, n mod paradoxal, nu necesit intervenii pentru adaptare la aceste condiii. Iat un exemplu de program adaptat, unde modificrile sunt subliniate. De remarcat c partea fixat nu a fost afectat.
int i; Persoana *pp; cin>>i; if( i==1)pp= new Persoana(Ionescu, Ion, 1947); else if(i==2) pp= new Student(Petrescu, Petre, Spiru Haret, 1986); else pp= new Cadru_didactic(Balanescu, Tudor, Spiru Haret, 1947,profesor); // urmeaza o instructiune cu caracter general pp->afisare_cu_chenar(pp);

Programarea polimorf (posibil datorit metodelor virtuale) elimin prezena repetat a structurilor de control de tip switch necesare pentru a distinge tipul obiectelor prelucrate. Clasele specializate pot furniza implementri proprii metodelor virtuale din clasa de baz, dar acest lucru nu este obligatoriu. Efectul transmiterii unui mesaj reprezentat de o metod virtual ctre un obiect depinde de clasa din care face parte obiectul respectiv. Dac o metod virtual dispune de mai multe implementri, la transmiterea mesajului este activat implementarea ce corespunde clasei din care face parte obiectul destinatar. Acest mod de legare al unei implementri la un mesaj, realizat la momentul executrii programului, poart numele de legare ntrziat (late binding) sau legare dinamic (dynamic binding) i este un concept contrapus modului de legare clasic, realizat la momentul compilrii (legare static). Legarea ntrziat permite firmelor ce furnizeaz software independent (independent software vendors, ISV) s distribuie programe fr a face cunoscute secretele de proiectare. Aceste firme distribuie doar fiiere antet i fiiere cu cod obiect. Depistarea secretelor de proiectare din fiierul cu cod obiect este practic imposibil deoarece efortul pentru decriptarea unui fiier cod obiect, n scopul de a depista algoritmii i structurile de date utilizate la implementare, este comparabil cu efortul de realizare a unei cercetrii proprii. Furnizorii de software independent nu livreaz textul surs al implementrilor. Clienii pot specializa clasele furnizate de vnztori pentru a crea clase noi prin care specializeaz funcionalitatea claselor obinute de la furnizor. Programele care funcionau cu clasele furnizorului vor continua s funcioneze, fr nici un fel de intervenie asupra lor, cu clasele specializate producnd efectele dorite prin operaia de specializare.

118

I.5. Observaii asupra modului de transmitere a argumentelor i rezultatelor Este recomandat evitarea transmiterii argumentelor i a rezultatului prin valoare; pentru a mpiedica modificarea lor, argumentele pot fi transmise prin referin, cu utilizarea modificatorului const; rezultatul se transmite de asemenea prin referin, cu condiia ca obiectul referit s existe i dup terminarea funciei (de exemplu poate fi un argument transmis prin referin, un obiect creat dinamic cu operatorul new sau un obiect global). La transmiterea prin valoare a rezultatului, utilizarea direct a unui constructor n instruciunea return este mai eficient dect returnarea unui obiect local: C m(){ // transmitere rezultat prin contruire return C(); }; n loc de C m(){ // transmitere rezultat prin construire + copiere C obiect_local; // apel constructor return obiect_local; // apel constructor de copiere }; Se poate interzice transmiterea prin valoare a obiectelor n programele client prin declararea unui constructor de copiere inaccesibil (nu e nevoie s fie implementat)
class R{ private: long n,d; R(R &r); // constructor de copiere inaccesibil public: // constructor de conversie R(long n=0, long d=1):n(n),d(d){} friend R operator+(R x, R y){ return R(x.n*y.d+y.n*x.d, x.d*y.d); } }; void main(){ R a=1,b=2,c; c=a+b; // eroare, constructor de copiere inaccesibil }

// apel constructor

I.5.1. Distrugerea obiectelor cu memorie alocat dinamic Dac obiectele au atribute cu memorie alocat dinamic, clasa va fi prevzut cu destructori pentru eliberarea spaiului alocat. Este recomandat legarea dinamic a destructorilor deoarece legarea static conduce la irosirea spaiului de memorie. Exemplu:
// destructor static, destructor virtual // a se evita stergerea explicita a obiectelor alocate pe stiva #include <iostream.h> #include <conio.h> class B{ public: B(){cout<<"B()"<<endl;} // destructor cu legare dinamica virtual ~B(){cout<<"~B()"<<endl;} }; class D: public B{ public: D(){cout<<"D()"<<endl;} virtual ~D(){cout<<"~D()"<<endl;} }; void main(){ clrscr(); B b, *pb; // apel B() D d, *pd; // apel B();D()

119

pd=&d; delete pd; // incorrect, stergere obiect alocat pe stiva pd= new D();// apel B();D() pb=new D(); // apel B();D() delete pb; // apel ~B();~D() datorata legarii dinamice delete pd; // apel ~B();~D() }

n absena specificatorului virtual din declaraia virtual ~B() a destructorului clasei B, operatorul delete pb activeaz doar destructorul ~B() iar memoria alocat dinamic pentru atributele obiectului de clas D referit de pb nu este eliberat. Nu se va utiliza operatorul delete dect pentru obiecte alocate dinamic de programator; tergerea obiectelor alocate pe stiv se face automat la terminarea funciei. I.5.2. Iniializare, atribuire, conversie Dac obiectele vor fi utilizate pentru iniializarea altor obiecte, clasa va fi prevzut cu constructor de copiere ce implementeaz semantica prin valoare. Sematica prin referin favorizeaz apariia obiectelor fantom. Exemplu:
class String{ public: // constructor de conversie si costructor implicit String (int len=0); // constructor de conversie String(const char*); // constructor de copiere, semantica prin valoare String(const String & s){ len=s.len; str=new char[len+1]; if(str==NULL) exit(1); strcpy(str,s.str}; } // destructor ~String(){delete str;} // operator atribuire, prima suprainarcare String & operator=(const String &d){ if (&d==this) return *this; delete str;//nu in constructorul de copiere! len=d.len; str=new char[len+1]; if(str==NULL)exit(1); strcpy(str,d.str); return *this; } // operator atribuire, a doua suprainarcare String& operator=(const char d){ delete str;//nu in constructorul de copiere! len=strlen(d); str=new char[len+1]; if(str==NULL)exit(1); strcpy(str,d); return *this; } private: int len; char *str; };

n absena constructorului String(String &)cu semantica prin valoare din clasa String, se utilizeaz constructorul de copiere predefinit care are semantica prin referin. n prezena destructorului ~String(){delete str;}, obiecte fantom pot aprea:

120

1. dup apelul unor funcii cu argumente transmise prin valoare.


void f(String s){// transmitere argument prin valoare } String x=abcd; f(x); // x a fost copiat de constructorul de copiere implicit, // care are semantica prin referinta // la terminarea lui f, este apelat ~String() // care elibereaza zona alocata pentru abcd // x este acum obiect fantoma! f(x);

x a fost modificat la primul apel al lui f, chiar dac nici o instruciune nu apare n corpul ei; 2. la ieirea din blocuri de instruciuni
String v=abcd; { String t=v; // constructorul predefinit, semantica prin referin t=xx; } // acum v are in componenta un obiect fantoma!

Dac obiectele sunt utilizate n operaii de atribuire s=d, operatorul de atribuire trebuie suprancrcat i implementat prin semantica prin valoare; se va avea n vedere: 1. nainte de atribuire, eliberarea spaiului de memorie alocat dinamic obiectului s; 2. verificarea cazului de autoatribuire s=s, pentru a evita pierderea informaiilor prin aciunea de la punctul 1; 3. rezultatul returnat prin referin, pentru ca efectul expresiei (a=b)=c s fie echivalent cu a=c; 4. suprancrcare multipl a operatorului de atribuire, pentru a evita conversiile implicite. n absena celei de adoua variante de suprancrcare din exemplul urmtor, n evaluarea expresiei s=abcd se utilizez constructorul de conversie nainte de aplicarea operatorului de atribuire.

Un constructor ce poate fi utilizat cu un singur argument care nu este din clasa constructorului se numete constructor de conversie. De exemplu, R(long n=0, long d=1) este un astfel de constructor. Constructorii de conversie permit relaxarea regulillor de verificare tare a tipurilor din C++ prin conversia implicit: a argumentelor: f(1) dac f este declarat prin f(R r) a rezultatelor returnate: R g(){return 1;} a operandului din dreapta operatorului de atribuire: r=1 a expresiei de iniializare: R r=1. Tehnica trebuie utilizat cu pruden deoarece poate afecta eficiena, mai ales n cazul semanticii prin valoare. Nu se face conversie implicit n cazul transmiterii prin adres, dar conversia explicit este posibil (n acest caz constructorul de conversie nu joac nici un rol i poate s lipseasc). Exemplu:
R f(R *p){return *p + *p;} int x; f(&x); // eroare f((R*)&x); // correct, dar operator+ va lucra pe o zona de memorie improprie; // de efectele acestui tip de conversie // este responsabil programatorul. String s; f((R*)s);// sintactic correct, chiar daca nu exista // constructor de conversie de la String la R

n cazul transmiterii prin referin, se face conversie implicit dar programatorul este avertizat c se utilizeaz o variabil temporar. Exemplu:
void f(R &r){ r=R(1,2);} int x; f(x); // correct, este modificata o variabila temporara

121

II. TEHNICI EVOLUATE DE PROGRAMARE N JAVA. DIAGRAME DE STARE n acest capitol sunt prezentate elementele definitorii ale diagramelor de stare, definindu-se tranziiile, evenimentele speciale, superstrile, pseudostrile iniiale i finale, modul de utilizare (folosind tabelele de stare), precum i tehnici de implementare a acestora: prin instruciuni switch-case seriale sau prin ablonul State. II.1. Noiuni fundamentale UML posed o mulime destul de bogat de notaii folosite pentru a descrie mainile cu stri finite (FSMs), n continuare prezentndu-se cele mai importante dintre acestea. Mainile cu stri finite reprezint unelte extrem de folositoare n dezvoltarea diverselor aplicaii software. De obicei, sunt folosite n crearea interfeelor grafice (GUIs), a protocoalelor de comunicaie, ct i a oricrui sistem bazat pe evenimente. n figura 1.1 este prezentat o diagram de stare (STDs State Transition Diagram) care descrie o main cu stri finite ce controleaz modul n care un utilizator se conecteaz la un sistem. Dreptunghiurile cu colurile rotunjite reprezint stri. Numele fiecrei stri se afl n compartimentul su superior. n partea de jos a dreptunghiului se afl aciunile speciale, care indic ceea ce trebuie fcut atunci cnd se intr sau se iese din starea respectiv. De exemplu, atunci cnd se intr n starea Prompting for Login, se invoc aciunea showLoginScreen. Cnd se iese din aceast stare se invoc aciunea hideLoginScreen. Sgeile dintre stri sunt numite tranziii. Fiecare este etichetat cu numele evenimentului care declaneaz tranziia. Aceast etichet poate conine i aciunea care trebuie executat atunci cnd are loc tranziia. De exemplu, dac sistemul se afl n starea Prompting for Login i are loc un eveniment login, se va trece n starea Validating User i se va invoca metoda validateUser. Cercul negru din colul stnga-sus al diagramei este numit pseudo stare iniial. O main cu stri finite i ncepe activitatea printr-o tranziie din aceast stare iniial. Deci, n exemplul nostru, FSM-ul pornete printr-o tranziie n starea Prompting for Login. Strile Sending Password Failed i Sending Password Succeeded sunt incluse ntr-o super stare, deoarece ambele reacioneaz la evenimentul OK printr-o tranziie n starea Prompting for Login. ntruct nu s-a dorit utilizarea a dou sgei identice, s-a introdus convenia de a folosi o superstare. II.2. Evenimente speciale Compartimentul inferior al unei stri conine perechi eveniment/aciune. Evenimentele entry i exit sunt standard dar, aa cum se poate vedea din figura 1.2, se pot folosi i alte evenimente, n funcie de necesiti. Dac unul din aceste evenimente speciale apare n timp ce maina cu stri finite se afl n acea stare, atunci este invocat aciunea corespunztoare. nainte de apariia UML, pentru a reprezenta un eveniment special se folosea o sgeat care forma un ciclu pe starea respectiv, aa cum se poate vedea n figura 1.3. ns, n UML, acest lucru are un alt neles. Atunci cnd se iese dintr-o stare, orice tranziie invoc aciunea corespunztoare evenimetului exit (dac exist). Similar, cnd se intr ntr-o nou stare, se invoc aciunea ce corespunde evenimentului entry (dac exist). Deci, n UML, o tranziie reflexiv precum cea din figura 1.3, invoc nu numai aciunea myAction, ci i aciunile corespunztoare evenimentelor exit i entry.

122

Figura 1.1 Diagrama de stare a unei FSM ce controleaz modul de conectare la un sistem

Figura 1.2 Diagram de stare cu evenimente speciale

Figura 1.3 Tranziie reflexiv II.3. Super stri Aa cum se poate vedea n figura 1.1, super strile sunt folositoare atunci cnd mai multe stri, la apariia aceluiai eveniment, rspund n acelai mod. Se poate trasa o super stare n jurul strilor care se comport identic la apariia unui anumit eveniment, putndu-se nlocui astfel tranziiile care pornesc de la fiecare stare cu o singur tranziie care pornete de la super stare. Aadar, cele dou diagrame din figura 1.4 sunt echivalente.

Figura 1.4

123

Tranziiile de la superstri pot fi suprascrise prin trasarea tranziiilor explicite de la substri. Astfel, n figura 1.5, tranziia pause de la S3 suprascrie tranziia implicit pause corespunztoare super strii Cancelable. n acest sens, o super stare poate fi comparat cu o clas de baz. Substrile suprascriu (override) tranziiile superstrii n acelai mod n care clasele derivate pot suprascrie metodele clasei de baz corespunztoare. Totui, relaia dintre superstri i substri nu este echivalent cu motenirea.

Figura 1.5 Suprascrierea tranziiilor super strilor La fel cum strile obinuite pot avea evenimente speciale, i superstrile pot deine astfel de evenimente. n figura 1.6 este prezentat o main cu stri finite n care exist evenimente exit i entry att n super stri, ct i n substri. Atunci cnd FSM-ul trece din starea Some State n starea Sub, mai nti este invocat aciunea enterSuper, urmat de aciunea enterSub. n acelai mod, dac are loc tranziia din starea Sub2 n starea Some State, prima dat este invocat exitSub2 i apoi exitSuper. Oricum, ntruct tranziia e2 din starea Sub n starea Sub2 nu iese din superstare, se invoc doar aciunile exitSub i enterSub2.

Figura 1.6 Invocarea ierarhic a aciunilor corespunztoare II.4. Pseudostri iniiale i finale

evenimentelor entry i exit

n figura 1.7 sunt prezentate dou pseudo stri care sunt utilizate frecvent n UML. O main cu stri finite

Figura 1.7 Pseudostri: iniial i final i ncepe existena n procesul de tranziie din pseudostarea iniial. Tranziia care pornete din pseudostarea iniial nu poate avea un eveniment, ntruct evenimentul este crearea mainii cu stri finite. n schimb, ea poate avea o aciune. Aceasta va fi prima aciune invocat dup crearea FSM-ului. Similar, existena unei maini cu stri finite se ncheie n procesul de tranziie n pseudostarea final. De fapt, n aceast psedostare final nu se ajunge niciodat. Dac trebuie realizat o aciune n timpul tranziiei ctre starea final, atunci aceasta va fi ultima aciune invocat de FSM.

124

II.5. Utilizarea diagramelor de stare Diagramele de stare sunt extrem de folositoare pentru determinarea strilor subsistemelor al cror comportament este destul de bine cunoscut. Pe de alt parte, majoritatea sistemelor care pot fi reprezentate prin diagrame de stare nu au comportamente ce pot fi cunoscute n avans. n plus, conduita celor mai multe sisteme se schimb, evolueaz n timp. Diagramele nu reprezint o metod favorabil pentru sistemele care trebuie s se schimbe n mod frecvent. Problemele legate de aezare i spaiu se manifest chiar i n cazul coninutului diagramelor. Acest lucru i mpiedic uneori pe designeri s fac modificrile necesare pentru un anumit proiect. Spectrul refacerii diagramei i determin s nu mai adauge anumite stri necesare, obligndu-i s foloseasc o soluie particular care s nu modifice layout- ul diagramei. Pe de alt parte, textul reprezint un mediu n care se pot face foarte uor modificri. Problemele legate de aezare aproape c nu exist, existnd ntotdeauna spaiu pentru adugarea de noi linii. De aceea, pentru sistemele care evolueaz n timp, se folosesc tabelele de stare (STTs State Transition Tables). n figura 2.1 este prezentat diagrama de stare (STD) corespunztoare unei ui de metrou, care poate fi uor reprezentat sub forma unui tabel de stare (STT), aa cum se poate vedea n figura 2.2.

Figura 2.1 Diagrama de stare corespunztoare unei ui de metrou

Figura 2.2 Tabelul de stare corespunztor unei ui de metrou Tabelul de stare conine patru coloane. Fiecare rnd al tabelului reprezint o tranziie, coninnd cele dou capete ale sgeii, evenimentul i aciunea corespunztoare ce formeaz eticheta tranziiei. Acest tabel se citete folosind urmtorul model: Dac sistemul se afl n starea Locked i apare un eveniment coin, atunci se trece n starea Unlocked i se invoc funcia Unlock. Acest tabel poate fi uor convertit ntr-un fiier text: Locked Locked Unlocked Unlocked coin Unlocked Unlock pass Locked Alarm coin Unlocked Refund pass Locked Lock

Aceste aisprezece cuvinte conin ntreaga logic a acestei maini cu stri finite. Exist compilatoare care citesc acest fiier text i genereaz cod care implementeaz logica respectiv. II.6. Implementarea mainilor cu stri finite Exist mai multe tehnici pentru a implementa o main cu stri finite. Una din cele mai folosite metode const n folosirea mai multor instruciuni switch seriale (nested). n codul surs 1 este prezentat implementarea mainii cu stri finite din figura 2.1. Cod surs 1
class Turnstile{ final static int Locked = 0; final static int Unlocked = 1; final int Pass = 2; final int Coin = 3; int s = Unlocked;

125

public void Lock(){ System.out.println("se executa metoda Lock()" + "\n" + "se trece in starea Locked"); } public void Unlock(){ System.out.println("se executa metoda Unlock()" + "\n" + "se trece in starea Unlocked"); } public void Refund(){ System.out.println("se executa metoda Refund()" + "\n" + "se ramane in starea Unlocked"); } public void Alarm(){ System.out.println("se executa metoda Alarm()" + "\n" + "se ramane in starea Locked"); } void Transition(int e){ // static int s = Unlocked; switch(s){ case Locked: switch(e){ case Coin: s = Unlocked; Unlock(); break; case Pass: Alarm(); break; } break; case Unlocked: switch(e){ case Coin: Refund(); break; case Pass: s = Locked; Lock(); break; } break; } } }; class MyApp{ public static void main(String[] args){ Turnstile stare = new Turnstile(); stare.Transition(stare.Pass); stare.Transition(stare.Pass); stare.Transition(stare.Coin); stare.Transition(stare.Coin); } };

Ieirea corespunztoare acestui program este urmtoarea: se execut metoda Lock( ) se trece n starea Locked se execut metoda Alarm( ) se rmne n starea Locked se execut metoda Unlock( ) se trece n starea Unlocked se execut metoda Refund( ) se rmne n starea Unlocked
126

Dei este destul de folosit, aceast metod nu este cea mai eficient. Pe msur ce maina cu stri finite implementat folosind aceast tehnic se dezvolt, instruciunile switch seriale devin din ce n ce mai greu de folosit i de citit de ctre programator. Codul poate ajunge foarte mare, multe poriuni fiind identice. O alternativ a acestei metode const n folosirea ablonului de proiectare State. Un ablon de proiectare denumete, abstractizeaz i identific aspectele cheie ale unei structuri comune de proiectare pe care o face util pentru crearea unui design orientat spre obiecte, reutilizabil. ablonul de proiectare identific clasele i instanele participante, rolurile i colaborrile lor, precum i distribuia responsabilitilor. Fiecare ablon de proiectare se concentreaz asupra unei anumite probleme de proiectare orientat spre obiecte. El descrie cnd se aplic, dac poate fi aplicat n cazul unor alte restricii de proiectare i consecinele i compromisurile utilizrii lui. ablonul de proiectare State (numit i obiect pentru stri) permite unui obiect s-i modifice comportamentul cnd starea sa intern se schimb. Astfel, obiectul va prea c-i schimb clasa. ablonul State este folosit n oricare dintre urmtoarele cazuri: Comportarea unui obiect depinde de starea sa i, la execuie, obiectul trebuie s-i modifice comportamentul n funcie de respectiva stare.

Figure 3.1 TurnstileFSM modelat cu ablonul State Operaiile au instruciuni condiionale de mari dimensiuni, cu multe pri, care depind de starea obiectului. Aceast stare este de obicei reprezentat prin una sau mai multe constante enumerate. Deseori, mai multe operaii vor conine aceeai structur condiional. ablonul State plaseaz fiecare ramur a structurii condiionale ntr-o clas separat. Acest lucru va permite tratarea strii unui obiect ca un obiect cu drepturi depline, care poate varia independent de alte obiecte. Folosind acest ablon de proiectare, maina cu stri finite din figura 2.1 poate fi transpus n diagram de clase din figura 3.1. Implementarea acestei diagrame este prezentat n codul surs urmtor: Codul surs 2
class Turnstile{ public void Lock(){ System.out.println("se executa metoda Lock()" + "\n" + "se trece in starea Locked"); } public void Unlock(){ System.out.println("se executa metoda Unlock()" + "\n" + "se trece in starea Unlocked"); }

127

public void Refund(){ System.out.println("se executa metoda Refund()" + "\n" + "se ramane in starea Unlocked"); } public void Alarm(){ System.out.println("se executa metoda Alarm()" + "\n" + "se ramane in starea Locked"); } }; class TurnstileFSM extends Turnstile{ public TurnstileState itsState; public void setState(TurnstileState s){ itsState = s; } public void Coin(){ itsState.Coin(this); } public void Pass(){ itsState.Pass(this); } }; abstract class TurnstileState{ public static LockedState lockedState; public static UnlockedState unlockedState; public abstract void Coin(TurnstileFSM t); public abstract void Pass(TurnstileFSM t); }; class LockedState extends TurnstileState{ public LockedState(){ } public void Coin(TurnstileFSM t){ t.setState(unlockedState); t.Unlock(); } public void Pass(TurnstileFSM t){ t.Alarm(); } }; class UnlockedState extends TurnstileState{ public UnlockedState(){ } public void Coin(TurnstileFSM t){ t.Refund(); } public void Pass(TurnstileFSM t){ t.setState(lockedState); t.Lock(); } }; class MyApp{ public static void main(String[] args){ TurnstileFSM turnstileFSM = new TurnstileFSM(); TurnstileState turnstileState = new UnlockedState(); turnstileState.lockedState = new LockedState(); turnstileState.unlockedState = new UnlockedState(); turnstileFSM.setState(turnstileState); turnstileFSM.Pass(); turnstileFSM.Pass(); turnstileFSM.Coin(); turnstileFSM.Coin(); } };

128

Ieirea corespunztoare acestui program este aceeai cu a celui din codul surs 1. Dac starea iniial este Unlocked, atunci TurnstileFSM, dup apelul funciei setState( ), va conine o referin ctre clasa UnlockedState, derivat din clasa TurnstileState. Dac apare un eveniment Pass, se apeleaz metoda Pass( ) a clasei TurnstileFSM. Aceasta deleag (apeleaz) metoda Pass( ) a clasei abstracte TurnstileState, care va apela metoda cu acelai nume din UnlockedState. Obiectul transmis ca parametru metodei Pass( ) este chiar obiectul curent (cel asupra cruia se va face modificarea). Metoda Pass( ) din UnlockedState va face operaiile necesare asupra obiectului current. Acest design are mai multe avantaje. n primul rnd, comportamentul este separat de logica sistemului. Aceasta din urm este coninut n ierarhia de clase TurnstileState, n timp ce comportamentul este cuprins n ierarhia ce are la baz clasa Turnstile. Dac se dorete schimbarea logicii, dar pstrarea comportamentului, ori se schimb clasa TurnstileState, ori se creeaz o nou clas derivat din aceasta. Pe de alt parte, dac se dorete modificarea comportamentului fr a modifica logica, se poate deriva clasa TurnstileFSM i suprascrie metodele acesteia. II.7. Concluzii Mainile cu stri finite reprezint un concept destul de important pentru dezvoltarea de sisteme software. UML, prin diagramele de stare, ofer o modalitate eficient de vizualizare a acestor FSM-uri. Cu toate acestea, pentru a dezvolta i menine un FSM, este mai convenabil s se foloseasc, n locul unei diagrame, un limbaj textual. EXERCIII 1. Explicai rezultatele afiate de urmtorul program:
#include <iostream.h> int g=0, x=0; class C{ public: void m(){x=500; g=100; } void afisare(){ cout<<"x= "<<x<<" g= "<<g; } private: int x; }; void m(){x=250; g=50; void main(){ C c; cout<<endl; }

c.m(); c.afisare(); cout<<endl; m(); c.afisare(); cout<< endl; }

2. Explicai rezultatele afiate de urmtorul program:


#include <iostream.h> void interschimb(float& i, float& j ) { float t=i; i=j; j=t; } void interschimb(float& i, int& j ) { float t=i; i=j; j=t; }

129

int x=100; float f1=1.0, f2=2.0; void main(){ interschimb(f1,f2); cout<< endl<< " f1= "<<f1<<" f2= "<<f2; interschimb(f1,x); cout<< endl<< " f1= "<<f1<<" x= "<<x; interschimb(x,f2); cout<< endl<< " x= "<<x<<" f2= "<<f2; }

3. a. Suprancrcai operatorul de inserie << i explicai rezultatele afiate de urmtorul program:


#include <iostream.h> #include <string.h> class Persoana{ public: Persoana(char* n){nume=n;}; void x(){ for (int i=0; nume[i]!= '\0';i++)nume[i]='x'; } private: char *nume; }; void main(){ char *t="Tudor"; char *a="Andrei"; Persoana pt(t), pa(a); cout<<pt<<endl<<pa<<endl; pt=pa; pt.x(); cout<<pt<<endl<<pa<<endl; }

b. Suprancrcai operatorul de atribuire astefel nct acelai program s afieze: Tudor Andrei xxxxx Andrei 4. Descriei diferena dintre legarea timpurie (static) i legarea trzie (dinamic) a metodelor 5. Explicai rezultatele afiate la executarea urmtorului program:
#include <iostream.h> class S{ public: static S* create(){ if(i==0){ refS=new S(); i++; } return refS; } void setName(char *s){name=s;} char* getName(){return name;} static int geti(){return i;} private: static S* refS; static int i; S(){}; char *name; }; int S::i=0; S* S::refS; void main(){ S *o1, *o2;

130

o1=S::create(); o2=S::create(); cout<<S::geti()<<endl; o1->setName("Matematica"); o2->setName("Informatica"); cout<<o1->getName()<<endl; cout<<o2->getName()<<endl; }

6. Numere raionale Definii clasa NumereRationale, ale crei obiecte sunt numere raionale. Definii metode i operatori astfel nct executarea programului urmtor:
#include rational.h void main(){ NumereRationale x(10, -20); cout<< x << ; << 3 * x << ; << x }

* 2 << ; << x * x<< endl;

s afieze linia: -1/2 ; -3/2 ; -1 ; 1/4 7. Persoana-Student Fie urmatoarele 3 fisiere: 1) Fisierul persoana.h
class Persoana{ private: char *nume; public: Persoana(char *n); void afisare(); };

2) Fisierul student.h
class Student:public Persoana{ private: char *universitate; public: Student(char *n, char *univ); void afisare(); };

3) Fisierul aplicatie.cpp
void main(){ Persoana p("Tudor"); Student s("Tudor", "Universitatea din Pitesti"); Persoana *ppers; Student *pstud; pstud=&p; ppers=&p; ppers->afisare(); ppers=&s; ppers->afisare(); }

131

Se cere sa: a) modificai cele trei fiiere astfel nct nici unul s nu aib erori de compilare; b) construii fiierele persoana.cpp i student.cpp pentru a implementa cele dou clase; c) indicai ce fiiere trebuie incluse n proiect pentru ca executarea programului s fie posibil d) explicai rezultatele afiate prin executarea programului e) modificai fiierele persoana.h i student.h astfel nct programul s afieze urmtoarele linii: Tudor Tudor, Universitatea din Pitesti f) explicai legarea static i legarea dinamic (trzie) a metodelor unei clase. g) Supradefinii operatorul << astfel nct liniile de la punctul e) s fie obinute prin executarea instruciunilor: cout << p<<endl; cout << s; 8. Explicai erorile semnalate la compilarea urmtorului program
class Punct{ public: Punct(){x=0; y=0;} Punct(int xx=0){x=xx;y=0;} Punct(int xx, int yy=0){x=xx; y=yy;} private: int x,y; }; void main(){ Punct p; Punct p1(1); Punct p2(1,2); }

9. a. Explicai erorile la executarea programului urmtor. b. Modificai constructorul de copiere pentru a elimina aceste erori
#include <iostream.h> class X{ public: X(int i=0){p=new int; if(p) *p=i;} X(const X &r){p=r.p;} ~X(){if(p){delete p; p=0;}} void show(){cout<<*p<<endl;} private: int *p; }; void main(){ X *o1,*o2; o1=new X(1); o2=new X(*o1); o1->show(); delete o1; o2->show(); delete o2; }

10. a. n programul urmtor, supradefinii operatorul << astfel nct cout<<i s afieze valoarea atributului i.x b. Precizai i explicai rezultatele afiate la executarea programului astfel obinut
#include <iostream.h> class C{ public: C(int i=0){x=i;} C& operator++(){++x; return *this;} C operator--(){--x; return *this;} private: int x;

132

}; void main(){ C i; cout<<i<<endl; cout<<++(++i)<<endl<<i<<endl; cout<<--(--i)<<endl<<i<<endl; }

11. a. Inlocuiti . . . in clasa Stack, astfel incat metodele push si pop sa asigure tratarea exceptiilor. Numarul maxim de elemente din vectorul supporteste dat de expresia suport.length. b. Scrieti o aplicatie in care sa tratati exceptiile lansate de push si pop.
class Stack{ int varf; Object suport[]; void push(Object x). . .{. . .} Object pop(). . .{. . .} void init(int s){ varf=0; support=new Object[s]; } Stack(int s){. . .} }

12. Declarai i implementai clasele din urmtoarea diagram UML (Unified Modeling Language)

Persoana +Persoana(char *n) + virtual void afisare() -char *nume Student +Student(char *n, char *f) +virtual void afisare() -char *facultate Profesor +Profesor(char *n, char *f) +virtual void afisare() +stabileste_specialiatea(char *s) -char *facultate -char *specialitate
13. Fie urmtorul program C++:
#include <iostream.h> class A{ public: void st(){cout<<"metoda A::st()"<<endl;} virtual void vrt(){cout<<"metoda A::vrt()"<<endl;} void stafis(){cout<<"metoda A::stafis()"<<endl; st(); vrt(); } virtual void vrtafis(){ cout<<"metoda A::vrtafis()"<<endl; st(); vrt(); } }; class B: public A{ public: void st(){cout<<"metoda B::st()"<<endl;} virtual void vrt(){cout<<"metoda B::vrt()"<<endl;}

133

void stafis(){cout<<"metoda B::stafis()"<<endl; st(); vrt(); } virtual void vrtafis(){ cout<<"metoda B::vrtafis()"<<endl; st(); vrt(); } }; void main(){ A a, *p; B b; cout<<"Obiectul a"<<endl; p=&a; p->st(); p->vrt(); p->stafis(); p->vrtafis(); cout<<"Obiectul b"<<endl; p=&b; p->st(); p->vrt(); p->stafis(); p->vrtafis(); }

Ce se afieaz prin executarea sa? Explicai fiecare linie afiat. R. Obiectul a metoda A::st(); metoda A::vrt(); metoda A::stafis(); metoda A::st(); metoda A::vrt(); metoda A::vrtafis(); metoda A::st(); metoda A::vrt(); Obiectul b metoda A::st(); metoda B::vrt(); metoda A::stafis(); metoda A::st(); metoda B::vrt(); metoda B::vrtafis(); metoda B::st(); metoda B::vrt(); 14. Matrice Fie urmtoarea schi a clasei Matrice, ale crei obiecte sunt matrice de numere reale:
class Matrice{ private: int m; // nr. linii int n; // nr. coloane float *p; // zona celor m*n elemente // constructori // operatori // metode };

134

Se cere s completai definiia clasei i s o implementai n aa fel nct urmtorul program s produca efectele sugerate n comentarii:
void main(){ Matrice a(2,3); //toate elementele nule cin>>a; // a va primi valoarea {1.1, 1.2, 1.3, 2.1, 2.2, 2.3} Matrice b(a); // b va fi initializat cu valoarea lui a cin>>a; // a = {0.11, 0.12, 0.13, 0.21, 0.22, 0.23} cout<<a; // afiseaza {0.11, 0.12, 0.13, 0.21, 0.22, 0.23} cout<<endl; cout<<b; // afiseaza {1.1, 1.2, 1.3, 2.1, 2.2, 2.3} b=a; a=0; // toate elementele nule cout<<a; // afiseaza {0.0, 0.0, 0.0, 0.0, 0.0, 0.0} cout<<b; // afiseaza {0.0, 0.0, 0.0, 0.0, 0.0, 0.0} }

15. Liste generice dublu nlnuite S se completeze definiiile i s se implementeze urmtoarele clase, ale cror obiecte sunt liste generice dublu nlnuite:
template <class T> class EDL{ private: T info; EDL *urmator; EDL *anterior; public: EDL(); EDL(T c); EDL<T> *da_urmator(); EDL<T> *da_anterior(); void da_info(T &c); // operatorii <<, >> }; template <class T> class LDL: public EDL<T>{ private: EDL<T> *inceput_lista, *sfarsit_lista; public: LDL(); void memo(T c);// introduce la inceputul listei c void scoate(T c);// scoate primul element c void afisare(); }; void main(){ LDL <double> ld; double c; EDL<double> *p; ld.memo(1.1); ld.memo(2.2); ld.memo(3.3); ld.afisare(); ld.scoate(2.2); ld.afisare(); }

135

16. ir caractere Fie urmtoarea schi a clasei CharString, ale crei obiecte sunt iruri de caractere:
class CharString{ private: char *p; int lungime; // constructori // operatori };

Se cere s completai definiia clasei i s o implementai n aa fel nct urmtorul program s produc efectele sugerate n comentarii:
void main(){ CharString s("Examen"), t("POO"); CharStringr; // sir nul r= " la "; cout<<("Examen la " + t)<<endl; // afiseaza "Examen la POO" cout<<(s+r+t)<< endl;// afiseaza "Examen la POO" cout<<(s+" la POO")<<endl;// afiseaza "Examen la POO" char *cs; CharString a("Anul 2 "); cs=a; cout<<cs; // afiseaza "Anul 2 " if(cs<=a) cout <<"cs <= a"<< endl; // afiseaza "cs <= a" }

Utilizai funciile strcmp, strcat etc. 17. Fie urmtorul program Java:
// Model View Controller import java.awt.*; import java.awt.event.*; class Model{ private int x=0; public Model(){}; public void increment(){x++;} public void decrement(){x--;} public int get_x(){return x;} } public class View extends Frame{ protected Button binc; protected Button bdec; protected Model m; private Controller c; protected TextField tf; public static void main(String args[]){ Frame f= new View(); } public View(){ setTitle("Exemplu Model-View-Controller"); binc= new Button("A"); add("North",binc); bdec= new Button("B"); add("South",bdec); m=new Model(); c=new Controller(this); binc.addActionListener(c); bdec.addActionListener(c);

136

tf=new TextField(10); add("Center",tf); setSize(100,250); setVisible(true); } } class Controller implements ActionListener{ private View vw; public Controller(View v){ vw=v; } public void actionPerformed(ActionEvent e){ Button source=(Button)e.getSource(); if (source==vw.binc) vw.m.increment(); else if(source==vw.bdec) vw.m.decrement(); vw.tf.setText(String.valueOf(vw.m.get_x())); } }

Se cere: a) descriei dispunerea in fereastr a componentelor i efectul acionrii butoanelor A i B; b) descriei procedeul Model-View-Controller; c) modificai programul folosind interfaa WindowListener i metoda sa public void windowClosing(WindowEvent e), astfel nct acionarea butonului x din colul din dreapta sus s nchida fereastra. R.
// Model View Controller import java.awt.*; import java.awt.event.*; class Model{ } public class View extends Frame implements WindowListener{ public void windowClosed(WindowEvent e){} public void windowOpened(WindowEvent e){} public void windowDeiconified(WindowEvent e){} public void windowIconified(WindowEvent e){} public void windowActivated(WindowEvent e){} public void windowDeactivated(WindowEvent e){} public void windowClosing(WindowEvent e){ System.exit(0); } } class Controller implements ActionListener{ }

18. Fie urmtorul program C++:


#include <iostream.h> class A{ public: void st(){cout<<"metoda A::st()"<<endl;} virtual void vrt(){cout<<"metoda A::vrt()"<<endl;} void stafis(){cout<<"metoda A::stafis()"<<endl; st(); vrt(); } virtual void vrtafis(){cout<<"metoda A::vrtafis()"<<endl; st(); vrt(); } }; class B: public A{ public: void st(){cout<<"metoda B::st()"<<endl;} virtual void vrt(){cout<<"metoda B::vrt()"<<endl;} void stafis(){cout<<"metoda B::stafis()"<<endl; st(); vrt(); } virtual void vrtafis(){cout<<"metoda B::vrtafis()"<<endl; st(); vrt(); } };

137

void main(){ A a, *p; B b; cout<<"Obiectul a"<<endl; p=&a; p->st(); p->vrt(); p->stafis(); p->vrtafis(); cout<<"Obiectul a"<<endl; p=&b; p->st(); p->vrt(); p->stafis(); p->vrtafis(); }

Ce se afieaz prin executarea sa? Explicai fiecare linie afiat. BIBLIOGRAFIE 1. Herbert Schildt, C++, manual complet, Teora, 1997. 2. Octavian Catrina, Iuliana Cojocaru, Turbo C++, Teora, 1993. 3. Tudor Blnescu, Metodologii avansate de programare orientat pe obiecte, Editura Fundaiei Romnia de Mine (n curs de apariie). 4. H.M. Deitel, P.J. Deitel, C++, How to program, Prentice Hall, 1998. 5. Ipate, Florentin Eugen, Modelare Orientat pe Obiecte cu UML, Editura Universitii din Piteti, 2001. 6. Martin, Robert Cecil, UML for Java Programmers, Prentice-Hall, 2002.

138

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