Laborator 3

I. DESIGN BY CONTRACT

- reprezinta un "contract" care specifica restrictiile la care trebuie sa se supuna datele de intrare ale unei metode, valorile posibile de iesire si starile in care se poate afla programul - aceste restrictii sunt date sub forma unor:

a) preconditii: reprezinta obligatiile pe care datele de intrare ale unei metode trebuie sa le respecte pentru ca metoda sa functioneze corect
b) postconditii: reprezinta garantiile pe care datele de iesire ale unei metode le ofera
c) invarianti: reprezinta conditii impuse starilor in care programul se poate afla la un moment dat
                class Treasury{
                   double amount=0;

                   void add(double sum){
                       amount+=sum;
                       }

                   double getAmount(){
                       return amount;
                       }

                   boolean payment(double sum){
                       if(sum<=amount){
                           amount-=sum;
                           return true;
                           }else{
                           return false;
                           }
                       }
                   }
Treasury:
- invarianti: - amount>=0 si amount diferit de infinit si de NaN
add:
- preconditii: - sum>=0 si amount diferit de infinit si de NaN
getAmount:
- postconditii: - valoarea returnata >=0, si amount diferit de infinit si de NaN
payment:
- preconditii: - sum>=0, si amount diferit de infinit si de NaN

- conditiile de mai sus reprezinta "contractul" pe care clasa Treasury si metodele ei il ofera si in acelasi timp il necesita de la cei care o folosesc - payment nu are postconditii fiindca este garantata sa functioneze pentru orice data valida de intrare, luand toate valorile logice posibile - uneori, pe langa preconditii, postconditii, invarianti, in contractul unei clase sau al unei metode mai pot aparea si alte conditii specifice, de exemplu conditii care garanteaza timpii de executie, scalabilitatea algoritmului in functie de datele de intrare, memoria folosita, etc. In cazul nostru se poate garanta, de exemplu ca metodele clasei Treasury au complexitatea O(ct) si ca nu emit niciodata exceptii - specificarea contractului unei clase sau al unei metode este foarte importanta din mai multe puncte de vedere:

a) cel care a implementat clasa su metoda restectiva ii spune utilizatorului ce date sunt considerate valide ca date de intrare. Aceasta il scuteste sa foloseasca teste de validare a datelor, care in unele cazuri doar ar incetini algoritmul, ele fiind redundante cu teste care deja sunt facute pentru validarea datelor, de exemplu imediat ce au fost introduse de catre utilizator. In cazul de mai sus, creatorul clasei Treasury specifica faptul ca intotdeauna metodele add si payment se asteapta sa primeasca valori non-negative si atunci nu mai este necesar ca aceste metode sa implementeze validari de date. Daca utilizatorul foloseste clasa Treasury cu date incorecte de intrare (sum<0), el nu respecta "contractul" clasei si atunci rezultatele nu mai sunt garantate ca fiind corecte
b) in acelasi timp utilizatorul stie din contract la ce date posibile de iesire sau stari intermediare sa se astepte si atunci poate sa-si optimizeze propriul cod in functie de acestea

- contractul unei clase sau al unei metode se specifica literar, printr-un text, inclus in program ca un comentariu sau scris in documentatia aferenta - in faza de dezvoltare a unui program se pot folosi instructiuni care sa testeze indeplinirea contractului, instructiuni care sa fie scoase pe urma (manual sau automat) din codul final

                void add(double sum)throws Exception{
                    if(sum<0)throw new Exception("sum is negative");
                    amount+=sum;                   
                    }

- in acest caz metoda raporteaza incalcarea preconditiei prin emiterea unei exceptii. In faza finala, la predare, acest test se poate comenta sau sterge pentru a nu ocupa timp procesor si memorie. - Java ofera si un mecanism special pentru testarea unor conditii in faza de dezvoltare, prin folosirea asertiunilor

                void add(double sum){
                    assert sum>=0:"sum is negative";
                    amount+=sum;
                    }

- o asertiune genereaza o exceptie speciala cu textul dat dupa ":", in cazul in care conditia ei este falsa. Deoarece asertiunile au fost introduse mai tarziu in limbajul Java trebuie specificat la optiunile de compilare: -source 1.4 - implicit asertiunile nu fac nimic (sunt dezactivate), ca si cum codul este pregatit pentru livrare. Daca dorim sa activam asertiunile trebuie sa specificam la optiuni: -enableassertions (sau -ea)

II. LISKOV SUBSTITUTION PRINCIPLE (LSP)

                abstract class Computer{
                    abstract String getOpticUnitType();
                    }

                class Desktop extends Computer{
                    String opticUnitType;
                    ...
                    String getOpticUnitType(){
                    return opticUnitType;
                    }
                }

                class Tablet extends Computer{
                    String getOpticUnitType()throws Exception{
                    throw new Exception("No unit");   
                    }
                }

- concluzia este ca intr-o superclasa nu putem avea definite decat acele proprietati care sunt intr-adevar valabile pentru toate subclasele ei si care au un comportament unitar - cea mai simpla metoda de a verifica acest principiu este sa testam in orice secventa de cod in care se foloseste o anumita clasa derivata din superclasa, ca acea clasa se poate substitui cu orice alta clasa derivata din aceeasi superclasa - LSP spune exact acest lucru:

! mostenirea trebuie sa asigure faptul ca orice proprietate valabila pentru superclasa, de asemenea se mentine pentru oricare din subclasele ei

- acest principiu spune ca relatia de baza care exista intre o subclasa si superclasa ei, si anume "IS A", trebuie inteleasa si intr-un sens comportamental ("behavior") si nu doar unul structural ("structure") - un alt corolar al acestui principiu, folosit in conjunctie cu design by contract, este urmatorul:

! cand se redefineste o metoda intr-o clasa derivata, preconditiile ei se pot inlocui doar cu unele mai slabe, iar postconditiile ei, doar cu unele mai puternice
                abstract class S{
                    //preconditii: n1,n2 - numere pozitive
                    //postconditii: rezultatul este numar pozitiv
                    abstract f(int n1,int n2);
                    }

                class D1 extends S{
                    //preconditii: n1,n2 - orice numere
                    //postconditii: rezultatul este numar pozitiv par
                   int f(int n1,int n2){...}
                   }

                class D2 extends S{
                    //preconditii: n1,n2-numere pozitive
                    //postconditii: rezultatul este orice numar
                   int f(int n1,int n2){...}
                   }

- clasa D1 respecta LSP fiindca la implementarea functiei f preconditiile sunt mai slabe decat cele din contractul superclasei S, ceea ce garanteaza ca orice set de date valabil pentru S va fi valabil si pentru D1, si in acelasi timp postconditiile lui f din D1 sunt mai tari decat cele cerute in S, ceea ce garanteaza ca orice rezultat pe care f din D1 il returneaza este o submultime valida a rezultatelor pe care prin contract f din S trebuie sa le garanteze - D2 nu respecta LSP fiindca implementarea lui f din D2 are postconditii mai slabe decat cele cerute in S si atunci pot sa rezulte cazuri in care folosirea unei instante a lui D2, desi este corecta in sine conform propriului contract, nu respecta contractul contextului general din care ea face parte (contractul lui S) - un alt aspect important cerut de LSP este ca in clasele derivate nu putem implementa o metoda din superclasa ca avand functionalitate nula (NOP-no operation). De exemplu ar fi incorect in clasa Tablet sa implementam functia getOpticUnitType la modul:

                String getOpticUnitType(){
                    return null;
                    }

- revenind la exemplul initial, o implementare corecta ar fi una in care metoda getOpticunitType ori sa fie implementata intr-un layer separat de abstractizare, sau sa fie declarata intr-o interfata care sa fie implementata doar acolo unde este nevoie de ea:

                abstract class Computer{}

                interface OpticUnit{
                    String getOpticUnitType();
                    }

                class Desktop extends Computer implements OpticUnit{
                    String getOpticUnitType(){...}
                    }

                class Tablet extends Computer{}

III. TEMA

1. Se cere un scurt program de test (main), diagrama UML si contractele pentru clasele (incluzand implementarea metodelor lor), care indeplinesc urmatoarele conditii: - se doreste calculul derivatei pentru mai multe tipuri de functii: polinomiala (cnxn+cn-1xn-1+...+c1x+c0), exponentiala(cx), logaritmica(logcx). - functia care calculeaza derivata va avea forma: double[] derivata(double[] a); unde a este un array care contine toate constantele necesare functiei de derivat (de la stanga la dreapta), si returneaza un alt array cu constantele functiei derivate - functia derivata() este definita intr-o clasa abstracta Functie din care se vor deriva clase specifice fiecarui tip de functie

2. Se cere un scurt program de test (main), diagrama UML si contractele pentru clasele (incluzand implementarea metodelor lor), care indeplinesc urmatoarele conditii: - se doreste calculul unghiului de refractie al unei raze de lumina cu o functie de forma double unghiRefractie(double ui,double ce); unde ui este unghiul de intrare al razei de lumina, iar ce este constanta de refractie a mediului exterior - functia va returna unghiul razei in mediul interior definit in clasa, iar daca se iese din domeniul unghiurilor de refractie, se returneaza infinit. - avem 3 tipuri de corpuri: solid, lichid si gazos - la corpurile gazoase indicele de refractie variaza direct proportional dupa o constanta data cu compresia acelui gaz: ci=compresie*ci0 - ci este constanta interna de refractie a mediului respectiv - compresie este gradul de compresie - ci0 este constanta interna de refractie a mediului pentru grad de compresie 1 - unele corpuri solide sunt opace si pentru acestea nu exista refractie - clasa de baza se numeste Corp