O greșeală de proiectare

Avem o problemă simplă: un număr poate varia între o valoare minimă și una maximă; utilizatorul poate decide care sunt aceste limite. Cum facem?

În principiu vom avea o clasă care va păstra aceste limite și vor exista metode care să permite modificarea lor. Mai putem decide și că există niște valori implicite. Le putem alege noi și/sau putem crea un constructor care primește cele două valori. Prima implementare consideră că valorile implicite sunt 5 și 10. Vom modifica minimul la 2 și maximul la 5 și vom afișa intervalul.

Pare simplu, dar sunt câteva probleme. Să începem cu una simplă: ce se întâmplă dacă modificăm doar minimul, dar acesta devine mai mare decât maximul? De exemplu, am putea elimina linia 15 și modifica linia 14 în:

Nu e prea bine fiindcă nu ar mai exista nicio valoare posibilă pentru numărul nostru (intervalul ar deveni [20, 10]). Așadar, nu ar trebui să permitem acest lucru.

Pentru ca cei care folosesc clasa noastră să știe dacă modificarea a fost permisă sau nu, am putea modifica metodele astfel încât să returneze o valoare booleană care indică dacă operația s-a efectuat sau nu cu succes. Noua implementare ar fi:

Observăm că modificarea din linia 18 nu este efectuată, dar intervalul final este valid.

Problema este că un utilizator obișnuit nu se așteaptă la acest efect. Dacă dorește să modifice limitele, nu se așteaptă ca una dintre modificări să eșueze datorită unui detaliu intern de implementare.

Evident, pot fi inversate liniile 18 și 19 și atunci totul va funcționa corect, dar utilizatorul trebuie să aibă informații suplimentare legate de implementare, ceea ce nu e chiar bine.

Am putea crede că e OK să modificăm tot timpul prima data maximul și apoi minimul, dar nu e așa. De exemplu, dacă intervalul inițial ar fi [5, 10] și am dori să devină [2, 4], trebuie modificată prima dată valoarea minimă. Din nou, utilizatorul trebuie să fie atent.

S-ar putea spune că totuși nu e foarte complicat și utilizatorul trebuie doar să fie atent la ordinea în care efectuează operațiile. Pentru acest exemplu, e destul de simplu - să-l complicăm puțin...

De data aceasta vom lucra cu două intervale. Să presupunem că într-un grup pot exista un număr de fete cuprins între două limite și un număr de băieți cuprins între două alte limite. Avem două intervale acum, fiecare cu problema anterioară. Implementarea ar fi:

Dar... pentru a simplifica situația, am putea decide că limitele inferioare pot fi modificate deodată (la fel și cele superioare). Vom permite modificarea doar dacă ambele valori noi sunt acceptabile. Să vedem o implementare:

Dar... acum devine interesant; dacă am dori ca noul interval pentru fete să fie [20, 50], iar cel pentru băieți să fie [2, 4]? Apelurile ar fi:

În orice ordine le-am pune, nu s-ar întâmpla nimic. Pentru una dintre valori modificarea ar crea un interval invalid, deci nu ar fi efectuată. Deci... așa ceva nu se face! Mai exact, nu ar trebui să se facă, dar o astfel de decizie de proiectare este uneori luată, de exemplu aici și aici.

Cu puțină imaginație am putea ajunge la intervalele pe care ni le dorim, dar nu ar trebui să fim nevoiți să facem astfel de artificii. O variantă ar fi:

Dar, dacă am trece la trei intervale? Sau un șir de intervale? Numărul artificiilor crește exponențial. Am greșit dacă am ales o astfel de abordare.

Soluția este simplă. Trebuie să ni se permite să modificăm minimum și maximul deodată. Iar dacă avem mai multe intervale și vrem să modificăm mai multe intervale deodată, trebuie să putem preciza minimele și maximele deodată. Pentru un singur interval nu trebuie să verificăm decât faptul că noul interval este valid și nu ne mai interesează care a fost cel vechi:

Implementarea pentru grupul de fete și băieți este similară.

Ar putea părea natural că așa ar fi trebuit să facem și probabil așa am fi făcut dacă ne-am fi gândit puțin. Din nefericire, uneori alegem soluții "exotice" fiindcă nu ne dăm seama care sunt consecințele lor. Cu alte cuvinte, cam sărim a doua fază dintre cele trei prezentate în acest articol.