Universitatea Politehnică Bucureşti
Facultatea de Electronică, Telecomunicaţii şi Tehnologia Informaţiei
Arhitecturi si principii O.O.D
Grupa: 443A
Studenţi: Petrescu Theodor Eduard
Stanciu Adrian
Susanu Daniel Cristian
Anul universitar
2014-2015
Cuprins:
Petrescu Theodor Eduard
1. Introducere O.O.D
2. Arhitecturi Software
2.1 Arhitectura Single-Tier
2.2 Dual-Tier Enviroments
-
Three-Tier architecture
-
Arhitectura multi-nivel
3.Validarea datelor
4.Aplicarea corecta a arhitecturii multinivel
Susanu Daniel Cristian
/*...... Designing with Packages
Au intervenit niste probleme si va trebui sa refaca.
.....*/
Stanciu Adrian Principiile Solid & DRY
-
SOLID
6.1 The Single Responsibility Principle
6.2 The Open Closed Principle
6.3 The Liskov Substitution Principle
6.4 The Interface Segregation Principle
6.5 The dependency Inversion Principle
-
DRY
-
Concluzii SOLID & DRY
-
Bibliografie
Object Oriented Design
1.Introducere O.O.D.
Cele mai populare limbaje de programare din prezent sunt limbaje de programare obiect-orientate. Acest lucru înseamnă că o aplicație este divizată în mai multe obiecte de sine stătătoare, asemenea unor mini-programe, fiecare reprezentând o parte a aplicației.
Un obiect, în programare, conține toate caracteristicile unui lucru, o abstractizare a acestuia. Acest lucru este o abstractizare matematică. Obiectele sunt formele independente și încapsulate ale informației.
Comunicarea între obiecte se realizează prin mesaje. Aceste mesaje conțin numele serviciului cerut de obiect și copie a informației necesară execuției serviciului și numele deținătorului rezultatului acestui serviciu. De obicei, mesajele sunt implementate ca proceduri, sau metode.
Obiectele sunt membre ale unei clase ce definește tipurile de atribute și operații. Clasele pot fi organizate într-o ierarhie unde o clasă (super-clasă) este o generalizare a uneia sau a mai multor clase (sub-clase). O sub-clasă moștenește atributele și operațiile de la super-clasă dar i se pot adăuga noi metode și atribute. Clasele reprezintă un set de obiecte cu același comportament, entitățile cu mai multe referințe în descrierea aplicației sunt potrivite, aflarea punctelor comune și proiectarea claselor pentru a capta aceste puncte comune.
Identificarea obiectelor este cea mai dificilă parte a design-ului deoarece acest lucru ține de priceperea programatorului și nu există vreo formulă. Pentru o indetificare mai facilă trebuie folosit un limbaj bazat pe descrierea sistemului, pe lucruri tangibile în domeniul aplicației, folosirea unei analize bazate pe scenariu, unde obiectele, atributele și metodele sunt ușor de identificat în fiecare scenariu.
Interfețele obiectelor trebuie specificate astfel ca celelalte obiecte și componente să fie create în paralel.
Relațiile între clase:
-Moștenirea
-Agregarea
-Dependența
Moștenirea este relația dintre o clasă generalizată și una specializată. Dependența este relația în care o clasă depinde de altă clasă.
Agregarea este relația în care o clasă conține referințe la obiectele altei clase. Aceasta este o formă mai puternică a dependenței.
În programarea orientată pe obiect nu se impun constrângeri legate de stilul și de cum decurge elaborarea codului programului, însă este de preferabil repectarea anumitor pași în realizarea unui produs software. Acești pași reprezintă ciclul de creare al unui program din stadiul de concept până la lansarea sa. Cel mai simplu și intuitiv este modelul cascadă. Astfel, ciclul de viață al unui produs software pornește din stadiul de Cerințe: ce încapsulează toate datele ce ajută la determinarea nevoilor și condițiilor în vederea realizării unui produs nou.
În etapa de design se creează un simulare primitivă a produsului în vederea sstabilirii funcționării produsului. În următoarea etapă, cea de implementare, se realizează codul și toată structura complexă a produsului, după care se intră în etapa de verificare și testare în care se verifică eventualele bug-uri și greșelile de cod, și produsul se testează în condiții normale și la limită. În final este etapa de mentenanță și optimizare în care se asigură depanarea și lansarea de update-uri pentru reparearea eventualelor problem apărute după lansarea produsului.
http://en.wikipedia.org/wiki/Object-oriented_analysis_and_design#mediaviewer/File:Waterfall_model_revised.svg
Arhitecturi Software în O.O.D.
2.Arhitectura
Arhitectura software în O.O.D. este procesul de definire a unei soluții structurate ce îndeplinește toate condițiile operaționale și tehnice și optimizează atribute comune de calitate precum securitate, performanță și accesabilitate.
Ca orice altă structură complexă, software-ul trebuie construit pe o fundație solidă. Omiterea unor scenarii cheie sau a unui design pentru probleme comune poate periclita funcționalitatea aplicației. Riscul presupus prin crearea unei arhitecturi păguboase este Acela că aplicația este instabilă, nu îndeplinește anumite cerințe, sau este dificilă de implementat într-un mediu de producție.
Arhitectura software poate fi de mai multe tipuri:
-
Arhitectura Single-Tier
-
Arhitectura Dual-Tier Enviroments
-
Arhitectura Three-Tier
-
Arhitectura N-Tier / Multitier
Arhitectura software este una multi-nivel. La cel mai înalt nivel se află modele ce definesc forma și structura aplicațiilor software. Un nivel mai jos este rezervat scopului aplicației, iar nivelul cel mai de jos se află arhitectura modulelor și conexiunile între ele.
Astfel vom trata tipurile de arhitecturi în ordine.
2.1. Ahirectura Single-Tier
Acest tip de arhitectură se folosea la începuturile erei informaționale, în jurul anilor 1960, o dată cu apariția mainframe-ului. Această arhitectură consta în faptul că toate resursele se aflau pe un singur computer, toată procesarea se executa de un același computer, iar accesul la date se face prin intermediul așa-ziselor terminale „dumb” – un monitor fără abilități de procesare, care doar acceptă date de la procesorul de pe terminalul ce realizează procesarea. De aceea se explică utilizarea mainframe-ului ca element central, deoarece era singura mașină capabilă să execute multe operații.
Avantajele acetui tip de arhitectură sunt: simplitatea, eficiența și lipsa complexității.
Marele dezavantaj însă este prețul terminalului central, acesta fiind unul foarte ridicat deoarece aceasta trebuia să fie foarte performant.
2.2. Dual-Tier Enviroments
Această arhitectură este asemănătoare unei aplicații client-server. Comunicarea este realizată direct între client și server, fără intermediari. A fost influențată de apariția computerului personal (PC) și, implicit, de necesitatea de produse software pentru acesta. Cele două niveluri constau în:
a) nivelul bazelor de date
b) al doilea nivel este reprezentat de aplicația clientului ce se conectează la baza de date și conține majoritatea componentelor logice
Diferența între cele două niveluri este că serverul răspunde cererilor mai multor clienți, în timp ce clienții inițiază cereri de informație de la un singur server.
Avantajele acestui tip de arhitectură sunt:
-întelegerea software-ului și mentenanța acestuia este mai ușoară
-este mult mai ieftin față de un mainframe
Dezavantaje:
-conexiunile între serverul bazei de date și terminalul clientului sunt scumpe
-numărul conexiunilor la un singur server este limitat, dacă este foarte mare atunci serverul va aloca mai mult timp gestionării conexiunilor decât procesării cererilor
-scade performanța odată cu creșterea numarului de terminale ale clientului
-ineficient din punct de vedere al costului, în timp ce conexiunea către server poate fi aglomerată, terminalele clientului folosesc doar 2-3% din conexiune.
2.3.Three-Tier arhitecture
Odată cu apariția internetului multă lume credea că soluția este întoarcerea la mainframe, deoarece arhitectura Dual-Tier a eșuat și apariția terminalelor thin client a contribuit la apariția arhitecturii pe trei niveluri. Se bazează pe arhitectura client-server în care prezentarea, procesarea aplicației și managementul datelor sunt procese logice separate. Cele trei niveluri sunt:
-nivelul de prezentare
-nivelul de aplicație
-nivelul de date
Un nivel poate accesa direct doar componentele publice ale nivelului de sub el. Spre exemplu, nivelul de prezentare poate accesa componente publice doar din nivelul de aplicație nu și din nivelul de date. Astfel, se reduce dependența între niveluri. Această reducere are beneficii la securitate, dezvoltare, mentenanță, scalabilitate etc. Pentru a avea o arhitectură Three-Tier trebuie ca fiecare nivel să fie capabil să ruleze pe computere separate.
Nivelul de prezentare conține designul interfeței grafice și poate fi accesat direct de user. Nivel denumit de altfel și client.
Nivelul de aplicație este nivelul intermediar care are funcțiile pentru nivelul de prezentare și are rolul de a face mai rapidă comunicarea între nivelul de prezentare și nivelul de date. Furnizează puterea de procesarea și accesul la date.
Nivelul de date este un server unde este stocata baza de date.
Avantaje:
-scalabilitate: nivelurile pot fi amplasate pe diferite mașini, iar baza de date nu mai necesită o conexiune cu fiecare client, ci de la un număr mai mic de servere de aplicație
-securitate crescută
-crește disponibilitatea
-portabilitate
-performanță crescută
Dezavatanje:
-complexitate crescută
2.4.Arhitectura multi-nivel
Această arhitectură este o arhitectură cu 3 sau mai multe niveluri. La baza este arhitectura pe 3 niveluri, însă câteva niveluri din această arhitectură vor fi sparte în alte niveluri. Astfel, nivelul de aplicație poate fi spart în nivelul bussines, nivelul persistență, iar nivelul de prezentare poate fi alcătuit din nivelul client și nivelul de prezentare pentru client.
Nivelul client este nivelul ce are contact direct cu userii. Pot exista diferite tipuri de clienți ce coexistă cum ar fi HTML, Windows, WPF.
Nivelul de prezentare client conține logica necesară prezentării clientului, cum ar fi ASP .net într-un server web. Se adaptează la diferiți clienți din nivelul business.
Nivelul business conține și dirijează toate domeniile și logica necesară și adaptează diferiți clienți la acest nivel. Se mai numește (Domain Layer).
Nivelul de persistență gestionează citirea/scrierea în nivelul business, se mai numește DAL (Data Access Layer).
Nivelul de date conține sursa externă de date, cum ar fi baza de date.
Diferența dintre arhitectura pe trei niveluri și cea multinivel este aceea ca la multinivel absolut toate nivelurile trebuie sa ruleze pe sisteme separate. Dacă folosim baze de date non-embedded cum ar fi SQL Server, Oracle etc. atunci aceste baze de date pot rula pe un sistem propriu, de sine stătător.
Avantaje:
-scalabilitate datorită compabilității pe mai multe niveluri.
-securitate sporită deoarece fiecare sistem are securitatea sa
-toleranță ridicată la defecțiuni, se poate crea un sistem de migrare a unui server în caz de defecțiune
-upgradare independetă a fiecărui nivel
-mentenanță ușoară
Dezavantaje:
-performanțe scăzute dacă conexiunile sunt slabe
-costuri ridicate
3.Validarea datelor
Validarea datelor în arhitectura multinivel este foarte importantă pentru a ține întreg sistemul funcțional în parametrii optimi. Validarea se poate face în orice nivel, însă cu cât validarea se face mai aproape de nivelul client cu atât performanța este mai bună, iar cu cât e mai departe cu atât aplicația este mai fiabilă. Astfel este necesar un compromis performanță/fiabilitate.
Validarea datelor este procesul ce asigură unui program un mediu curat, corect și perfect funcțional în care acesta să ruleze. Acesta folosește constângeri ca:
-reguli de validare pentru verificarea corectitudinii
-constrângeri de validare pentru verifiarea înțelegerii
-rutine de verificare penrtu verificarea securității
Pentru a decide ce nivel execută validarea trebuie luată o decizie în funcție de condiții. Dacă avem control total asupra tuturor nivelurilor, putem lăsa validarea să fie efectuată în nivelul client, dar dacă nivelul business este expus nivelului client, lucru ce nu este la latitudinea programatorului, atunci nivelul business trebuie să execute validarea pentru a crește fiabiliate chiar dacă clienții fac aceeași validare.
4.Aplicarea corectă a arhitecturii multinivel
Mai multe niveluri cresc nivelul de complexitate, costurile și efort pentru mentenanță. Prin urmare, numărul lor trebuie să fie minim posibil pentru a rezolva probleme cum sunt scalabilitatea, securitatea, redundanța etc. dacă aceste probleme sunt rezolvate cu ceea ce este la dispoziție atunci alte niveluri nu mai sunt necesare. Dar recomandat este folosirea arhitecturii pe trei niveluri cel putin.
Principipiile SOLID & DRY 6.SOLID Introducere
In programare, SOLID este un acronim introdus de Michael Feathers pentru "first five principles", numite de Robert C. Martin la începutul anilor 2000 care provin de la cinci principii de baza ale programarii orientate obiect si ale OOD-ului. Principiile, atunci cand sunt aplicate concomitent, au rolul de a creste sansa ca un programator sa creeze un sistem care este usor de mentinut si de dezvoltat in continuare.
Principiile SOLID reprezinta un set de reguli ce pot fi aplicate in timp ce se lucreaza asupra softului pentru a indeparta “code smells”( erori de logica ce pot aparea in anumite situatii) si obliga programatorul sa revizuiasca codul sursa pana cand este lizibil si extensibil. Este parte a unei strategii generale de programare AGILE.
Aceste principii ilustreaza aspecte de management-ul dependentelor de OOD in proiectarea aplicatiilor software care nu exclud aspectele de conceptualizare si modelare a acestor aplicatii, ci din contra,pentru proiectarea unei astfel de aplicatii ele lucreaza impreuna. De aceea din ce in ce mai multi programatori dau o importanta sporita problemei de management al dependetelor.
Aceasta problema este des intalnita si fiecare programator si-o poate aminti atunci cand vede un cod “urat” din cauza nerespectarii conventiilor standard, mostenire incalcita, etc. avand ca urmare un cod greu de modficat, fragil si care nu va putea fi reutilizat.
De aceea,respectarea principiilor SOLID duce la punerea unei “temelii solide” in construirea aplicatiei software datorita faptului ca dependentele sunt bine gestionate astfel codul rezultat este flexibil, robust si reutilizabil.[8]
In continuare vom detalia cele 5 principii care formeaza si acronimul SOLID :
-
SRP(The Single Responsibility Principle)- Principiul unei singure responsabilitati.
De la acest principiu avem “S-ul” ,el afirma ca o clasa ar trebui sa aiba un singur motiv de schimbare.
-
OCP(The Open Closed Principle) - Principiul “Deschidere Inchidere”.
De la acest principiu avem “O-ul”.El afirma ca o clasa ar trebui sa fie capabila sa extinda functionalitatile unei alte clase fara ca ea sa se modifice.
-
LSP(The Liskov Substitution Principle) – Principiul Substitutiei Liskov.
De la acest principiu avem “L-ul”.El afirma ca pot fi substituibile clase derivate pentru clasele lor de baza.
-
ISP(The Interface Segregation Principle) – Principiul Segregarii Intefetei.
De la acest principiu avem “I-ul”.El afirma ca o clasa nu trebuie sa depinda de metodele unei interfete pe care nu le foloseste.
-
DIP(The dependency Inversion Principle) – Principiul Inversiunii Dependetei.
De la acest principiu avem “D-ul”.El afirma ca o clasa trebuie sa depinda de abstractizare si nu de concretizare.
6.1.Principiul unei singure responsabilitati
Acest principiu a fost enuntat de Tom DeMarco in cartea sa “Structured Analysis and Systems Specification” in 1979. Robert Martin a reinterpretat acest concept si i-a definit responsabilitatea ca un motiv de schimbare.[2]
Responsabilitatea este definita ca o sarcina atribuita unei functionalitati ale unei clase/modul/aplcaitii.
In acest context o responsabilitate este considerata ca fiind un singur motiv de schimbare. In cazul in care avem doua motive de schimbare pentru o clasa, atunci trebuie sa despartim functionalitatile in doua clase diferite. Fiecare clasa se va ocupa doar de o singura responsabilitate si pe viitor daca avem nevoie sa facem o schimbare,o vom face in clasa corespunzatoare. Daca am face o schimbare intr-o clasa care are mai multe responsabilitati am putea altera si alte functionalitati ale clasei.
La nivel teoretic SRP este simplu si intuitiv, dar in practica este uneori greu de realizat si necesita chiar si unele compromisuri.
In continuare vom lua urmatorul exemplu pentru a intelege mai bine acest concept si efectele incalcarii lui.
Consideram o clasa Rectangle care contine doua metode: draw() si area(); prima proiecteaza pe ecran dreptunghiul,iar celalata calculeaza aria.
(referinta bibliografica [4])
Aplicatia din stanga este folosita pentru a face operatii geometrice si foloseste metoda area(), dar nu va desena niciodata dreptunghiul, in schimb cea de-a doua aplicatie este folosita in mod special pentru afisarea pe ecran a formei dreptunghiului.
Aceasta proiectare incalca principiul SRP,deoarece are doua functionalitati, iar acest lucru poate duce la situatii neplacute. Spre exemplu,trebuie sa includem GUI in aplicatia 1 si daca ar fi cazul unei aplicatii in C++ interfata grafica ar fi trecut prin etapa de linkeditare,preprocesare,compilare si astfel consuma timp si memorie.[4] Acelasi lucru se intampla si in cazul unei aplicatii java, fisierul .class pentru GUI trebuie sa fie pus (“deployata”) pe platforma JVM ( masina virtuala). In al doilea rand,daca o schimbare in aplicatia 2 se propaga si in clasa Rectangle, atunci suntem obligati sa facem din nou rebuild, retest si redeploy. In cazul in care omitem acest lucru,aplicatia nu va functiona .
Un design mai bun care va respecta acest principiu este urmatorul:
Acest design imparte vechea clasa in doua noi clase separand responsabilitatile. Acum, eventualele schimbari in clasa Rectangle nu vor afecta si Computational Geometry Application.
Concluzie: SRP este unul dintre cele mai simple principii dar si greu de realizat in practica. Imbinarea responsabilitatilor este ceva ce facem in mod natural, dar design-ul software chiar in acesta consta: gasirea unei cai de separare a acestora.
6.2.Principiul Open Closed
O aplicatie proiectata intr-un mod inteligent ar trebui sa aiba grija de schimbarile frecvente care pot aparea in timpul dezvoltarii sau mentenantei ei. De obicei apar multe schimbari cand adauga o noua functionalitate aplicatiei. Aceste schimbari in codul existent ar trebui sa fie reduse pe cat posibil la minimum, deoarece se presupune ca acest cod este deja testat si ar putea altera functionalitatile existente deja.
OCP afirma ca proiectarea si scrierea codului ar trebui sa se faca intr-o maniera in care noua functionalitate trebuie sa fie adaugata cu modificari minime in codul existent. Design-ul trebuie sa permite adaugarea de noi functionalitati prin noi clase/module pastrand cat mai mult posibil codul neschimbat[3].
Programatorul trebuie sa tina cont atunci cand proiecteaza o clasa pentru ca ea nu trebuie sa fie modificata in cazul extinderii comportamentului ,ci doar sa permita aceasta extindere.
Acest principiu este format din doua parti:
-
“Open for extension” (deschidere pentru extindere): comportamentele unei clase pot fi extinse, ceea ce permite adaugarea de noi caracterestici ;
-
“Closed for modification” (inchidere pentru modificare): nu se permite modificarea codului clasei.
Secretul acestui principiu consta in abstractizare. Utilizam clase abstracte si clase concrete pentru implementarea comportamentelor lor. Astfel fortam clasele concrete sa mosteneasca clasele abstracte in loc sa le modifice. Design Patern-uri care sunt folositi des,avand acest scop sunt Template Pattern si Strategy Pattern.
Alte conventii sunt acelea de a face toate variabilele membre private. Motivul este clar.Daca o variabila a unei clase se modifica,atunci fiecare functie care depinde de ea va suferi modificari, facandu-le private clasele extinse din clasa respective nu vor avea acces la aceea variabila,deci nu vor trebui modificate.[6]
In continuare vom considera urmatorul exemplu care incalca acest principiu, iar apoi oferim un exemplu care respecta OCP.
Cosideram un editor grafic ca si clasa care are ca scop desenarea unor forme diferite. Dupa cum se poate observa si in imaginea de mai jos este evident ca el nu respecta acest concept,deoarece aceasta clasa trebuie sa fie modificata pentru fiecare forma care trebuie sa fie adaugata. Exista mai multe dezavantaje: pentru fiecare forma noua trebuie facute din nou teste unitare, adaugand o noua forma functionalitatea ar putea afecta aplicatia intr-un mod nedorit chiar daca noua forma functioneza perfect, in plus,programtorul care adauga o noua functionalitate trebuie sa inteleaga logica de functionare a clasei GraphicEditor si ,deci, timpul pentru adaugarea functionaltatii se va mari.
Diagrama cu un design care incalca OCP
(referinta bibliografica [11])
In imaginea urmatoare se arata cum ar fi trebuit realizat acest design al aplicatiei software astfel incat sa respecte OCP. In clasa GraphicEditor vom avea o metoda abastracta draw() pentru desenarea formelor, in timp ce implementarea se va face in formele concrete. Astfel adaugarea unei noi forme nu va modifica si clasa GraphicEdtior, respectand astfel acest principiu. In plus,nu va trebui sa facem un nou test unitar, nu va trebui sa intelegem codul din clasa GraphicEditor si reduce si riscul de a altera si alte functionalitati atunci cand introducem una noua.[11]
Diagarama unui design care respecta OCP
(referinta bibliografica [11])
Concluzie: Acest principiu este considerat inima OOD-ului, dar ca toate principiile ramane doar un principiu si de multe ori respectarea lui implica mult timp si efort, precum si un grad sporit de complexitate. Exista si design pattern-uri care sa ne ajute sa urmarim acest concept,cum ar fi Decorator/Factory/Observer Pattern.
6.3.Principiul Substitutiei Liskov
Acest concept a fost introdus de Barbara Liskov in 1987 la conferinta “Object Oriented Programming Systems Languages and Aplication”.
Barbara a declarat umatoarea fraza: “ Daca pentru fiecare obiect o1 al tipului S exista un obiect o2 al tipului T astfel incat pentru orice program P comportamentul lui P este neschimbat cand o1 inlocuieste o2 atunci S este un subtip al lui T ”.[9]
Acest principiu este o extensie a Principiului Open Closed, de aceea pentru a intelege comportamentul trebuie sa fim siguri ca noile clase derivate extind clasele de baza fara a le schimba comportamentul . Clasele derivate noi ar trebui sa inlocuiasca clasele de baza fara a produce vreo schimbare in cod.
Pentru ca o clasa derivata sa poata inlocui clasa ei de baza (sa se respecte LSP) trebuie sa se respecte proprietatile:
-
Preconditiile sa nu fie mai puternice decat in metoda clasei de baza.
-
Postconditiile sa nu fie mai slabe decat in metoda din clasa de baza.
-
Invarianta din supertip trebuie sa se conserve si in subtip.
Cu alte cuvinte,metodele derivate ar trebui sa nu astepte nici mai mult nici mai putin.
Vom considera urmatorul exemplu care incalca LSP-ul. Avem 2 clase Rectangle si Square . Presupunem ca obiectul Rectangle este folosit undeva in aplicatie. Vom extinde aplicatia si clasa Square. Consideram ca aceasta clasa Square este proiectata printr-un Factory Pattern bazata pe anumite conditii si de aceea nu stim exact ce tip de obiect va fi returnat, dar stim ca este un Rectangle.
Presupunand ca exista metode getter si setter atat pentru latime cat si pentru lungime. Clasa Square intotdeauna presupune ca latimea este egala cu inaltimea. Daca o clasa Square este folosita intr-un context unde un Rectangle este asteptat , se va produce un comportament anormal,deoarece dimensiunile unui Square nu pot ( sau mai degraba,nu ar trebui) sa fie modificate independent.
Aceasta problema nu poate fi usor reparata.Daca am putea modifica metodele setter in clasa Square pentru a o pastra invariabila,atunci aceste metode vor incalca postconditiile pentru setterul Rectangle, care presupune ca dimensiunile pot fi modificate independent, astfel incalcand acest principiu. [9]
Se observa din exemplul din imagine cum si invers se incalca principiul Liskov Substitution.
Exemplu de incalcare a conceptului LSP
Concluzie: Acest principiu,reprezintand o noua caracteristica ce trebuie luata in considerare in orice proiectare a unei aplicatii software,fiind o extindere a conceptului OCP,semnifica faptul ca noile clase derivate extind clasele de baza fara a le modifica comportamentul.
6.4.Principiul Segregarii Interfetei
Atunci cand proiectam o aplicatie trebuie sa tinem cont de cum vom trata o clasa/ un modul abstract care contine si alte submodule. Daca vrem sa extindem aplicatia noastra adaugand un nou modul care nu contine toate submodulele clasei initiale,atunci suntem obligati sa implementam toate functionalitatile interfetei si sa scriem niste metode nefolositoare. O astfel de metoda poarta numele de “fat interface” sau “polluted interface” si poate aduce un comportament necorespunzator aplicatiei.
Acest principiu ne invata cum sa scriem interfete intr-un mod cat mai eficient. Cand proiectam o aplicatie si scriem propriile noastre interfete trebuie sa avem grija sa adaugam doar metodele care ar trebui sa se afle acolo, altfel clasa ce va implementa interfata va fi obligata sa implementeze toate metodele,ceea ce ofera un design prost.[5]
Exista cel putin 2 posibilitati de evitare a incalcarii acestui principiu:
-
“Separarea prin delegare”: In acest caz se utilizeaza in design-ul aplicatiei Adapter Pattern care va delega doar catre interfata necesara. Problema este rezolvata, dar intr-un mod mai putin elegant,deoarece se va creea de fiecare data un nou obiect rezultand timp si memorie in plus. In cazul aplicatiilor cu sisteme de control in timp real este foarte important acest aspect si trebuie tratat corespunzator.
-
O alternativa reprezinta “Separarea prin mostenire multipla” . Aceasta abordare este mai eleganta si rezolva problema consumului de memorie si de timp, dar uneori este o metoda mai laborioasa pentru ca lantul de mostenire ar putea fi unul mai complex.
Ambele posibilitati au avantaje si dezavantaje, iar in functie de aplicatie ne putem orienta catre una din alternative.
In continuare vom ilustra urmatorul exemplu care nu respecta ISP. Consideram clasa Manager care are 2 feluri de muncitori: roboti si muncitori normali care implementeaza interfata IWorker. Aceasta Interfata contine doua metode de implementat work() si eat(), dar robotul nu are nevoie sa manance,ceea ce inseamna ca am realizat un design prost.
Diagrama unui design care incalca ISP
Pentru a realiza un design bun vom imparti interfata IWorker in doua interfete cu responsabilitati diferite(IFeedable si IWorkable). Clasa Robot nu va mai fi fortata sa implementeze interfata IFeedable si va rezulta un design reusit.
Un design care respecta conceptul de ISP
Acest exemplu este unul simplu,dar in cadrul unei aplicatii cu foarte multe module necesitatea spargerii interfetelor in “subinterfete” este mult mai mare.
Concluzie: In cazul in care design-ul este deja facut prost putem segrega intefertele folosind Adapter Pattern. Ca orice alt principiu abordarea lui necesita timp si efort suplimentar crescand si gradul de complexitate al aplicatiei. Daca il aplicam mai mult decat este cazul va rezulta un cod care contine o multime de interfete cu o singura metoda, de aceea aplicarea lui trebuie sa se faca pe baza unei experiente si o viziune in identificarea zonelor care vor putea fi extinse in viitor.[6]
6.5.Principiul Inversiunii Dependentei
In cazul proiectarii unei aplicatii software putem considerea ca avem de-a face cu doua tipuri de module/clase. Clase Low Level (LL) care abordeaza operatii de baza (conectarea cu diferite protocoale de retea,accesarea disk-ului,etc) si clase High Level (HL) care trateaza logica aplicatiei, de cele mai multe ori fiind operatii mult mai complexe. Acest aspect este tratat cu ajutorul acestui principiu.[7]
Conceptul DIP contine 2 proprietati:
-
Modulele HL nu trebuie sa depinda de modulele LL ,dar ambele ar trebui sa depinda de abstractizare.
-
Conceptul de abstractizare nu trebuie sa depinda de detalii, dar detalile trebuie sa depinda de abstractizare.
Fluxul de lucru este urmatorul:
High Level Classes --> Abstraction Layer --> Low Level Classes
DIP afirma ca este necesar sa decuplam modulele HL de cele LL introducand un nivel abstract intermediar intre aceste doua nivele. Prin urmare,se inverseaza rolurile: in loc sa scriem modelele noastre abstracte in functie de detalii,vom trata detalile in functie de nivelul abstract.
De la acest principiu provine specificatia de Inversion of Control(IoC) sau Dependecy Injection din multe framework-uri (Spring,AngularJS,etc) care este folosita pentru rezolvarea dependetelor intre modulele unei aplicatii sau chiar intre aplicatii.
In scopul de a decupla un modul M1 de alt modul M2 este necesar modulul M1 sa puna la dispozitie un parametru sau o proprietate si sa existe un modul extern care va controla dependetele si le va injecta ca referinta catre modulul M2 prin intermediul proprietatii sau parametrului. Aplicand acest concept modulele pot fi usor de inlocuit cu altele doar schimband modulul de control al dependentelor. Factory si Abstract Factory pot fi folosite pentru aplicarea acestui principiu, dar in ultima perioada se utilizeaza framework-uri specializate cum este IoC.[7]
In continuare vom arata un exemplu simplu care nu respecta acest principiu. Consideram ca avem 3 module: Copy (HL), ReadKeyboard (LL) si WritePrinter (LL). Dupa cum se vede si in figura de mai jos modulele LL depind de cea HL. In cazul in care vom adauga un device nou,spre exemplu: Scanner va trebui sa modificam si modulul Copy.
Design-ul nu respecta DIP
In imaginea urmatoare se arata ca introducand un nivel intermediar abstract, componentele LL numai depind in mod direct de cele HL,ci de nivelul abstract, astfel se realizeaza inversiunea dependetelor.
Design care respecta DIP
(referinta bibliografica [8])
In acesta situatie introducand un nou device el va depinde de nivelul abstract si nu de modulul HL, facilitand flexibilitate si reutilizarea codului.
Concluzie: Utilizarea acestui principiu creste gradul de complexitate si efort, mententanta va fi mai dificila deoarece vor creste numarul interfetelor si claselor, dar vom castiga in flexbilitate. Obiectele din clasele LL ar trebui create dupa design pattern-uri creationale ca Factory,Prototype,AbstractFactory si nu direct prin operatorul “new”. Acest concept nu trebuie aplicat in situatia in care o clasa trebuie sa ramana nemodificata,ci doar atunci cand este necesar decuplarea dintre module.
7.Principiul Don’t repeat yourself (DRY)
In ingineria software DRY este un principiu de dezvoltare si proiectare a aplicatiilor software care vizeaza reducerea repetarii de informatie de orice tip, util mai ales in arhictecturi multi-tier. Acest concept afirma ca fiecare “piece of knowledge”(informatie) trebuie sa aiba o singura reprezentare in sistem si aceasta reprezentare trebuie sa fie lipsita de ambiguitate.[1]
Principiul a fost formulat de catre Andy Hunt si Dave Thomas in carte lor “The Pragmatic Programmer”. Aplicabilitatea acestui principiu este una destul de diversa incluzand scheme ale bazelor de date, planuri de testare, build system,chiar si documente. Atunci cand DRY este aplicat cu success o modificare a oricarui element dintr-un sistem nu necesita o schimbare in logica altor module necorelate. In plus, elementele care sunt expuse intr-un mod logic se schimba predictibil si uniform si sunt astfel mentinute sincronizate.In afara de folosirea metodelor si subrutinelor in codul lor Thomas si Hunt se bazeaza pe generatoare de cod, A.B.S.-uri(automatic build systems) si limbaje de scripting sa respecte acest concept.Beneficile aduse prin folosirea DRY-ului:[1]
-
Scrierea codului o singura data,folosirea lui de ori de cate ori dorim
-
Modificarea codului intr-un loc se va propaga in tot design-ul aplicatiei
-
Usor de mentinut si de inteles
-
Incurajeaza o abordare structural in dezvoltarea aplicatiilor
De asemenea,cunoscut si sub numele de Single source of Truth, aceasta filozofie este predominanta in arhitecturile model-driven, in care artefactele software sunt derivate dintr-un model de obiect centrat exprimat intr-o forma cum ar fi UML. Codul DRY este creat de transformari de date si generatoare de coduri care permit dezvoltatorilor software evitarea copierii si lipirii operatiilor. Codul DRY face de obicei sistemele software de mari dimensiuni usor de intretinut atata timp cat transformarile de date sunt usor de creeat si mentinut.Un sistem care necesita duplicarea codului este Enterprise Java Beans versiunea 2. Sisteme care incearca sa reduca duplicarea informatiei sunt:Yii,Play Framework Django,Ruby on Rails,etc.[1]
Concluzii: Acest principiu este simplu,dar in practica uneori poate fi mai dificil de aplicat, are o aplicabilitate vasta si nu se foloseste doar cand scriem cod. Orice proiectant de aplicatie software trebuie sa aiba in vedere aplicarea acestui concept.
8.Concluzii despre aplicare conceptelor SOLID & DRY
Daca in trecut un programator trebuia sa stie doar cum sa aplice conceptele programarii orientate obiect,cum ar fi:mostenire,polimorfism,suprascriere etc. ,acum nu il mai putem numi un programator daca se limiteaza doar la atat. Este important sa stim sa scriem cod ,dar poate mai important este sa stim cum sa scriem cod. Software design este vazut din ce in ce mai mult ca o punte de legatura intre cerinte si cod, de aceea trebuie tratat foarte serios si acest aspect de design.
Respectarea OOD-ului implica implicit respectarea principiilor SOLID si DRY, care dupa cum a fost evidentiat mai sus ,evita problemele de fragilitate, rigiditate, imobilitate a codului ,facand-ul mult mai usor de reutilizat, imbunatatit, mentinut si mai flexibil.
Totusi sa amintim faptul ca,desi ar fi ideal respectarea concomitenta a acestora, in practica exista situatii in care suntem obligatii sa facem anumite compromisuri si ,desi la nivel teoretic uneori par usor de implementat, in cadrul aplicatiilor complexe poate deveni mult mai laborios.In cazul in care design-ul este deja facut prost putem folosi diferite Design Pattern-uri (ex.: Adapater Pattern) ceea ce necesita timp si efort suplimentar crescand si gradul de complexitate al aplicatiei.
Beneficiul este remarcat cand SOLID &DRY sunt aplicate cu success. O modificare a oricarui element dintr-un sistem nu necesita o schimbare in logica altor module necorelate. In plus,elementele care sunt expuse intr-un mod logic sunt flexibile,reutilizabile,se schimba predictibil si uniform si sunt astfel mentinute sincronizate.
9.Bibliografie:
-
[1]http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
-
[2] http://codebetter.com/raymondlewallen/2005/07/19/4-major-principles-of-object-oriented-programming/
-
[3] http://www.codeproject.com/Articles/567768/Object-Oriented-Design-Principles
-
[4] http://goose.ycp.edu/~dhovemey/spring2011/cs320/lecture/lecture14.html
-
[5] http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
-
[6] http://c2.com/cgi/wiki?PrinciplesOfObjectOrientedDesign
-
[7] http://zeroturnaround.com/rebellabs/object-oriented-design-principles-and-the-5-ways-of-creating-solid-applications/
-
[8 ]Agile Software Development, Principles, Patterns, and Practices(Robert C.Martin)
-
[9] Design Principles and Design Patterns - Robert C. Martin
-
[10] Java Design: Objects, UML, and Process De Kirk Knoernschild
-
[11] https://detailfocused.wordpress.com/2008/07/16/the-open-close-principle-ocp/
Dostları ilə paylaş: |