rss
    System.out.println("Hello World");
Se afișează postările cu eticheta specificatii. Afișați toate postările
Se afișează postările cu eticheta specificatii. Afișați toate postările

joi, 25 noiembrie 2010

Constrangeri, procese si specificatii in java: Partea IV Specificatii - constructia dupa plan a obiectelor

Uau! Put your hands in the air!
Suntem din nou aici cu specificatiile si cu toate lucrurile fascinante pe care ele ni le ofera!
Vreau sa va aud America, toti impreuna…
….

Ups! Stai ca scriem in romana… Hai frate...
....
E asta e!

Prezentam datile trecute cerintele pentru care ne luptam cu aceste specificatii.
Printre altele, este visul meu de la 3 ani (de cand mama ma obliga sa invat Luceafarul pe dinafara),
Sa pot construe obiecte in mod dynamic.
Mai précis, habar nu am la momentul rularii ce si cum trebuie sa arate un obiect, dar stiu ca am datele si regulile pe care acest obiect trebuie sa le indeplineasca, undeva, intr-un fisier, intr-o baza de date.
Pot specificatiile sa ma ajute in aest caz?
Ei bine nu, dar o pot face intr-un caz intermediar.
Dar daca dezvoltam putin conceptul de specificatie, vom obtine si acest comportament pur dinamic.
(intr-o postare ulterioara, poate il dezvoltam).

Despre constructia dupa plan a unui obiect!



Cand ne cumparam un nou televizor, ne uitam dupa anumite caracteristici.
Vreau ca televizorul sa aiba contrast cel putin 30000:1, sa aiba o diagonala de cel putin 66 cm, sa fie capabil sa decodeze si dvb-t si dvb-c, sa aiba gauri de conectare pentru cat mai multe tipuri de mufe, iar in final sa nu coste mai mult de 1300 ron.
O companie producatoare, poate lua aceste specificatii si imi poate livra mai multe sortimente de televizoare din gama lor care respecta aceste caracteristici.
Mai mult, companii competitoare, pot oferi produse conforme cu aceste specificatii.
Cuvantul cheie aici pare a fi „specificatii”.

La fel se intimpla si in viata computerelor.
Cand inserati o poza intr-un procesor de texte, textul curge pe langa poza.
Voi ati specificat locatia pozei si poate modul in care textul sa curga pe langa poza.
Din aceste specificatii, procesorul de texte va aranja liniile si cuvintele de asa natura incat sa se conformeze.
Desi nu e chiar vizibil la inceput, acest concept de specificatii, este fix acela pe care l-am folosit la validare si la selectie.
Desi implementarea va diferi putin, deoarece de data asta, nu e un filtru pentru obiecte existente, nu e un test pentru obiecte pentru a le testa integritatea, ci este o modalitate de a construi obiecte inexistente si de a le configura.

Multi dintre voi veti spune:
Bine, bine, de ce nu pot face o clasa, in care sa fac tot felul de metode, care sa constituie pasii constructiei?
Asa ar arata un generator de obiecte scris fara specificatii. Metodele respective ar defini comportamentul generatorului.

Insa, daca definim o interfata pentru generator, bazat pe specificatii, atunci aceasta ar constrange in mod explicit produsul generatorului, sa fie conform cu acea specificatie.
Aceasta abordare are cateva avantaje:
- Interfata generatorului este detasata de implementarea sa. Ea defineste, declara, ce lucruri vor trebui respectate, facute, fara a impune si cum aceste lucruri vor fi facute, lasand asta la latitudinea generatorului
- Interfata ofera posibilitatea ca programatorii sa inteleaga ce sa se astepte de la generator, fara ca acestia sa fie nevoiti sa inteleaga cum lucreaza generatorul de fapt. Singurele modalitati de a intelege rezultatele unui generator scris de o maniera procedurala, e prin rularea de diverse cazuri de test, ori prin intelegerea orcarei linii de cod.
- Interfata este mult mai flexibila, sau poate fi imbunatatita mult mai flexibil, deoarece documentul de cerinte este in mana clientilor (ei hotarasc cum si in ce fel va functiona softul), pe cand generatorul nu va trebui decat sa tina seama, sa respecte aceste specificatii.
- Aceasta interfata este mai usor de folosit si la testare, deoarece declara in mod explicit ce „input” trebuie dat generatorului, precum si un mod de a testa ceea ce iese din generator. Asa cum aminteam mai sus, specificatiile acestea pot fi folosite si in rolul lor de validare (daca implementarea o suporta (pentru asta se foloseste paternul assert despre care vom vorbi intr-o postare ulterioara)).


Dar unde este java?

Of, m-ati omorat cu aceasta intrebare! Vine, vine si java, e pe drum, i-auziti,
teleap-teleap!

Sa luam un exemplu al celor de mai sus. Nimic nu ne serveste mai concret decat un exemplu pedagogic.
Am fi putut scoate din programele noastre un exemplu, dar exemplele din viata reala tind sa fie mult prea complexe si cu detalii de implementare care omoara conceptul de baza.
Si de data aceasta, pentru a exemplifica ceea ce vrem sa spunem, Fovler si cu Evens ne sunt de mare ajutor.

Vom folosi exemplul oferit de Evens, in cartea sa.
Folosim aest exemplu, deoarece el ne-a ajutat si pe noi, in aplicatia noastra de facturi, comenzi si livrari.
Desi noi acolo vorbeam de alimente si bunuri de larg consum, nu de chimicale cum ne arata Evens in continuare.
Am folosit doar codul din acest exemplu, restul de comentarii, expliatii sunt ale noastre (adica ale mele, ce tot vorbesc eu asa la plural! Anunturicl dormi?)

Deci avem un program, care trebuie sa impacheteze cateva chimicale inofensive, dar foarte pretentioase la un loc.
Avem tnt care e explozibil, avem Amonium care e volatil, avem probe biologice (cum ar fi nisipu).
Si de parca nu ar fi de ajuns, mai exista si reguli care privesc combinatia dintre toate acestea.
Pe scurt regulile sunt asa:
Tnt nu poate sta decat in Container Blindat.
Amoniu nu poate sta decat in container ventilat.
Nisipu poate sta oriunde, dar nu poate fi in acelas container cu tnt-ul.

Vom scrie deci clase de specificatii pentru container.
Acestei specificatii de container, ii vom furniza caracteristicile de container.
Fiecare chimicala va avea o specificatie de container, configurata cu niste caracteristici de container.
Deci vom avea clasele: ContainerSpecification, ContainerFeature, precum si clasa Drum (chimicala).
Clasa ContainerSpecification va contine o caracteristica de container pe care containerul trebuie sa o indeplineasca.
Pentru a verifica acest lucru (dupa cum va amintiti de la validare) scriem o metoda.
Clasa va arata asa:

public class ContainerSpecification {
private ContainerFeature requiredFeature;
public ContainerSpecification(ContainerFeature required) {
requiredFeature = required;
}

boolean isSatisfiedBy(Container aContainer){
return aContainer.getFeatures().contains(requiredFeature);
}
}

Uitati cum vom construi specificatia unei chimicale:

tnt.setContainerSpecification(
new ContainerSpecification(ARMORED));

Am zis ca intr-un container sunt mai multe chimicale.
Va trebui deci sa il putem intreba pe container daca este impachetat in siguranta.
El va realiza acest lucru, intreband toate chimicalele din interiorul sau, daca el le satisface specificatia de container.
Cu alte cuvinte, cum un baiat intreaba o fata (sunt bun la pat iubit-o)?

Iata metoda din container care verifica acest lucru:

boolean isSafelyPacked(){
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
if (!drum.containerSpecification().isSatisfiedBy(this))
return false;
}
return true;
}


In acest moment am putea scrie foarte usor o bucata de cod care sa preia toate posibilitatile din baza de date sis a le verifice daca sunt bine ambulate:
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (!container.isSafelyPacked())
unsafeContainers.add(container);
}

Dar nu pentru asta am fost platiti. Puteti sa le spuneti clientilor vostri ca, datorita inteligentei voastre, informatiilor gasite pe acest blog si cu bunavointa lui Fovler si Evens, puteti, folosind domain driven design, sa scrieti usor orice noua cerinta, dar noi vroiam sa generam obiecte.
Am vazut insa, cat de usor e sa implementezi si partea de validare daca doresti.

Revenim la cazul nostrum, trebuie sa scriem un soft, care preia niste chimicale, niste containere si optimizeaza impachetarea acestora.
Vom scrie o interfata pentru un serviciu care sa faca asta.
Pentru ca mai intai ne-am lamurit cum e cu partea de test a impachetarii, acum ne va fi foarte usor sa facem asta:

public interface WarehousePacker {
public void pack(Collection containersToFill,
Collection drumsToPack) throws NoAnswerFoundException;

/* Cerinta: La finalul metodei pack(), ContainerSpecification
Al fiecarui Drum trebuie sa fie satisfacuta de al sau Container.
Daca o solutie completa nu poate fi gasita, se va arunca o exceptie */

}

In acest moment, implementarea modului de impachetare a fost detasata de restul design-ului, ne incarcandu-l pe acesta.
Dar atentie, regulile care definesc domeniul de activitate, au ramas incluse in modelul de proiectare.

Vom vorbi putin aici si de lucrul in echipa.
Adesea se intimpla ca echipe de programatori sa lucreze la portiuni de aplicatii in parallel, dar acele portiuni de aplicatii sa fie interdependente, astfel incat, la un moment dat, in cazul unui design prost, una, sau mai rau, ambele echipe vor stagna.
Sa luam acest exemplu.
A scrie un soft de optimiare, de orice fel ar fi el, este o provocare mareata, desigur daca vorbim de un optimizator pe bune sin u unul cu numele.
Inchipuitiva ca avem o echipa incercand sa dezvolte serviciul de impachetare, iar alta echipa incercand
Sa scrie restul de cod.
Cea de a doua echipa, va scrie codul de integrare in baza de date, scoaterea de date de acolo, introducerea lor in serviciul de impachetare, preluarea rezultatelor de la acesta, interpretarea si afisarea lor.
Ce ne facem insa, ca ei nu pot face decat oleaca de integrare cu baza de date, pot eventual sa faca designul la o interfata grafica, dar cam atit.
Nici macar nu pot sa arate un prototip utilizatorilor sis a aiba un feetback (aferentatie inversa in romana! Eeee! Va place ce cuvinte stiu?), de la ei despre functionarea softului,
Deoarece acest feetback nu ar fi valabil decat in cazul unui system functional de la cap la coada.
Dar stati putin, ca cu design-ul prezentat mai sus, aceasta a doua echipa, poate scrie repede un optimizator nu tocmai performant, dar sufficient pentru a prezenta softul clientilor, iar la momentul cand prima echipa a terminat marele optimizator performant, acesta doar sa fie introdus in soft.
Cum arata codul echipei a doua deci:

public class Container {
private double capacity;
private Set contents; //Drums

public boolean hasSpaceFor(Drum aDrum) {
return remainingSpace() >= aDrum.getSize();
}

public double remainingSpace() {
double totalContentSize = 0.0;
Iterator it = contents.iterator();
while (it.hasNext()) {
Drum aDrum = (Drum) it.next();
totalContentSize = totalContentSize + aDrum.getSize();
}
return capacity – totalContentSize;
}

public boolean canAccommodate(Drum aDrum) {
return hasSpaceFor(aDrum) &&
aDrum.getContainerSpecification().isSatisfiedBy(this);
}

}


public class PrototypePacker implements WarehousePacker {

public void pack(Collection containers, Collection drums)
throws NoAnswerFoundException {

/* Aceasta metoda satisface cerinta asa cum a fost ea scrisa in interfata. Totusi, cand are loc o exceptie, este posibil ca datele continute in containere sa se fi schimbat. Activitatea de rollback trebuie realizata la un nivel mai sus. */

Iterator it = drums.iterator();
while (it.hasNext()) {
Drum drum = (Drum) it.next();
Container container =
findContainerFor(containers, drum);
container.add(drum);
}
}
public Container findContainerFor(
Collection containers, Drum drum)
throws NoAnswerFoundException {
Iterator it = containers.iterator();
while (it.hasNext()) {
Container container = (Container) it.next();
if (container.canAccommodate(drum))
return container;
}
throw new NoAnswerFoundException();
}

}

Si cu aceasta am exemplificat si a treia caracteristica a specificatiilor. Lucrurile pot fi dezvoltate de aici mult mai mult, dar asta depinde si de timpul si de bugetul si de cheful pe care il aveti.
Aveti insa, prin amabilitatea ;lui Evens si Fovler si din experienta noastra cu acest concept, o modalitate corecta si completa de a modela anumite lucruri, astfel incat san u mai injurati ori de cate ori un client va cere un lucru nou.
Dupa cum s-a vazut intr-un soft bine modelat, lucrurile chiar grele se adauga relative usor.
Va multumesc ca ati urmarit acest serial de 4 zile, iar maine va invite la un vot pe care AnunturiCL il va realize.
In functie de dorinta dumneavoastra,
Vorbim despre un subiect sau altul, sau nu mai vorbim de loc!
Multumiri avem si pentru:
Fovler si Evens pentru munca depusa in domeniul software developing, pentru ca ne-au scos pe noi la momentul potrivit din rahat cu gandirea lor extraordinara si ca au pus la dispozitia lumii intregi, cunostintele lor.


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.

miercuri, 24 noiembrie 2010

Constrangeri, procese si specificatii in java: Partea III Specificatii - Selectia

Atacam astazi a doua cerinta pentru specificatii, selectia cu toate problemele sale.


Despre selectie!

Aici avem doua sub probleme.
Le vom ataca pe rand.
Pentru simplitate vom presupune mai intai ca toate datele le avem in memorie si ca nu avem nevoie de date din baza de date.
Ne referim la acelas exemplu cu facturile.
Vrem sa obtinem toate facturile „delicvente”:


public Set selectSatisfying(InvoiceSpecification spec) {

Set results = new HashSet();
Iterator it = invoices.iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (spec.isSatisfiedBy(candidate)) results.add(candidate);
}

return results;
}

Sa zicem ca aceasta metoda se afla in repository de facturi (InvoiceRepository).
Un client va putea obtine deci toate aceste facturi, bazandu-se pe o specificatie:

Set delinquentInvoices = invoiceRepository.selectSatisfying(
new DelinquentInvoiceSpecification(currentDate));

Din pacate, este foarte rar cazul cand avem toate datele in memorie, iar interactiunea cu baza de date, desi pare simpla pentru unii, este cel mai greu de obtinut in conditii corecte.
Este cu atit mai greu cu cat, ne dorim obtinerea unui model detasabil fata de baza de date si in care interogarile sql san u apara la fiecare pas.
Vom prezenta in continuare, pasi prin care am trecut cu rationamentul pentru a ajunge la codul perfect.
In continuare, de ajutor ne-au fost (prin scrierile lor Fovler si Evens).
Mai intai, ca multi dintre voi, ne-am gandit sa bagam in clasa specificatie o metoda ca urmatoarea:

public String asSQL() {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(currentDate);
}

Cum ziceam, acest lucru implica insa o problema, o sa incepem sa avem cod sql peste tot in aplicatie si asta nu ar fi asa o problema, daca nu ar depinde acest sql de structura tabelelor din baza de date.
Regula generala:
Informatia despre baza de date si structura ei, trebuie bine izolata intr-un strat (layer) de legatura intre aplicatie si baza de date sin u trebuie sa iasa de acolo.
Altfel, vom avea probleme de intretinere a aplicatiei.
S
Solutia numarul doi, este sa urmam regula de mai sus, vom pune interogarea in InvoiceRepository, formand o Metoda de interogare specializata, darn u vom furniza toate detaliile reguli de verificat, pentru a nu introduce detalii despre aplicatie in stratul (layer) legat de baza de date.
Ci vom divide funtionalitatea intre cele doua straturi si vom folosi un mechanism de chemare dubla (double dispatch) pentru a realize verificarea regulii.
(despre patterni de dispatch si dispatchers, vom vorbi intr-o alta postare).

public class InvoiceRepository {

public Set selectWhereGracePeriodPast(Date aDate){
String sql = whereGracePeriodPast_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}

public String whereGracePeriodPast_SQL(Date aDate) {
return
"SELECT * FROM INVOICE, CUSTOMER" +
" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +
" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" +
" < " + SQLUtility.dateAsSQL(aDate);
}

public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}

public class DelinquentInvoiceSpecification {

public Set satisfyingElementsFrom(
InvoiceRepository repository) {
return repository.selectWhereGracePeriodPast(currentDate);
}
}

Aceasta varianta, aseaza interogarea in repository, in timp ce specificatia controleaza ce interogare va fi folosita.
Chiar daca regula nu e toata in specificatie, essential este ca in specificatie se defineste clar ce inseamna “delicventa” unei facturi si anume depasirea perioadei de gratie.

Repository are acum o metoda de interogare foarte specializata, care probabil va fi folosita doar in cazul “delicventei”.
Asta este acceptabil, dar gandindu-ne la numarul de facturi care doar au deposit perioada de scadenta, fata de cele “delicvente”, ajungem la o solutie intermediara, care face metodele din repository mai generale, lasand specificatia sa fie mai concreta.

public class InvoiceRepository {

public Set selectWhereDueDateIsBefore(Date aDate) {
String sql = whereDueDateIsBefore_SQL(aDate);
ResultSet queryResultSet =
SQLDatabaseInterface.instance().executeQuery(sql);
return buildInvoicesFromResultSet(queryResultSet);
}

public String whereDueDateIsBefore_SQL(Date aDate) {
return
"SELECT * FROM INVOICE" +
" WHERE INVOICE.DUE_DATE" +
" < " + SQLUtility.dateAsSQL(aDate);
}

public Set selectSatisfying(InvoiceSpecification spec) {
return spec.satisfyingElementsFrom(this);
}
}

public class DelinquentInvoiceSpecification {
//alt cod.

public Set satisfyingElementsFrom(
InvoiceRepository repository) {
Collection pastDueInvoices =
repository.selectWhereDueDateIsBefore(currentDate);

Set delinquentInvoices = new HashSet();
Iterator it = pastDueInvoices.iterator();
while (it.hasNext()) {
Invoice anInvoice = (Invoice) it.next();
if (this.isSatisfiedBy(anInvoice))
delinquentInvoices.add(anInvoice);
}
return delinquentInvoices;
}
}


In speranta ca sunteti multumiti de cat java ati vazut, chit ca discutam incontinuare despre business layer, va spun ca urmeaza cea mai interesanta parte,
Aceea a constructiei obiectelor dupa un plan bine stabilit, intr-un mod declarativ, fara prea multe if-uri thenuri.
Practic urmeaza un generator de obiecte pe care il voi prezenta.
Deci nu pierdeti episodul de maine!

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.

marți, 23 noiembrie 2010

Constrangeri, procese si specificatii in java: Partea II Specificatii - Validarea

Ati vazut ce nume de site are bunul meu prieten?
Inchipuitiva prin ce calvar trebuie sa trec cand strig dupa el pe strada:
„bai anunturicl, ba anunturicl, stai dracu sa ne luam si noi o saorma”!

Aveti deci mai jos, prin bunavointa gazdei noastre, o aplicatie exemplu, cei dintre voi care doriti sa aveti ceva asemanator, dar mai evoluat, puteti sa ne contactati si vom fi mai mult decat bucurosi sa adaugam aplicatiei cerintele voastre.

Revenim la continuarea serialului nostru despre subiectele incepute ieri!

prezentam postarea trecuta, constrangerile si procesele si va promiteam ca vom reveni cu un model, cu un patern, care sa rezolve aceasta problema a constrangerilor si a proceselor.
Trecand prin tot procesul anterior prezentat, am ajuns noi la concluzia ca ne-ar trebui un concept de modelare care sa ne permita urmatoarele:
- Validarea: O modalitate prin care sa pot verifica constrangerile, sau anumite caracteristici importante;
- Selectia: Fiind date mai multe obiecte de acelas tip, ma intereseaza sa le obtin pe acelea care respecta constrangerea/regula, sau din potriva, sa le aleg pe acelea care nu respecta constrangerea/regula;
- Constructia: As dori sa pot construi un obiect, dupa niste reguli date si regulile respective sa fie verificabile. Mai precis as dori sa pot construi un obiect dupa un plan dat, adica, spre exemplu, la o masina stiu ca trebuie sa ii pun mai intai motorul si apoi caroseria, sau invers. Sa am un numar de pasi dupa care sa ma ghidez in constructia unui obiect.
Mai mult, constructia unui obiect se poate realiza in diferite conditii:
Obiectul abia a fost creat, deci e un obiect nou;
Obiectul se afla in baza de date, deci el trebuie luat de acolo si adus in aplicatie;
Alte cazuri.
Daca s-ar putea, am zis noi, ar fi chiar tare daca acest concept de modelare ne-ar oferi si posibilitatea de a combina aceste constrangeri intr-un lant de constrangeri aplicate.
Un cuvant ne-a venit la amindoi in minte:
- Frate, asta suna ca si cum ai construi specificatia unui produs;

Am inceput sa cautam febril daca exista un astfel de concept de modelare.
Si l-am gasit intr-un articol de Evans si Fovler din anul 1997, dar si mai tarziu intr-o carte a lui Evens.

Ne intoarcem putin la exemplul cu facturi.
Cum am aratat mai sus, avem tendinta de a testa cu metode booleene toate constrangerile care ne apar pe un obiect.
Acest lucru merge perfect atita timp cat regulile sunt simple.
Deci la o clasa pentru factura, verificarea faptului ca o factura a depasit data scadenta ar fi:

public boolean isOverdue() {
Date currentDate = new Date();
return currentDate.after(dueDate);
}

Ce ne facem insa daca vrem sa verificam ca o factura este „delicventa” adica a depasit orice urma de bun simt.
Pentru aceasta, verificarea ar incepe mai intai cu constrangerea faptului ca factura trebuie sa fi depasit data scadenta.
Dar, mai departe trebuie sa ne bazam verificarea pe un plan de gratiere (acea perioada de gratie acordata la o factura), plan de gratiere care poate fi diferit de la client la client, in functie de statusul contului clientului respectiv, de ofertele pe care clientul le poate primi si de comportamentul financiar si nu numai al clientului.
Mai mult, e vorba si de procesul de dupa (vedeti, ajungem iar la procese).
Unele facturi pot fi gata pentru un al doilea avertisment care sa fie trimis clientului, pe cand altele abia se afla la primul avertisment, sau mai rau, sunt gata de a fi trimise la recuperatori.
Este clar ca, functionalitatea de baza a unei facturi, aceea de a fi un document de cerere de plata, se va pierde printre tonele de cod de verificare de reguli.
Clasa factura va dezvolta de asemenea o multime de dependente fata de alte clase, care la baza, nu aveau treaba cu acest concept.
Acum, multi dintre voi, ati lua acest cod de verificare al regulilor si l-ati pune in partea de prezentare a aplicatiei.
Ati lua factura asa cum e ea din baza de date, ati aduce-o in cod si de acolo abia la prezentarea ei sau la salvarea ei ati verifica datele.
Din pacate, acest comportament, lasa in urma un obiect cu date mort, care nu stie sa se verifice singur pentru regulile elementare din domeniu specific.
Verificarea regulilor si a constrangerilor, trebuie sa ramana in modelarea domeniului specific (ceea ce se cheama in design business layer), dar daca nu se potriveste in cadrul definitiei unui obiect, cum ar fi factura, modelati-l ca obiect separat.
Nu doar atit, dar evaluarea regulilor cu metode conditionale, va face regulile sa fie greu de citit.
Dezvoltatorii care lucreaza cu paradigma programarii logice, ar scapa usor.
Ei ar construi predicate pentru fiecare regula si ar evalua factura cu aceste predicate.
Predicatele sunt functii care returneaza adevarat sau fals bazat pe o anumita regula.
Acestea, predicatele, pot fi combinate folosind operatori logici cum ar fi conjunctia si disjunctia.
Numai ca noi, avem o mica problema, una mica de tot, aproape de detaliu as putea spune.
Nu suntem in paradigma de programare logica.
Din fericire, nici nu ne trebuie intreaga paradigma logica de programare, ci avem nevoie doar de conceptul de predicat.
Vom modela acest concept de predicat prin obiecte separate, de sine statatoare, care pot primi si evalua starea unui alt obiect.
Informatiile de care acest predicat are nevoie, ii vor fi date la creiere de catre un factory.
In acest fel, obiectul factura isi va pastra functionalitatea sa de baza.
Vom lua pe rand cele trei cerinte ale noastre de mai sus, prezentandu-le pe rand in cate o postare, astazi validarea:

Despre validare!

Ne intoarcem la exemplul cu factura, sa o verificam daca este” delicventa”...

class DelinquentInvoiceSpecification extends
InvoiceSpecification {
private Date currentDate;
//o instanta este utilizata si apoi distrusa intr-o singura zi

public DelinquentInvoiceSpecification(Date currentDate) {
this.currentDate = currentDate;
}

public boolean isSatisfiedBy(Invoice candidate) {
int gracePeriod =
candidate.customer().getPaymentGracePeriod();
Date firmDeadline =
DateUtility.addDaysToDate(candidate.dueDate(),
gracePeriod);
return currentDate.after(firmDeadline);
}

}

Pentru a vedea cum sa utilizam aceasta clasa/specificatie, presupunem acum ca va trebui sa aratam in aplicatie un semn distinctiv ori de cate ori un vanzator aduce persoane rau platnice.

Codul ar arata cam asa:

public boolean accountIsDelinquent(Customer customer) {
Date today = new Date();
Specification delinquentSpec =
new DelinquentInvoiceSpecification(today);
Iterator it = customer.getInvoices().iterator();
while (it.hasNext()) {
Invoice candidate = (Invoice) it.next();
if (delinquentSpec.isSatisfiedBy(candidate)) return true;
}
return false;
}


In felul acesta, regulile de verificare ale unei facturi, sunt in propriul lor obiect, iar factura isi vede linistita de functionalitatea ei.


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.

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.