TSM - Perspective asupra principiilor în design-ul orientat pe obiecte

Cătălin Tudor - Principal Software Engineer

Definiția entropiei pe care cu siguranță am auzit-o cu toții nu lasă loc de interpretare: cu cât entropia este mai mare cu atât dezordinea și haosul încep să îsi facă de cap. Acest lucru înseamnă impredictibilitate care nu se numără cu siguranță printre calitățile dorite ale unui design bun. Cu toate acestea, după cum vom vedea în cele ce urmează, o entropie mare (si aici mă refer la entropia Shannon și nu la versiunea sa termodinamică, deși există similitudini între cele două) nu este defectul principal al unui design prost.

De fapt, folosind ca reper doar entropia design-ului în ansamblul, nu suntem în măsură să spunem foarte multe despre acel design, putem spune de exemplu că el rezolvă o anumită problemă care are nevoie de un anumit număr de stări. Poate părea contraintuitiv, dar pentru a obține un design bun clasele care moștenesc alte clase trebuie să aibă o entropie mai mare decât cea a claselor de bază, orice încercare de a reduce entropia într-o clasă derivată va duce în general la situații de strong-coupling și la un comportament ciudat, în general complicat și nedorit.

Scopul acestui articol este să studieze modul în care principiile binecunoscute de OOD (Object Oriented Design) influențează entropia locală a designului.

Voi începe cu Principiul Substituției Liskov (LSP) pentru că influența entropiei este observabilă într-un mod clar și permite exemplificări concrete.
În câteva cuvinte, LSP este o regulă care ajută programatorii să decidă când anume o clasă poate moștenească o altă clasă. Unul dintre cele mai cunoscute exemple de încalcare LSP este următorul:
Imaginați-vă că ați creat o aplicație care gestionează dreptunghiuri. Aplicația are un success atât de mare încât utilizatorii solicită un nou feature pentru ca aplicația să poată gestiona și pătrate.

Știind că un pătrat este un dreptunghi cu toate laturile egale, cea mai la îndemâna alegere de a extinde design-ul este să folosim moștenirea. Astfel, adăugăm o nouă clasa numita Square care va moșteni clasa Rectangle, în acest fel putem reutiliza toate funcționalitățile deja implementate.

class Rectangle
{
public:
  void SetWidth(double w) {
    width = w; }

  double GetWidth() const { 
    return width; }
 
 void SetHeight(double h) { 
    height = h; }

  double GetHeight() const { 
    return height; }        

private:
  double height;
  double width;
};

In Square suprascriem SetWidth si SetHeight pentru a ne asigura că cele patru laturi sunt egale.

void Square::SetWidth(double w)
{
   Rectangle::SetWidth(w);
   Rectangle::SetHeight(w);
}
void Square::SetHeight(double h)
{
   Rectangle::SetHeight(h);
   Rectangle::SetWidth(h);
}

Această alegere nu este una fericită insă nu voi insista asupra motivelor clasice. Vă voi arăta însă o altă perspectivă a implicațiilor acestei alegeri de extindere a design-ului, din punctul de vedere al variației entropiei.

Mai întâi, câteva cuvinte despre entropie. Entropia Shannon este o măsură a incertitudinii asociată unei variabile aleatoare și este de obicei măsurată în biți. Remarcați că este aceeași unitate de măsura folosită pentru a determina capacitatea memoriei sau lățimea de bandă pentru transferul in rețea. În cazul în care acestă observație nu vi se pare suprinzătoare, aflați că o singură aruncare a unei monede (datul cu banul) are o entropie de un bit! Entropia măsoară cantitatea de informație necesară pentru a reprezenta toate stările unei variabile aleatoare.

Pentru cantități mici de informație putem identifica reguli simple de reprezentare concisă a tuturor stărilor variabilei. Însă în cazul haosulului sau al secvențelor de numere aleatorii avem de a face cu entropii uriașe, adevărate explozii de informație unde nu există reguli simple pentru a anticipa, de exemplu, următorul număr din secvență.

[Vă oferim câteva detalii legate de asemănărea dintre entropia din termodinamică și entropia Shannon. În final,ambele reprezintă de fapt același lucru. Imaginați-vă un container cu două lichide (unul alb și unul negru) separate de un perete. După îndepărtarea peretelui lichidele se amestecă, prin urmare entropia termodinamica crește. Din punctul de vedere al teoriei informației, identificarea poziției fiecărei particule în raport cu poziția sa inițială față de peretele despărtitor (la stânga sau la dreapta de acesta) necesită mult mai multă informație decât atunci când lichidele nu sunt amestecate.]

Există, de asemenea, o definiție și o formulă pentru calculul entropiei:
Pentru o variabilă aleatoare X cu n valori posibile (x1, x2, x3…, xn) entropia Shannon (notata cu H (X)) se poate calcula după formula:

unde p(xi) reprezintă probabilitatea ca variabila aleatoare să ia valoarea xi.

Să luăm câteva exemple în scopul de a căpăta o înțelegere asupra a ceea ce reprezintă formula și de ce are sens să măsurăm entropia în biți.

[Mai exact, în funcție de baza logaritmului entropia este măsurată în biți (baza 2), nats (baza e) sau in bans (pentru baza 10)]

Exemplul 1

Cât de multă informație este necesară pentru a stoca o variabilă aleatoare X care

ia valori în mulțimea {0, 1}? Considerăm [Aceasta înseamnă ca 0 și 1 au aceeași șansă, 50%, de a fi atribuite lui X].

Exemplul 2

Cât de multă informație este necesară pentru a stoca o variabilă aleatoare X care ia valori în mulțimea {00, 01, 10, 11}? Luăm în considerare

[Aceasta înseamnă că toate valorile au aceeași șansă, 25%, de a fi atribuite lui X]. Formula ne spune că

[Întrebați-vă dacă stiți definiția bitului. Definiția nu este evidentă însă fiecare programator crede că o cunoaște (este într-un fel amuzant când realizezi că poate nu ți-ai pus niciodată această întrebare)]

Dacă s-au mai clarificat lucrurile în privința entropiei, e important să vedem cum se aplică ea în analiza design-ului. Pentru început, vom încerca să răspundem la o întrebarea: Cât de mare este entropia clasei Rectangle? Analizăm domeniul în care ia valori, mai precis combinațiile posibile de lățime și înălțime. Vom folosi un caz simplificat în care acestea pot lua numai valorile 0 și 1. Se pare că putem defini clasa Rectangle ca o variabilă aleatoare XR={wh}, XR cu valori în multimea {00, 01, 10, 11} fiecare valoare reprezentând o combinație de lățime (w) și înălțime (h). Din exemplul anterior știm că entropia unei astfel de variabile aleatoare este egală cu 2.

H(XR)=2 (biți)

Cât de mare este entropia clasei Square? Observăm domeniul în care iau valori lățimea și înălțimea în cazul simplificat cu valorile permise 0 și 1. De asemenea, putem defini clasa Square ca o variabilă aleatoare XS={wh}, XS. În acest caz însă, entropia este diferită, deoarece lățimea și înălțimea nu mai variază în mod independent. De fiecare dată când este setată lățimea (w) este setată și înălțimea (h) cu aceeași valoare. Știm de la primul exercițiu că entropia este egala cu 1 în acest caz.

H(XS)=1 (bit)

Este momentul să identificăm prima regulă a modului în care entropia trebuie să varieze într-un design:
Ori de câte ori o clasă S (Square) extinde o clasă R (Rectangle), este necesar ca H(XS)>=H(XR). În cazul nostru 1=H(XS)R)=2 ! Ce se întâmplă de fapt când încălcăm "regula entropiei"?

[Aici aspectul important este modul în care designul crește entropia, deoarece haos-ul și dezordinea din interiorul codului provine și din modul în care entropia este crescută, structurată și utilizată în cadrul claselor.]

Un exemplu din lumea reală

Putem exemplifica regula entropiei folosind o ușă. Ce anume face o ușă? Care este comportamentul ei? Evident o ușă se deschide (dacă nu cumva este blocată) și folosește interiorul unei camere pentru a face acest lucru. Iată un mod simplu de a scrie aceasta în cod:

class Door
{
  void Open(Room r)
  {
  …. the door opens inside the room
  }
}

Imaginați-vă că entropia camerei este proporțională cu volumul său. Ce s-ar întâmpla dacă am extinde clasa Room prin reducerea entropiei (a volumului)? Să numim această nouă clasa FakeRoom. Ei bine ... următoarea imagine vorbește de la sine.

Informația (entropia) lipsă din noua camera ajunge să fie codificată în ușă (partea de jos a fost decupată pentru a se putea deschide). Acum ușa și camera sunt cuplate (strong-coupling). Nu vom mai putea utiliza această ușă la o altă cameră fără a ridica semne de intrebare!

[Dezvoltatorii trebuie să înțeleagă că designul lor va arăta la fel ca sistemul din această imagine sfatul meu fiind să nu ignore aceste semne, vaza si florile nu sunt suficiente pentru a-l transforma într-un design bun.]

[Ne putem imagina un alt doilea exemplu, cu o pompă de apă si țevi de diferite dimensiuni.]

Concluzii

  1. Scăderea entropiei prin utilizarea moștenirii este un semn de încălcare a încapsulării unei clase.
  2. Agregarea este recomandată în locul moștenirii. Nu există o modalitate de a încălca regula entropiei atunci când se utilizează agregarea. Entropia poate fi variată după dorință.
  3. Să depinzi de abstractizări este o practică recomandată pentru că interfațele nu au o limită inferioară a entropiei.
  4. Matematicienii ar spune despre regula entropiei că este necesară dar nu și suficientă pentru a semnala o încălcare a LSP în sensul că, dacă ne supunem regulii am putea obține un design bun, în timp ce dacă încălcăm regula acest lucru duce cu siguranță la un design prost.
  5. Entropia design-ului este o perspectivă care poate îmbunătăți metodele deja existente de detectare a încălcării LSP.