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

luni, 22 noiembrie 2010

Constrangeri, procese si specificatii in java: Partea I Constrangeri si procese

Salutare extinsa tuturor!

Inainte de a incepe postarea de azi, va atrag atentia asupra unui fapt. Ati observat pe parcursul postarilor anterioare, diverse subiecte despre care spunem ca vom vorbi in postari ulterioare.
Ei bine, publicam pe moment ce ne intilnim cu ele si va facem prezentarea unui caz concret.
Insa, daca vreunul dintre voi doreste tratarea unuia din subiectele enumerate, sau a altui subiect, nu are decat sa comenteze si vom extrage din experienta noastra episoade importante despre subiectul respectiv.

Revenim la postarea de azi.
Lucram impreuna cu prietenul meu si gazda noastra pe acest blog la un program cand am fost loviti de o revelatie:
- Bai, tu vezi cate if-uri sunt prin bucatile astea de cod?
- Da ma, chiar ma gindeam sa vad cum naiba sa le rescriem sa nu mai fie atitea, sau ma rog, sa nu mai aiba atita cod.

Care e problema?

Problema este ca adesea ne intilnim cu situatii in care trebuie sa ne asiguram ca o anumita caracteristica a unui obiect este respectata, drept pentru care trebuie sa o controlam mereu sa vedem respectarea ei. In Object Oriented Programming asta se cheama realizarea de invarianti asupra unui produs si impunerea de constrangeri.
Deci unul din subiectele despre care vom vorbi azi sunt constrangerile. Nu plecati, nu e singurul!
Invariantul este o caracteristica a unui obiect care nu se poate schimba. Constrangerea este regula impusa ca invariantul sa fie respectat.

Unde v-ati intalnit cu astea?

Foarte des, dar mai precis intr-un soft de comenzi si facturi pe care il dezvoltam impreuna, de aceea exemplele vor fi din acest soft.
Vom vedea acum un caz simplu. Sa presupunem ca aplicatia noastra realizeaza si impachetarea marfii.
Avem un container cu 100 kg capacitate. Deci asta, 100 kg, e o caracteristica care nu poate fi schimbata, atita poate containerul.
Ajungem la concluzia ca pentru container capacitatea este un invariant si ca genereaza o constrangere.
Asta inseamna ca in fiecare metoda care are de a face cu capacitatea, trebuie sa ne asiguram ca nu depasim respectiva capacitate.
Multi dintre voi ar face asa:

class Container {
private float capacity;
private float contents;

public void pourIn(float addedVolume) {
if (contents + addedVolume > capacity) {
contents = capacity;
} else {
contents = contents + addedVolume;
}
}
}

Cazul de fata este unul simplu, dar ganditi-va ce ar fi daca metodele acestui obiect ar fi mai complexe si mai multe.
Ce va faceti daca verificarea invariantului se schimba nitel, va trebui sa modificati in fiecare metoda in care el exista.
Dar nu despre multiplicare vorbim azi, lucrurile prezentate au o aplicabilitate mult mai mare si mai interesanta.
Mai mult, daca verificarea constrangerii se modifica si devine din ce in ce mai complexa, atunci creste si metoda care trebuie sa se asigure de aceasta constrangere, dar fara ca functionalitatea ei de baza sa se fi modificat.

Cum ar arata o varianta mai interesanta:

class Container {
private float capacity;
private float contents;
public void pourIn(float addedVolume) {
float volumePresent = contents + addedVolume;
contents = constrainedToCapacity(volumePresent);
}

private float constrainedToCapacity(float volumePlacedIn) {
if (volumePlacedIn > capacity) return capacity;
return volumePlacedIn;
}
}


In acest mod, Constrangerea poate creste cat doreste, pentru ca functia care are nevoie de constrangere ramane la fel de simpla.
Mai mult, prin aceasta varianta, respectam un alt principiu important, „reveal the intention”.
Astfel cine va citi codul va stii exact ce bucata de cod la ce foloseste si mai mult, cei ce vor utiliza clasa, vor stii ca exista aceste constrangeri care trebuie folosite.
Daca lasam bucatile de cot azvarlite prinmetode, riscam ca lumea sa nu inteleaga despre ce e vorba, mai ales daca codul nostru era imbricat si cu alte verificari, sau chiar cod de realizare al functiei respective.

Este aceasta metoda cea mai buna?

Chiar daca acum am separat cele doua bucati de cod, chiar daca avem o noua metoda deci un nou concept despre care putem discuta,
Chiar daca acum constrangerea se poate modifica independent de codul care o foloseste, exista totusi niste situatii cand nu e de ajuns acest pas si trebuie facut inca unul in plus.
Iata cateva semne dupa care sa recunoasteti aceste situatii:
- Evaluarea unei constrangeri necesita date care nu se afla si nu au ce sa caute in definitia obiectului;
- Constrangeri care se afla intr-o relatie, se afla raspandite prin mai multe obiecte, lucru care forteaza duplicarea codului, sau mostenirea inutila intre obiecte care altfel nu formeaza o familie;
- O multime de discutii se poarta pe marginea conceptului reprezentat de o constrangere, dar aceste constrangeri nu sunt explicite in cod, ele fiind adanc ascunse in codul procedural.

Ca si regula generala:
Cand constrangerile ascund functionalitatea reala a obiectului, sau cand constrangerea este prezenta ind discutiile de specialitate, dar nu e prezenta in proiectarea codului nostru, trebuie sa construiti constrangerea ca un obiect separat, sau ca un set de obiecte si relatii intre acestea.

Despre procese!

Un alt aspect important pe care l-am depistat, alaturi de constrangeri si, si, o sa vedeti voi, sunt procesele.
Avem doua tipuri de procese, procese specifice domeniului si procese computationale.
Cele specifice domeniului sunt acele procese despre care utilizatorii softului creat de dumneavoastra vorbesc si ele sunt specifice domeniului in care softul dumneavoastra va actiona.
Procedura de inchiriere a unui bun sau serviciu, modalitatea de calcul a salariilor etc, sunt toate procese de domeniu.
Un proces computational, este un proces care tine de mecanismele de a realiza anumite activitati cu ajutorul unui computer.

Care e problema?

Problema identificata de noi, in softul la care lucram, era aceia ca, peste tot, erau proceduri de genu:
calculeazaSalariuManager()
InchiriazaBarca()
InchiriazaContainer()
CalculeazaDrum()
Si multe altele ca astea, toate intr-un anumit obiect.
Dorinta noastra fierbinte, cand scriem un soft, este aceea de a nu ajunge sa avem toate clasele comportandu-se ca un cod procedural.
Pentru aceasta recomandarea generala este:
Oricand aveti de a face cu un proces specific domeniului, incercati sa il construiti ca un obiect.

Cum sa facem asta?

Pai o prima varianta, atunci cand procesul este unic si nu comporta mai multe variante din care sa se aleaga, este patternul Service (vorbim despre el, intr-o postare urmatoare).
Daca insa procesul respectiv se poate realiza in mai multe variante, variante din care sa se aleaga eventual la momentul rularii, folositi patternul strategy pentru a incapsula intregul algoritm intr-o clasa.

De ce vorbim despre astea?

Constrangerile si procesele sunt doua tipuri de concepte foarte raspandite care nu iti vin in minte la momentul proiectarii unei aplicatii, dar care, odata identificate si introduse in cod, netezesc modelul claselor scrise de noi si imbunatatesc uzabilitatea si intretinerea codului.

Dar unde este java?

Da, da, stiu, aveti dreptate, iar bat campii pe design si nu va arat java.
V-am spus sa ma iertati cand fac asa, promit sa nu se mai intimple, pana data viitoare.
Ajungem si la java imediat, atit java ca o sa va saturati.
Dar mai intai sa ajungem la modelul, paternul, care ne va ajuta sa rezolvam problema constrangerilor, A proceselor si a mai multor lucruri.

Va Urma

In elaborarea acestui articol ne-am sprijinit pe:

Domain-driven design : tackling complexity in the heart of software / Eric
Evans.
p. cm.
Includes bibliographical references and index.
ISBN 0-321-12521-5

Evans, E., and M. Fowler. 1997. "Specifications." Proceedings of PLoP 97 Conference.

2 comentarii:

  1. Interesanta ideea acestui blog, dar pe cand putem vedea o aplicatie propriu-zisa, care sa puna in evidenta aplicarea principiilor de mai sus?

    RăspundețiȘtergere
  2. Punem si aplicatii, dar va lasam ca teme unele modificari, ca sa fiti mai activi!

    RăspundețiȘtergere

Va rugam lasati un comentariu!