Lab 4

I. Cuplaj

- consideram urmatorul program:

      class C1{
            int i1,i2,a;
            public void f1(){
                  i1=f2(a);
            }

            public int f2(int a){
                  return a+a;
            }

            public int f3(int a1, int a2){
                  i2=f2(a1)+f2(a2);
                  f1();
                  i2=i1;
                  return a1+a2+i1;
            } 
      }

      class C2{
            int i1, i2;
            public C1 f1(boolean a){
                  C1 c=new C1();
                  c.i1=c.i2=1;
                  i1=a?c.f2(i1):c.f3(i1,i2);
                  return c;
            }

            public C2 f2(int a){
                  C1 c=f1(a<0);
                  c.i1=i1;
                  c.i2=i2;
                  c.f3(a,-a);
                  return this;
            } 
     }

- daca realizam diagrama de apeluri si de dependente de date pentru C1 si C2 obinem:

- cu nume_clasa.nume_membru s-a notat faptul ca este voba de membrul apartinand acelei clase sau a unei instante a acelei clase si nu faptul ca membrul este static

- cu linie continua s-a notat faptul ca o metoda apeleaza alta metoda (dependenta functionala)

- cu linie punctata s-a notat faptul ca o functie are nevoie de anumite variabile externe ei (dependenta de date)

- se poate remarca faptul ca unele dependente prin tranzitivitate, urmand un drum in graful orientat al dependentelor, pot sa duca la alte dependente, de exemplu: C2.f2 depinde de C2.f3 si C1.f3 depinde de C1.f1 => C2.f2 depinde indirect de C1.f1

- faptul ca mai multe functii depind si modifica aceleasi date face ca folosirea acestor functii sa fie foarte problematica, din cauza ca ele modifica starea contextului (clasa), si aceasta modificare nu este imediat vizibila doar din semnatura functiei, ci apare ca efect colateral ("side effect"), de exemplu: C1.f1 modifica pe C1.i1 si il foloseste pe C1.i2, iar C1.f3 il modifica pe C1.i2 si il foloseste pe C1.i1 si deci conteaza ordinea in care apelam C1.f1 si C1.f3 pentru a seta corect atributele C1.i1 si C1.i2

- acest mod de programare instituie un cuplaj strans atat intre clasele C1 si C2 cat si intre metodele aceleiasi clase

- in aceasta situatie este foarte greu sa modificam o anumita metoda sau un anumit tip de date, fiindca aproape inevitabil, modificarea va afecta multe alte locuri din program, fara ca aceasta sa fie intentia noastra, si de multe ori chiar fara sa ne dam seama. Totodata cuplajul strans are tendinta sa reflecte toate folosirile unor metode sau atribute in multe alte locuri, cu rezultate de cele mai multe ori imprevizibile si in acelasi timp greu de urmarit catre sau la originea lor

- o alta situatie in care apare cuplajul strans este atunci cand avem clase cu foarte multi membri in interfata lor. In aceasta situatie aproape inevitabil fiecare membru al acelei clase va depinde de mai multi membri

- de multe ori chiar adaugarea de noi functionalitati la un cod in care apare cuplajul strans este foarte grea, pe de-o parte din cauza ca ceea ce exista deja este de cele mai multe ori proiectat intr-un mod foarte particularizat in contextul vechiului program, iar pe de alta parte din cauza ca noile adaugiri sau modificari vor aduce de multe ori noi interactiuni (dependente) care ulterior prin tranzitivitate se vor reflecta in multe alte parti

Din motivele expuse mai sus reiese ca este foarte important, in special in cazul programelor mari, sau in cazul programelor care vor trebui extinse in viitor, sa implementam cerintele intr-un mod in care cuplajul intre diverse module ale programului sa fie cat mai slab. Daca reusim aceasta, vom beneficia de mai multe avantaje printre care:

- vom putea testa individual diverse componente ale programului fara sa trebuiasca sa tinem cont de o multime de interactiuni, unele dintre ele ascunse, cu alte parti din program

- vom putea relativ usor sa inlocuim/modificam implementarea unui algoritm cu o alta implementare mai performanta fara sa trebuiasca sa ne intrebam, in cazul in care interfata ramane aceeasi, daca nu cumva noile modificari vor afecta alte parti ale programului

- vom putea insera sau sterge relativ usor, acolo unde este nevoie, un alt modul intre alte doua deja existente, fara sa trebuiasca sa duplicam, in interiorul noului modul, cod din cele doua anterior alaturate, duplicare necesara doar pentru a mentine dependentele stranse dintre cele 2 initiale. Acest aspect este valabil si in cazul in care vrem sa introducem noi layere (straturi) de abstractizare intr-un program

- codul scris respectand principiul cuplajului slab, are tendinta sa fie mai generic si astfel poate fi folosit mai usor si in cadrul altor aplicatii

- intelegerea si mentenanta codului este mult mai usoara, aspect foarte important in cazul echipelor de programatori si in cazul in care codul trebuie modificat dupa un anumit timp

Pentru a obtine cuplajul slab in cazul unui program, exista mai mlte metode:

a) tinand cont ca actiunile au tendinta sa se modifice mai rar decat datele si in acelasi timp ca folosirea unor date trebuie urmata si de alte actiuni standard pentru acele date, este bine sa eliminam cat mai mult accesul direct la date, pe care in acest caz le vom folosi doar prin intermediul unor metode de interfata

b) vom evita clasele “mamut”, care contin foarte multi membri in interfata, urmarind sa implementam problema in termenii unor clase mai mici, fiecare cu o anumita functie bine stabilita

c) daca o clasa foloseste multe alte clase este bine sa introducem un layer de “subcontractare”, in asa fel incat clasa respectiva sa foloseasca serviciile unor mult mai putine clase si acestea la randul lor sa interactioneze cu acele multe clase din situatia initiala

d) functionalitatile comune este bine sa fie identificate si implementate sub forma unor interfete simple, in acest caz fiecare interfata implementand o anumita “trasatura” (trait). De exemplu o trasatura a unei instante a unei clase poate fi capacitatea ei de a fi comparata cu alta instanta a aceleiasi clase (Comparable) sau capacitatea ei de a contine colectii de obiecte care pot fi iterate (Iterable).

e) unde este posibil, este bine sa introducem un layer de abstractizare, care sa cuprinda intr-un mod mai general functionalitatea diverselor clase si sa gandim obiectivul in termenii acestui nou layer. Aceasta va asigura pe viitor capacitatea de decuplare si inlocuire usoara a unei implementari specifice a layerului de abstractizare, cu o alta implementare

II. Legea lui Demeter (LoD)

- LoD sugereaza anumite reguli pe care daca le respectam, codul rezultat va avea un cuplaj mai slab. Ea se adauga regulilor enumerate anterior si se prezinta in doua forme:

a) forma slaba: in interiorul unei metode M al unei clase C, datele pot fi accesate si mesajele pot fi transmise doar catre si folosind urmatoarele obiecte:

1. this si super

2. atributele clasei C

3. parametrii metodei M

4. obiectele create in interiorul lui M prin apelarea constructorului sau prin apelul unei metode care creaza obiectul

5. variabile globale

b) forma tare: in plus fata de forma slaba nu este permis accesul direct al membrilor mosteniti.

Metodele de interfata se pot apela oricand.

    class Point{
            double x,y;
            public Point(double x,double y){
                    this.x=x; // ok (din regulile LoD 2, 3)
                    this.y=y;// ok (2, 3)
            }
            public double mod(){...}

            public double getX(){...}

            public double setX(double x){...}
    }

    class Line{
            Point p1,p2;
            public Line(){
                    p1=new Point(0,0); // ok (2, 4)
                    p2=new Point(0,0); // ok (2, 4)
            }

            public double len(){...}

            ...

    }

    class SomeClass {
         public void doSomething() { 
             Line l=new Line(); 
             l.getP1(); // (4) ok
             show(l.getP1().getX()); //NU, accesare metoda a instantei unei alte clase adica Point
             show(l.getP2().getX()); //NU, accesare metoda a instantei unei alte clase adica Point
         }
    }

III. Teme

1. Sa se conceapa un algoritm de sortare care sa sorteze obiecte cat mai diverse, grupate in array-uri ([ ]), folosind o metoda de forma

      Sortare s= new Sortare();
      s.sorteaza(a,"nume");

In acest caz functia sorteaza va sorta obiectele din a dupa atributul lor "nume". Se vor folosi doar elementele expuse pana acum la laborator si nu alte abilitati ale limbajului Java gen "Reflection". Se va testa functionarea clasei Sortare pe minim doua clase diferite care sa formeze obiectele de sortat.

2. Sa se conceapa o clasa "Colectie" care folosind array-uri ([ ]), sa implementeze o colectie care poate sa contina oricate elemente. Elementele vor trebui sa implementeze o metoda care nu primeste niciun argument si care returneaza true daca obiectul mai este necesar. Nu este important motivul pentru care un obiect nu mai este necesar. Clasa "Colectie" va implementa trei metode:

adauga - adauga un nou obiect in colectie

afiseaza - afiseaza toate obiectele din colectie

compacteaza - elimina din colectie obiectele care nu mai sunt necesare

Programul de test va folosi colectii formate din cel putin instante a doua clase distincte si va testa toate cele trei metode cerute.