Laborator 1

I. Dezavantajele programarii procedurale

a) Datele si logica programului (functiile) nu se pot grupa impreuna si este dificil sa se spuna o anumita operatie ce date influenteaza:

    Struct Person { ... char name[30]; ...};
    Struct Car{ ... char name[30]; ...};

    void setName(char *name);

- nu se stie daca setName seteaza numele unei persoane sau a unei masini. De aici rezulta slaba corelare intre date si functii.

b) Este greu sa se ascunda functiile auxiliare, care nu sunt folosite decat in interiorul unui modul, in mod privat, de accesarea lor din alte module:

    void initCarZero(Car *c){...}
    Car *createCar(){ ... initCarZero(c); ... return c;}

- functia initCarZero este folosita doar intern de catre functia createtCar si ar trebui sa fie invizibila din afara modulului.

c) Nu se pot ascunde mebrii unei structuri care sunt destinati doar uzului intern, existand riscul folosirii lor intr-un mod incompatibil cu intentia pentru care au fost creati:

    struct Line {double x1,y1,x2,y2; double len;};
    ...
    void f1 (Line *l){l->y1=100;}

- cel care a creat structura Line i-a adaugat si atributul len cu intentia ca acesta sa pastreze mereu lungimea liniei, pentru optimizarea vitezei de procesare a liniei. Din cauza ca len nu poate fi ascuns, un alt programator care a scris functia f1 a modificat o extremitate a liniei si nu a modificat in consecinta si atributul len. De aici rezulta inconsistenta de date.

d) Nu se pot folosi functii cu acelasi nume si cu semnaturi diferite:

    int max(int v1, int v2);
    int max (int v1, int v2, int v3);
    float max(float v1, float v2);

- in acest caz programatorul trebuie sa aleaga nume diferite pentru fiecare necesitate de folosire a functiei max.

e) Limbajele orientate pe obiecte dispun de mecanisme avansate de gestiune a memoriei, care elimina multe din pericolele folosirii pointerilor (memory leaking, pointer dangling). Din acest motiv, in general codul OOP necesita mai putine instructiuni decat cel procedural si este mai sigur.

II. Concepte de baza ale programarii orientate pe obiecte

a) Incapsulare - cuplarea in aceeasi structura semantica (clasa) a datelor si operatiilor care se executa asupra lor:

    class Line {
        float x1,y1,x2,y2;
        Line (Line l) { ... }
        float length () { ... }
        }

- datele unei clase se numesc atribute (x1, y1, ...). - functiile unei clase se numesc metode (length() ). - functiile unei clase care au acelasi nume ca cel al clasei si nu intorc nicio valoare, nici macar void, se numesc constructori (Line() ). Ele sunt apelate implicit la folosirea operatorului new. - in Java, exista o metoda speciala numita finalize, care este apelata implicit de sistem la eliberarea memoriei ocupata de obiectul respectiv. Aceasta eliberare are loc la un moment decis de sistem, chiar daca acel obiect nu mai este referit din program. finalize joaca rol de destructor, dar este rar utilizat fiindca Java foloseste un mecanism automat de Garbage Collection pentru managementul memoriei.

b) Mostenire (Inharitance)

- o clasa poate fi extinsa cu noi membrii (atribute sau metode prin crearea de noi clase care o mostenesc):

         class MyLine extends Line {
               Point middle() { ... }
               }

- MyLine este o clasa derivata din clasa Line care beneficiaza de toti membrii clasei Line la care se mai adauga middle. - in Java nu exista mostenire multipla, de exemplu:

        class C extends C1, C2 { ... } //invalid

nu este permis in Java. Pentru a se simula mostenirea multipla se foloseste mecanismul de implementare al unor interfete, Java permitand implementarea oricator interfete:

        class C implements I1, I2, I3 { ... }

- Java foloseste extends pentru mostenirea unei clase si implements pentru mostenirea (implementarea) unei interfete.

c) Structuri de date abstracte

- uneori este necesara modelarea unor concepte abstracte sau a unor generalizari care nu exista prin ele insele, ci doar prin particularizari ale lor:

        abstact class Being {
              boolean alive;
              abstract boolean hasGender();
              }

- metoda hasGender este declarata abstracta si atunci nu are implementare la nivelul clasei Being. Dupa ea se pune ";". Ea se va putea implementa ulterior in clasele derivate, atunci cand deja se stie implementarea ei:

        class Mammal extends Beeing{
              boolean hasGender () { return true; }
              }

- deoarece stim ca toate mamiferele au gen putem implementa hasGender la nivelul clasei Mammal. - daca o clasa contine cel putin o metoda abstracta, proprie sau mostenita, ea devine abstracta si trebuie marcata ca atare. - o clasa abstracta nu poate fi instantiata. - doar metodele pot sa fie declarate abstracte, nu si atributele. - o interfata (interface) este o clasa care are toate metodele abstracte, publice si nu are niciun atribut. Cand folosim interface nu mai trebuie sa declaram explicit abstract sau public metodele si interfata pentru ca ele sunt implicit asa. Deoarece Java permite implementarea oricator interfete intr-o clasa, este preferabil, daca problema o permite, sa modelam un anumit concept abstract cu ajutorul interfetelor decat cu clase abstracte:

        interface Dancer {
              String style();
              }

- metoda style() este implicit declarata ca fiind publica si abstracta. Interfata Dancer se poate folosi astfel:

        class TangoDancer implements Dancer {
              String style() { return "tango"; }
              }

- interfetele, la randul lor, pot sa derive din alte interfete (se foloseste extends si nu implements fiindca interfetele nu pot sa aiba implementari de metode):

        interface I extends I1,I2 {}

d) Decuplarea implementarii de interfata de acces

- cu exceptia celor mai simple tipuri de date, aproape intotdeauna avem nevoie de unele functii auxiliare care nu dorim sa fie vizibile din exterior si totodata sunt multe situatiile in care accesul unui membru al unei clase trebuie sa se faca doar printr-un protocol bine definit:

         class Line {
               private float x1,y1,x2,y2;
               private float len;

               private void update() {
                   len=Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
                   }

               public Line (float x1,float y1, float x2, float y2) {
                   this.x1=x1;
                   this.y1=y1;
                   this.x2=x2;
                   this.y2=y2;
                   update();                   
                   }

              public void setX1(float v){
                   x1=v;
                   update();
                   }

             public void setY1(float v){
                   y1=v;
                   update();
                   }

             public void setX2(float v){
                   x2=v;
                   update();
                   }

             public void setY2(float v){
                   y2=v;
                   update();
                   }

             public float length(){return len;}
             ........................
             }

          ...

       Line l = new Line(0,0,100,200);
       System.out.println("length="+l.length());
       l.setY1(50);
       System.out.println("length="+l.length());

- pentru marirea vitezei de calcul realizatorul clasei a decis ca lungimea liniei sa fie mereu precalculata si tinuta minte in atributul len. Daca s-ar lasa utilizatorului posibilitatea de a modifica in mod direct atributele liniei, se poate ajunge usor la inconsistenta de date, noua lungime a liniei nemai respectand valoarea stocata in len. Totodata len trebuie sa aiba o semantica de tip "read only" fiindca el doar poate sa reflecte noua lungime a liniei si nu sa o influenteze. Din aceste motive toate atributele au fost facute private clasei, ele nemai putand fi accesate in mod direct nici din exterior si nici din clasele derivate din Line. Singurul acces posibil pentru modificarea acestor atribute este prin intermediul constructorului sau a metodelor setX1, setY1, setX2, setY2. Toate aceste metode, care modifica linia, sunt concepute astfel incat sa tina mereu actualizat atributul len prin invocarea metodei auxiliare update. Metoda update() este declarata private pentru ca este destinata doar uzului intern al clasei Line si nu prezinta niciun interes pentru cei care folosesc clasa. - in Java avem patru modificatori de vizibilitate:

        1. private - membrul este vizibil doar in interiorul clasei respective
        2. protected - membrul este vizibil doar in interiorul clasei respective si a celor derivate din ea
        3. "package" (se considera implicit asa daca nu este alt modificator si deci nu exista ca si cuvant cheie pentru modificarea vizibilitatii) - membrul este vizibil in toate clasele din acelasi package
        4. public - membrul este vizibil in toate clasele din orice package

e) Polimorfism

- datorita structurilor de date abstracte, putem sa avem date generice asupra carora sa actionam intr-un mod generic, chiar daca nu stim concret clasa care a fost instantiata pentru acele date:

      class TangoDancer implements Dancer { ... }
      class WaltzDancer implements Dancer { ... }
      ...
      Vector<Dancer> dancers = new Vector<Dancer>();
      ...
      for(Dancer d:dancers){
            System.out.println("stil="+d.style());
            }

- vectorul dancers poate sa contina orice clase care implementeaza interfata Dancer. Din acest motiv stim ca toate aceste clase trebuie sa implementeze metoda "String style()" care este definita in Dancer. Deoarece metoda style() este polimorfica (in mod implicit in Java), ea stie de fiecare data cand este apelata despre ce obiect concret este vorba si poate rula codul corespunzator acelei implementari concrete. - polimorfismul ne scuteste de folosirea unui atribut care sa tina minte tipul concret al clasei si de un selector de tipul switch sau if in care sa implementam actiuni concrete utilizarii fiecarui tip concret.

f) Supraincarcare (overloading)

- uneori este necesar ca aceeasi operatie sa accepte mai multe cazuri, fiecare necesitand un set diferit de parametri de intrare:

        class Line {
              ...
              Vector<Point> intersections (Line l){...}
              Vector<Point> intersections (Circle c){...}
              ...
              }

- dorim sa avem metode pentru calculul intersectiei unei linii cu o alta linie sau cu un cerc. Rezultatul este un vector continand toate intersectiile gasite. Prin supraincarcare este posibil sa avem acelasi nume al metodei "intersections" pentru fiecare set de parametri de intrare, compilatorul stiind sa decida in mod corect, in functie de parametrii de apel, pe care dintre metode trebuie sa o apeleze. - atributele nu pot fi supraincarcate, ci doar metodele si constructorii. - supraincarcarea nu se poate face doar in functie de tipul returnat ci trebuie sa difere cel putin tipul unui parametru sau numarul parametrilor

g) Membri comuni tuturor instantelor unei clase

- uneori avem nevoie de un membru comun, care sa poata fi folosit din orice instanta a clasei respective, analog variabilelor globale din limbajele procedurale, dar pastrand avantajele incapsularii:

        class Car{
              static int n=0;
              Car(){++n;}
              }
        ...
        Car c1=new Car();
        Car c2=new Car();
        System.out.println("cars="+Car.n);

- atributul n este declarat static si atunci el apartine clasei, fiind comun pentru toate instantele ei. Din acest motiv fiecare incrementare a lui n in constructor se refera la aceeasi variabila comuna. In final programul va afisa cars=2. - membrii statici se apeleaza folosind "nume_clasa.membru_static" si nu "nume_instanta.membru_static".

h) Suprascriere de metode (override) si membri finali

- uneori este necesar sa modificam comportamentul unei metode a unei clase de baza intr-o clasa derivata din ea sau, dimpotriva, sa interzicem claselor derivate sa schimbe implementarea din clasa de baza:

        class Person{
              String name;
              final int MALE=1;
              final int FEMALE=2;
              int sex;
              final String getName{return name;}
              String title(){return sex==MALE?"Mr":"Mrs";}
              }

        class Engineer extends Person{
              String title(){return "Eng";}
              }

- atributele declarate final devin constante, ele nemai putand fi modificate ulterior. - metoda getName() fiind final nu mai poate nici ea sa fie reimplementata in descendentii clasei. - metoda title() a fost reimplementata in clasa Engineer pentru a reflecta noua situatie.

III. Reprezentarea cu ajutorul UML

IV. Teme

1. Implementati clasele, membrii si diagrama UML pentru urmatoarele cerinte: - este necesar sa modelam masini cu urmatoarele caracteristici: marca, tip, numar locuri, numar usi, motor, viteza, culoare - motorul poate fi diesel sau pe benzina - la motoarele pe benzina trebuie specificat daca este cu catalizator sau nu - un motor are putere si cuplu maxim - masinile pot fi de doua feluri: SUV si turisme. La SUV-uri mai avem: garda la sol si daca este 4x4.

2. Implementati clasele, membrii necesari si diagrama UML pentru urmatoarele cerinte: - exista mai multe accesorii de imbracaminte, specializate pe trei mari categorii: bijuterii, palarii, genti - bijuteriile pot fi impartite in cu sau fara surse de energie (ceasuri) - la bijuteriile cu sursa de energie ne intereseaza si cat tine bateria - la toate bijuteriile ne intereseaza culoarea dominanta si daca au sau nu pietre incrustate - la palarii avem nevoie sa stim pentru ce anotimp sunt destinate - gentile trebuie sa fie impartite in mici, medii si mari si daca sunt sau nu din piele naturala - toate accesoriile au pret