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:
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; } } }
- 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:
- 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:
- 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:
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