Haideți să continuăm ce discutam în episodul precedent.
Single Responsability Principle – Principiul responsabilității unice. Asta presupune ca o clasă să aibă întotdeauna o singură responsabilitate și numai una. Deci să nu facem clase din acelea de tipul Utility, nu știu ce Manager, în care punem niște metode care trebui să fie așa generale, dar nu au loc pe niciunde. Și se face un fel de zonă globală din programarea structurală. Deci, ca la C, acolo aveam funcții și zonă globală. Aici trântim tot ce e global. Nu. Orice în programarea orientată obiect, și aici vorbim de clean code în programarea orientată obiect, orice trebuie să fie inclus într-o clasă. Nu avem voie să scriem cod în afara claselor și codul scris într-o clasă trebuie să aibă neapărat legătură cu acea clasă.
În plus, mai avem problema, că orice schimbare de specificații va duce la inutilitatea acestei clase și la rescrierea întregului cod sursă, pentru că gândind-o prost de la început, nerespectând principiile de clean code și design patterns, o să vă amintesc și mai târziu de design patterns, codul meu nu este ușor mentenabil. Adică, la ce mai mică schimbare de specificații o să mă trezesc că trebuie să rescriu tot programul.
Open-Closed Principle – Principiul închis-deschis. Acesta spune că o clasă trebuie să fie deschisă pentru extensii adică pentru a-i adăuga membri sau metode în plus, pentru a deriva din ea, dar trebuie să fie închisă pentru modificări. Adică la modificarea specificațiilor, atunci când primesc noi specificații sau clasa mea trebuie să facă lucruri în plus, acest lucru îl va face primind noi membri și noi metode sau derivând din această clasă, dar niciodată modificând codul deja existent. E un „best practice”, un obicei prost să modific cod existent în clasă, cel mai ok e ca la specificații să scriu clasa în așa fel încât, ca la modificarea specificațiilor să trebuiască doar să adaug chestii în ea, să fie deschisă pentru extensii.
Liskov Substitution Principle – Principiul substituției lui Liskov, ce spune că obiectele pot fi înlocuite oricând cu instanțe ale claselor derivate, fără ca acest lucru să afecteze funcționalitatea. Probabil știți că în majoritatea limbajelor orientate obiect avem de-a face cu noțiunea de clasă abstractă și interfață. Și totuși și clase derivate din clasele abstracte, funcții virtuale și așa mai departe. Acestea nu fac obiectul acestui tutorial, însă dacă sunteți stăpân pe ele, realizați că acest principiu are o fundamentare destul de mare în programarea orientată obiect. Și anume, întotdeauna când scriu o clasă, de exemplu am clasa Student care e derivată din clasa Persoană. Când scriu o clasă care prelucrează persoane, clasa respectivă trebuie să știe să prelucreze și studenți, e obligatoriu. În limbaje chestia asta e introdusă aproape nativ. Problema se pune la interfețe. Interfața, vă reamintesc, e o clasă ce nu are membri, ci are doar niște metode ce nu sunt implementate, ele sunt doar, le sunt doar definite signatura sau prototipul, ce returnează, ce parametri primește. Și oricând derivez o clasă dintr-o interfață, sunt obligat să implementez toate acele metode. Ideea e că, dacă eu fac o metodă care primește o interfață ca și parametru, indiferent de ce instanța, de ce clasă derivată din acea interfață vorbesc, și o dau drept parametru, ea va trebui să știe să o prelucreze.
Și ajungem la o altă chestie sau o altă noțiune, oarecum valabilă și pentru design patterns și anume „design by contract” sau dezvoltarea bazată pe contracte. Asta e același lucru ca și abonamentul nostru la telefon, sau e inspirată din viața reală. Ca atunci când mergem să ne facem un abonament la telefon, ca atunci când mergem să închiriem un apartament. Ce facem? Semnăm un contract. În acel contract noi ne angajăm că plătim chiria sau, respectiv, abonamentul la telefon, firma de telefonie sau proprietarul se angajează că ne pune la dispoziție apartamentul, respectiv că ne pune la dispoziție servicii de telefonie mobilă. Cam același lucru se transpune și în cod, adică, contractul e o interfață, care dă anumite comportamente. Vă amintesc că în C# interfețele erau cu „I” în față și aveam IEnumerable, ISearizable, adică dădeam un comportament de enumerabil, de serializabil, în Java sunt fără „I” în față dar cu „able” la sfârșit, de exemplu Runnable ce face din acea clasă o clasă cu cod ce poate fi executat pe mai multe fire de execuție. Ei, și interfața stabilește contractul. Spune că, dacă vrei să derivezi, tu ca și clasă, dacă vrei să fii derivată din mine, atunci ești obligată să implementezi aceste funcții.
Pe de altă parte și astea sunt condițiile puse de interfață, condițiile puse de clasă sunt simple. Deci dacă pentru o interfață acest lucru e dat de faptul că acea clasă derivată e obligată să le implementeze, clasa derivată poate la fel de bine să își adauge și lucruri în plus, adică trebuie să respecte contractul, dar ce-i pe lângă, că și la mine dacă îmi fac un abonament la o firmă de telefonie mobilă, dacă prin contract nu sunt obligat să nu-mi fac și la alta, pot să îmi fac și la altă firmă de telefonie mobilă. La fel și aici, pot să derivez din oricâte interfețe vreau eu, dacă clasa mea știu că va respecta toate contractele.
Și oarecum, adică acest principiu design by contract se aplică și la Interface Segregation Principle, sau principiul interfețelor segregate. Asta ce înseamnă, că oricând prefer mai multe interfețe specializate decât o singură interfață mare, adică la fel ca și în cazul meu, prefer să am mai multe abonamente, fiecare cu clauze exacte către telefonia mobilă, către cei care mi-au închiriat apartamentul, către cei de la care am energie electrică, decât să am un singur contract mare. Adică aș prefera să fac cu proprietarul un contract prin care mă oblig să îi dau chiria, cu cei de la curent electric un contract prin care mă oblig să plătesc curentul, cu cei de la telefonie prin care mă oblig să plătesc telefonul, decât să fac un singur contract cu proprietarul în care sunt specificate și curentul și telefonul și toate astea. E mai multă bătaie de cap și am mai multe chestii de îndeplinit. Plus că dacă mă supăr și nu mai vreau telefonie de la un anumit operator, va trebui să vorbesc cu proprietarul să modifice în acel contract mare doar clauza asta, cu telefonia mobilă. Pe când dacă chestiile astea sunt separate, mă duc la operatorul respectiv, închid contractul, mă duc la alt operator și îmi fac alt contract. La fel cum spun și aici, nu riscăm astfel ca prin modificarea contractului unui client să modificăm și contractele altor clienți (ceea ce fac de obicei operatorii de telefonie mobilă, dacă stăm bine să ne gândim, dar în fine). Adică, modificăm interfața aia masivă, modificând doar o bucată din interfața aia masivă, asta înseamnă că riscăm oarecum să modificăm și bucăți din clasele derivate efectiv, chiar dacă nu am intuit acest lucru sau nu ăsta era scopul nostru.
Și ajungem la ultimul principiu din SOLID, Dependency Inversion Principle sau Principiul dependenței inversate. Asta îmi spune că o clasă trebuie să depindă tot timpul de abstractizări, niciodată de obiecte concrete. Adică, o clasă trebui să depindă de interfața din care e derivată, niciodată de un obiect de tipul respectiv. Și un caz practic al Dependency Inversion e Dependency Injection, ce permite cuplarea slabă a serviciilor de clienți poate ați auzit de Dependency Injection, el e folosit în AngularJS. AngularJS e o modalitate de a face binding pe client, e o librărie JavaScript creată de Google, de fapt, e mai mult decât o librărie, dacă sunteți interesați puteți căuta pe internet despre Dependency Injection și despre AngularJS. În principiu ce înseamnă, e același lucru aplicabil la joburi și la restul: „Nu ne sunați voi, vă sunăm noi”. Adică am un client și un server. La programarea pe socket-uri de exemplu, când un client se conectează, serverul știe că acel client s-a conectat, se mai conectează unul, știe. Are o listă cu clienți și știe când, de exemplu când un client transmite un mesaj către toți ceilalți să itereze prin lista de clienți și să transmită mesajul fiecăruia în parte. Asta se întâmplă la programarea pe socket-uri care nu respectă acest principiu de Dependency Inversion. Pe când un serviciu web REST, respectă acest principiu de Dependency Inversion. Adică, serverul nu știe de clienți, ci doar clienții știu de server. Vedeți, această dependență e inversată, clientul depinde de server, nu serverul depinde de clienți. Și atunci voi parsa de exemplu unei metode ce face un GET, să spunem, pe serverul acesta REST. Din nou, serviciile web nu sunt scopul acestui tutorial, dacă doriți mai multe detalii, vă rog să căutați despre servicii web și ce înseamnă servicii web REST. Spuneam că, dacă am o funcție ce apelează un serviciu, va trebui ca acel serviciu să îl dau oarecum ca parametru. Chiar e o idee bună și asta se întâmplă și în AngularJS. Deci, primesc serviciul ca parametru, prelucrez, doar când am nevoie de el atunci îl apelez. Adică doar când clientul are nevoie de el atunci îl apelează. Nu trebuie să am niște referințe salvate pe server cu toți clienții, ca să pot apela o metodă, de exemplu, de pe server. La fel se întâmpla și în AngularJS unde modulele principale își anunțau dependențele și atunci când era nevoie, aceste dependențe erau încărcate în acel modul.
0 comentarii