Lab 6

I. VISITOR

Consideram ca avem de proiectat un compilator pentru un limbaj simplu de programare, care compileaza codul sursa intr-o reprezentare intermediara (AST- Abstract Syntax Tree), din care ulterior se va genera cod in mai multe limbaje de programare: C, Java, etc Ex: pentru codul sursa:

for i=0 to 10 do show (i*i) end

va rezulta reprezentarea intermediara:

iar codul final generat in C:

        void main(){
            int i;
            for(i=0;i<=10;i++){
                show(i*i);
            }
        }

- reprezentarea intermediara este o ierarhie de clase, pornind de la o clasa abstracta Node, care defineste clase pentru toate constructiile valide in limbajul de programare dat

- chiar si pentru limbaje de programare simple, aceasta ierarhie de clase poate fi destul de complexa, trebuind sa existe clase pentru: fiecare operatie matematica, definiri de functii, clase, variabile, constructii de limbaj, etc

- intr-o prima implementare putem sa generam codul final folosind o functie generate(), care sa fie implementata specific in fiecare clasa si care sa genereze codul final corespunzator acelui nod din AST

- deoarece in problema trebuie sa generam cod pentru mai multe limbaje de programare, rezulta ca vor fi in aceasta implementare necesare mai multe metode generate(), cate una pentru fiecare limbaj (ex: generateC, generateJava, etc)

- dezavantajele acestei implementari sunt:

a) daca mai adaugam inca un limbaj destinatie (de exemplu C#), va trebui sa modificam in toata ierarhia de clase, adaugand peste tot metoda generateCS

b) metodele generate, fiind implementate direct in clasele lor, exista tendinta de a folosi in cadrul lor si unele detalii interne ale claselor respective, aspecte care se pot modifica mai des si care realizeaza o cuplare stransa intre partea de reprezentare a codului intermediar si partea de generare de cod

c) daca nu avem acces la sursele claselor reprezentarii interne (de exemplu daca modulul respectiv a fost realizat de altcineva si din cand in cand trebuie resincronizat cu versiuni noi), atunci nu vom putea sa modificam clasele respective pentru a adauga o noua metoda de generare

- pentru a evita deficientele implementarii de mai sus, vom implementa generarea de cod in felul urmator:

a) construim o ierarhie de clase care se vor ocupa, fiecare dintre ele, cu generarea codului intr-un limbaj de programare specific, ierarhie care are ca superclasa clasa Generator

b) in clasa Generator vom avea cate o metoda abstracta de generare a secventei de cod specifica fiecarui tip de nod din AST. Implementarile acestor metode vor fi realizate specific pentru fiecare limbaj de generare

c) la nivelul lui Node vom avea o unica metoda generate(Generator g) care, implementata pentru fiecare nod din AST, va apela metoda corespunzatoare de generare a acelui nod din Generator. Apelarea metodelor generate din Generator se va face pasand ca parametru chiar obiectul curent al metodei generate, pentru a sti pentru cine se genereaza codul

- in cazul nostru ierarhia de generatori va fi:

        abstract class Generator{
            public abstract void genSequence(Sequence s);
            public abstract void genFor(For f);
            public abstract void genCt(Ct c);
            public abstract void genCall(Call c);
            public abstract void genMultiply(Multiply m);
        }

        class GeneratorC extends Generator{
            ...
            public void genCt(Ct c){
                ...
                finalText.add(c.value);
                ...
            }

            public void genMultiply(Multiply m){
                finalText.add("(");
                m.left.generate(this);
                finalText.add(")*(");
                m.right.generate (this);
                finalText.add(")");
            }
            ...
        }

        class generatorJava extends Generator{...}
        ...

- in clasa Node vom avea:

        public abstract void generate (Generator g);

- ca exemplu, in clasa Ct vom avea:

        ...
        pubcli void generate(Generator g){
            g.genCt(this);
        }

- daca avem un AST (Node root) si un generator oarecare (GeneratorC genC), vom genera codul dorit prin: root.generate(genC);

- prin aceasta a 2-a implementare, obtinem urmatoarele avantaje:

a) daca mai dorim sa adaugam noi limbaje de programare pentru codul generat, nu mai trebuie sa facem nicio modificare in ierarhia de clase corespunzatoare reprezentarii intermediare si nici in generatorii existenti. Astfel nu mai este necesar nici macar sa avem acces la codul sursa al ierarhiilor respective

b) deoarece metodele gen... sunt implementate in alte clase decat cele corespunzatoare reprezentarii intermediare, vom fi obligati sa folosim doar metodele de interfata ale acestor clase, realizand decuplarea intre partea de reprezentare a codului intermediar si partea de generare de cod

- aceasta modalitate de abordare reprezinta pattern-ul Visitor

- si aceasta abordare are unele deficiente, dintre care una importanta este faptul ca daca modificam ierarhia de clase corespunzatoare reprezentarii intermediare, va trebui sa modificam (sa adaugam sau sa scoatem) din toti generatorii existenti metodele gen... . Din acest motiv Visitor-ul se aplica cu succes in cazul ierarhiilor de clase relativ stabile

- exista mai multe metode de folosire a acestui pattern, printre altele si in functie de cum se face traversarea colectiei de obiecte. In cazul nostru metodele generatorului sunt responsabile cu logica de traversare a colectiei de obiecte. Alte metode de traversare ar fi sa avem un iterator extern care sa parcurga colectia, apeland pentru fiecare obiect din ea metoda generate, sau obiectele colectiei sa fie responsabile cu logica de traversare

II. ACYCLIC VISITOR

Consideram cazul unui hotel care are diversi angajati, cum ar fi soferi, menajere si bucatari. In extrasezon nu este nevoie de soferi, iar orarul de munca si alte insarcinari auxiliare pentru fiecare angajat sunt diferite de cele din sezon. Dorim sa implementam o solutie in care avand o lista de angajati si de cerinte specifice sezonului sau extrasezonului, sa putem da de lucru angajatilor.

Daca folosim Visitor, putem implementa angajatii ca fiind clasele vizitate, iar Perioada ca fiind clasa vizitator, in care vom implementa toate metodele necesare diversilor angajati. Cand vom deriva clasa Perioada in Sezon si Extrasezon, vom implementa metodele necesare (de exemplu la extrasezon nu avem "indeplineste(Sofer s)"), urmand ca la apelul celorlalte sa emitem o exceptie. Daca avem multe tipuri de angajati si multe sarcini, rezulta ca va trebui sa implementam metode care nu vor fi folosite niciodata, deci nu este o solutie eleganta. Totodata, daca vom adauga un nou tip de angajat, va trebui sa rescriem toate clasele vizitator, pentru a adauga noile metode.

Pentru a remedia aceste lipsuri, vom folosi o solutie usor diferita, astfel:

- interfata de baza vizitator (Sarcina) va fi goala (degenerata)

- pentru fiecare tip de angajat, pe langa clasa lui specifica din ierarhia de vizitat (care are ca baza Angajat), vom deriva din Sarcina o interfata cu o singura metoda "indeplineste", care are ca parametru un angajat de acel tip

- din Sarcina (clasa vizitator) vom deriva si clasa de baza pentru perioade

Metodele vor fi de genul:

      class Sofer extends Angajat{
          ...
          public void primeste(Sarcina s){
            if(s instanceof Sofat){
                Sofat sofat=(Sofat)s;
                sofat.indeplineste(this);
            }
        }
      }

      abstract class Perioada implements Sarcina{
          ...
          public void daDeLucru(Angajat a){
            a.primeste(this);
          }
      }

      class Sezon extends Perioada implements Sofat,Gatit,Menaj{
          ...
          public void indeplineste(Sofer s){
            System.out.println("sofat in sezon");
          }
      }

Pentru a da de lucru unui angajat "a", vom apela metoda daDeLucru(a). In aceasta metoda, la apelul "a.primeste(this)", pasam angajatului respectiv Perioada (care deriva din Sarcina), pentru munca pe care o are de indeplinit. Deoarece fiecare Perioada implementeaza doar interfetele angajatilor care muncesc in acea perioada, testarea cu instanceof este valabila doar pentru perioadele in care interfata dorita de acel angajat e valabila. In acest caz, convertim Perioada la interfata specifica, si apelam metoda "indeplineste" specifica interfetei, pasand chiar angajatul respectiv ca parametru. In final, vom ajunge astfel la implementarea specifica a metodelor "indeplineste". In aceasta situatie este permisa folosirea instanceof, pentru ca este folosit doar pentru testarea unei clase specifice si nu pentru selectia intre diverse clase. Altfel, daca am fi facut conversie directa, ar fi fost nevoie sa includem conversia intrun bloc try..catch, in caz ca obiectul respectiv nu ar fi fost derivat din interfata dorita.

Resource:

http://www.objectmentor.com/resources/articles/acv.pdf

III. Tema

Un program de desen vectorial are urmatoarele primitive grafice: - linie, definita prin coordonatele capetelor - cerc, definit prin coordonatele centrului si raza - dreptunghi, definit prin coordonatele a 2 puncte opuse

Pentru acest program trebuie implementat modulul de salvare a desenului. Salvarea se poate face in doua formate:

a) XML:

    <desen>
        <linie x1="7" y1="10" x2="50" y2="20"/>
        <cerc x="90" y="80" r="10"/>
        <dreptunghi x1="2" y1="5" x2="20" y2="50"/>
        ...
    </desen>

b) JSON:

    [
        {tip: "linie", x1:7, y1:10, x2:50, y2:20},
        {tip: "cerc", x:90, y:80, r:10},
        {tip: "dreptunghi", x1:2, y1:5, x2:20, y2:50}
        ...
    ]

Se cere:

- ierarhia de clase si diagrama UML

- considerand ca procesul de salvare afiseaza datele pe ecran, sa se scrie un program de test care "salveaza" baza de date in ambele formate