rss
    System.out.println("Hello World");

vineri, 12 noiembrie 2010

despre interceptie si contraceptie cu java

De ceva vreme, bunul meu prieten, fost coleg (dar tot bun) si discipol (evident bun), m-a invitat sa scriu pe acest blog despre subiectele cu care ma confront in viata mea de zi cu zi de afacerist in lumea software.
Cum blogul sau este despre java, o sa ii pastrez tipicul si ma voi referi numai la subiecte java, sau care au legatura cu java.
O sa ma scuzati daca uneori calc pe alaturi si mai vorbesc despre subiecte cum ar fi, arhitecturi, business, data access, marketing, management, dar promit ca toate exemplele sa fie din java.
Probabil ca daca va veti exprima avantul de cunoastere, voi face un site cu tenta mai generala, denumit software_design sau cum_sa_iti_prajesti_singur_cartofi.
Pentru a nu ii face pe cei riguros professional sa strambe din nas la glumele mele (nu e asa ca fac glume bune?), voi trece abrupt si direct in subiectul de azi.

Context

Sunt de ce in ce mai nervat de cod precum:
Public void metoda(tip parametru)
{
Try
{
If(parametru==null) loger.log(“mesaju”);
If(parametru…) loger.log(“mesaju”);
…..(aici e alt cod)
If(parametru…) loger.log(“alt mesaj”);
….(alt cod)
}
Catch(Exception ex)
{
Loger.log(ex.getMessage());
}
}

Nu vi se pare enervanta imixtiunea asta a tot felul de alte bucati de cod, in codul in care dumneavoastra doriti sa faceti ceva?
Pe mine ma enerveaza spre exemplu cand am de scris o metoda de calcul a dobanzilor trimestriale, cu aplicare zilnica si cu retragere in momentul stricarii unui deposit, sa tot stau sa ma gandesc la tot felul de alte coduri, cum ar fi cel de verificare, cel de exceptii, cel de alta natura decat cel pe care il am de scris.
Eu vreau ca atunci cand scriu o metoda de calcul a dobanzilor, sa scriu despre dobanzi si atit.
Un alt lucru important,
Avem o bucata de cod, compilat, nu avem codul sursa.
Codul ala are o interfata publica si atit.
Dar codul ala nu face exact ce vreau eu, iar ceea ce mi-ar trebui mi ear trebui sa se intimple inainte ca o metoda din interfata publica sa fie chemata, sau atunci cand e pe punctul sa fie chemata, sau imediat dupa ce a terminat de lucrat, dar pana cand se ajunge in codul care a chemat-o.
Spre exemplu, am o metoda de calcul a preturilor, dar respectiva metoda e intr-o clasa unde eu nu pot sa intervin, dar totusi mie mi-ar trebui sa aplic la calcul pretului niste procentaje de penalizare.
Ei bine, veti zice, ce mare lucru, iti calculezi ce valori vrei, dai ca parametru metodei si cu rezultatul intors mai faci niste calcule si gata.
Desigur, merge si asa si asa va face orice programator care nu a depasit pragul programatorului de liceu.

Ce variante avem:

Ca sa ridicam putin nivelul de lucru, sa trecem de la liceu la facultate, prezentam urmatoarele posibilitati.
O prima posibilitate este patern-ul decorator, cine il stie il stie, cine nu aia e, inseamna ca postarea asta nu se ridica inca la nivelul vostru.
Prin acest patern, extindem clasa pe care o dorim, detinem si o copie a clasei respective inauntru si scriem metode de inlocuire pentru toate metodelepe care dorim sa le alteram.
O alta solutie este oferita de patern-ul adapter (din nou, cine nu il stie, la carte si cititi despre).
Acest patern decupleaza patern-ul decorator in doua paterne diferite.
Paternul nr 1, adapter prin extindere. Adica cu clasa noastra extindem clasa ce dorim sa ii alteram comportamentul.
Patern-ul nr. 2, adapter prin compozitie, adica creiem o noua clasa, care va fi un wrapper pentru clasa ce dorim sa o modificam si scriem metode de inlocuire pentru cele care dorim sa le alteram de la clasa initiala.
De ce am avea nevoie de un adapter prin compozitie daca tot il avem pe cel de extindere?
Asta e un alt subiect interesant si este strans legat de principiul inlocuirii al lui Liskov, despre care, daca veti dori, vom avea o alta discutie.

Dar unde este Java?

Aveti dreptate, am palavragit pana acum fara sa vedem putin cod java.
O solutie si mai interesanta, s-ar putea obtine folosind programare orientata pe aspecte (intr-un post urmator vorbim despre asta).
In principiu aceasta paradigma introduce notiunile de punct de join si punct de taiere.
Si poate interveni in momentele enumerate mai sus legate de metoda.
Un alt patern care ne poate oferi o solutie si care ne adduce mai aproape de programarea orientata pe aspecte, este patern-ul Proxy (cred ca deja o sa vreti sa va vorbesc despre fiecare, ca v-am enumerat mai multe, nu e nimic, avem loc de scris in posturi urmatoare).
Imprumutand putin din patern-ul proxy, putin din programarea orientata pe aspecte, java ne pune la dispozitie o caracteristica foarte interesanta pentru a rezolva aceasta problema.
Sa vedem cum functioneaza.
Tot odata, va voi prezenta ceea ce urmeaza ca un principiu de urmat, atunci cand va scrieti propriul cod.

Folositi puncte de interceptie pentru a adauga optimizari, particularizari sau functionalitati noi

Incepand cu java 1.3, java permite ceea ce se cheama proxy dynamic, permitand programatorilor sa scrie un proxy dynamic care implementeaza o interfata pentru un obiect classic care implementeaza aceeasi interfata (intr-un fel ca la decorator).
Ca sa ne exprimam in romana, proxy dynamic permite programatorului sa scrie cod de interceptare in jurul unui obiect normal, atit timp cat obiectul respectiv este apelat/folosit printr-o interfata.
Sa ne intoarcem putin la exemplul cu logerul si sa vedem cum putem adauga mecanismul de loging fara a schimba tot codul.
In loc sa apelam cine stie ce jiumbuslucuri de cod, mai de graba folosim un mechanism clasa I! Si anume mecanismul de proxy dynamic pus in fata fiecarui obiect pe care il dorim cu mechanism de loging, avand grija ca acest obiect sa fie folosit printr-o interfata:

public class LoggingProxy

implements InvocationHandler

{

public static Object

newLoggingProxyAround(Logger logger, Object obj)

{

return Proxy.newProxyInstance(

obj.getClass().getClassLoader(),

obj.getClass().getInterfaces(),

new LoggingProxy(logger, obj));

}



private Object proxiedObject;

private Logger logger;



private LoggingProxy(Logger l, Object obj)

{

logger = l;

proxiedObject = obj;

}



public Object invoke(Object proxy,

Method m, Object[] args)

throws Throwable

{

Object result;

try

{

l.info("Entering method " + m.getName());

result = m.invoke(proxiedObject, args);

}

catch (Exception x)

{

l.warn("Unexpected exception " + x + " invoking "

+ m.getName());

throw x;

}

finally

{

l.info("Exiting method " + m.getName());

}

return result;

}

}


Un logingproxy se va comporta ca un wrapper pentru fiecare obiect in jurul caruia este construit (Utilizand metoda Factory newLoggingProxyAround) si va scrie mesaje de log pentru fiecare chemare de metoda care va veni prin interfata respectiva.
Il vom utiliza dupa cum urmeaza:


public interface Person

{

public String getFirstName();

public void setFirstName(String value);

public String getLastName();

public void setLastName(String value);

public int getAge();

public void setAge(int value);

}


public class PersonImpl

implements Person

{

//alte detalii

private String fname;

private String lname;

private int age;

public String getFirstName() { return fname; }

public void setFirstName(String value) { fname = value; }

public String getLastName() { return lname; }

public void setLastName(String value) { lname = value; }



public int getAge() { return age; }

public void setAge(int value) { age = value; }

}

public class PersonManager

{

public Logger personLogger = ...; //o instanta a unui logger

public boolean loggingEnabled = false;

// Set pe true pentru a porni logging



public static Person

getPerson(String firstName, String lastName)

{

Person p = new PersonImpl();

// Obtinerea persoanei de undeva; intr-un system real

// persoanele vor fi intr-o baza de date sau undeva

// asemanator

//



if (loggingEnabled)

p = (Person)

LoggingProxy.newLoggingProxyAround(personLogger, p);



return p;

}

}


Dupa cum ati vazut, pentru a construe o persoana, folosim metoda statica Factory getPerson.
Aceasta primeste datele necesare despre o persoana, se uita in baza de date, sau unde se afla persoanele si obtine toate datele necesare,
Apoi construieste o instanta a clasei concrete.
Utilizam aceasta metoda statica, pentru a introduce un pas in plus in creierea persoanei.
Amintiti-va, tot ce am dorit a fost sa introducem loging inauntru obiectelor respective.
Deoarece nu putem, sau mai de graba nu vrem sa modificam bytecode-le existent, am emis doar cate un mesaj la inceputul si sfarsitul apelului unei metode.
Deci vom prezenta un obiect interceptor inaintea obiectului propriuzis; Interceptorul va avea o referinta catre PersonImpl si va trimite mai departe catre PersonImpl toate apelurile de metoda pe care le va primi, dar numai dupa ce va afisa un mesaj de log.
Frumusetea acestei abordari este ca, indifferent de tipul obiectului pentru care implementam loging, interceptia metodelor functioneaza.
Cheia este ca proxy-ul are nevoie, pentru a trimite apelurile catre obiectul real (PersonImpl in exemplul de fata), sa treaca printr-o interfata mai de graba decat printr-o clasa concreta.
De aceea PersonManager returneaza Person si nu PersonImpl.
Mai mult, nu exista nici-o relatie de structura la momentul compilarii intre Person si actul de logging.
In schimb, logging-ul poate fi pornit sau oprit, prin simpla modificare a variabilei boolene LogingEnabled, care poate fi un element in configurarea aplicatiei.
Construind componente in loc de obiecte, vom putea folosi interceptorii pentru a introduce in ce mod dorim comportamentul necesar, fara sa schimbam codul specific pentru un domeniu, sau codul de interfata cu clientii.
Folositi interfete pentru a defini ce vor vedea clientii, folositi metode statice Factory pentru constructia de obiecte, ascundeti implementarea reala a unui obiect in spatele unuia sau mai multor proxy-uri dinamice care sa implementeze functionalitatea transversala dorita.
Ca bonus, am obtinut (gratis) “cuplare slaba” intre componente, deoarece implementarea reala a obiectelor se poate schimba nestingherit, fara ca clientii sa stie sau sa le pese de asta.

Va multumesc mult pentru ca ati avut rabdare pana la final,
Astept opiniile voastre si sugestii despre ce ati dori sa vedeti publicat.
Daca aveti probleme pe care nu puteti sa le rezolvati, sau ati dori sa vedeti solutia noastra, le asteptam.
Mentionez ca pentru acest material am utilizat (pentru reamintiri exacte) cartea
Effective Enterprise Java
By Ted Neward
-
Publisher-: Addison Wesley Professional
Pub Date-: August 26, 2004
ISBN-: 0-321-13000-6
Pages-: 496

Niciun comentariu:

Trimiteți un comentariu

Va rugam lasati un comentariu!