Sunteți pe pagina 1din 8

Proiectarea Sistemelor Software Complexe

Curs 3 Principii de Proiectare Orientat pe Obiecte

Principiile de proiectare orientat pe obiecte au fost formulate pentru a servi ca reguli pentru evitarea
proiectrii unei arhitecturi proaste. Principiile au fost propuse de ctre Robert Martin n lucrarea
intitulat Agile Software Development: Principles, Patterns, and Practices[1]. n opinia lui Robert Martin
exist trei caracteristice care definesc o arhitectur proast i care trebuie evitate:
- rigiditatea sistemul este greu de modificat pentru c fiecare modificare afecteaz prea multe
pri ale sistemului;
- fragilitatea dac se modific ceva, apar tot felul de erori neateptate;
- imobilitatea este dificil s se reutilizeze pri dintr-o aplicaie pentru c nu pot fi separate de
aplicaia pentru care au fost dezvoltate iniial.

1.1 Principiul Singurei Responsabiliti (Single Responsibility Principle)

Enun: There should never be more than one reason for a class to change.
(Niciodat nu trebuie s existe mai mult de un motiv pentru a modifica o clas.)
n contextul acestui principiu prin responsabilitate se nelege un motiv de a modifica . Dac pot fi
gsite mai multe motive pentru a modifica o clas, nseamn c acea clas are mai multe
responsabiliti. Acest principiu spune faptul c o clas nu trebuie s aib mai multe responsabiliti
fiindc orice modificare la nivelul cerinelor se reflect printr-o modificare la nivelul uneia sau mai
multor responsabiliti care se propag mai departe la nivelul claselor. Astfel, dac o clas
implementeaz mai multe responsabiliti automat pentru acea clas la un moment dat va exista mai
mult de un motiv pentru a fi modificat. n plus se poate ajunge la un efect de domino, i anume
modificarea unei responsabiliti poate duce la introducerea unor erori n implementarea
corespunztoare altei responsabiliti. Se ajunge astfel la o interdependen nedorit ntre
responsabiliti.
n continuare principiul va fi ilustrat printr-un exemplu. n Fig. 3.1 este prezentat o diagram de clase n
care apare clasa Rectangle care are dou metode: draw() i area(): double. Prima metod este
responsabil de desenarea formei geometrice dreptunghi pe ecran, cea de a dou calculeaz suprafaa
dreptunghiului.
n plus clasa Rectangle este folosit n dou aplicaii diferite. O aplicaie face calcule geometrice:
folosete clasa Rectangle doar pentru calcule matematice i nu folosete funcionalitatea de desenare.
Cea de a dou aplicaie folosete att metoda de desenare ct i cea ce calcul al suprafeei.
n acest exemplu clasa Rectangle ncalc principiul separrii responsabilitilor. Clasa are dou
responsabilitii: (1) implementeaz modelul matematic al formei geometrice dreptunghi i (2)
deseneaz dreptunghiul pe o interfa grafic.

1
Fig. 3.1. Mai mult de o responsabilitate ntr-o singur clas.
Violarea principiului separrii responsabilitilor n exemplul din Fig. 3.1 duce la apariia a dou
probleme. Avnd n vedre faptul c pentru a desena pe interfaa grafic clasa Rectangle folosete clasa
GUI este evident faptul c aceast clas va trebui s fie disponibil i n aplicaia de calcule geometrice
dei aceast aplicaie nu folosete funcionalitatea de desenare. Acest lucru nseamn timp de compilare
mai lung i o dimensiunea mai are a executabilului. A doua problem const n faptul c, dac clasa
Rectangle este modificat pentru a rezolva o problem n partea de desenare acest lucru va necesita nu
doar recompilarea i retestarea aplicaiei de desenare ci i a celei de calcul geometric.
n Fig. 3.2 este prezentat diagrama de clase din Fig. 3.1 dar corectat astfel nct s nu existe mai mult
de o responsabilitate pe clas. Astfel a fost adugat clasa GeometricRectangle care implementeaz
modelul matematic al unui dreptunghi, obinndu-se astfel o decuplare a celor dou responsabiliti. n
acest nou design modificrile fcute la partea de desenare nu mai influeneaz aplicaia de calcule
geometrice.

Fig. 3.2. O singur responsabilitate pentru fiecare clas.

1.2 Principiul Deschis-nchis (Open-Close Principle)

Enun: Software entities (classes, modules, functions, etc.) should be open for extension, but closed for
modification.
(Entitile software (clasele, modulele, funciile etc.) trebuie s fie deschise n ceea ce privete
extinderea, dar nchise n ceea ce privete modificarea.)

2
Atunci cnd o singur modificare la un program genereaz o serie de modificri n toate module ntre
care exist o dependen, putem spune cu certitudine c acel program a fost proiectat incorect. Un
astfel de program este fragil, rigid, impredictibil i nereutilizabil. Principiul nchis-Deschis are drept scop
tocmai evitarea unei astfel de erori. El spune c trebuie proiectate module care nu se modific niciodat.
Atunci cnd cerinele se modific trebuie extins comportamentul modulelor software prin adugarea de
cod nou i nu prin modificarea codului existent care a fost deja testat i este funcional.
Modulele care respect acest principiu au dou proprieti importante:
- Sunt deschise pentru extindere: adic, comportamentul modului poate fi extins; se poate
actualiza modulul astfel nct s nglobeze noi comportamente pe msur ce se modific
cerinele aplicaiei sau pentru a satisface nevoile unei alte aplicaii.
- Sunt nchise pentru modificare: adic, codul surs al unui astfel de modul nu poate fi modificat.
Aparent cele dou atribute par s se contrazic. Soluia la aceast problem este abstractizarea. n cazul
programrii orientate pe obiect abstractizarea se poate obine prin intermediul claselor abstracte, iar
diferitele comportamentele se obin print derivarea claselor abstracte. Astfel un modul poate fi nchis
pentru modificri pentru c el depinde de o clas abstract care nu poate fi modificat, dar totui
comportamentul modului poate fi extins prin derivarea clasei abstracte.
n Fig. 3.3 este prezentat o diagram de clase care nu respect principiul deschis-nchis. Att clasa
Client ct i clasa Server sunt clase concrete. Clasa Client folosete clasa Server. Dac mai trziu se
dorete ca, clasa Client s foloseasc un alt tip de server va fi nevoie s se modifice clasa Client astfel
nct s utilizeze noul server.

Fig. 3.3. Exemplu de clase care nu respect principiul nchis-deschis.

Fig. 3.4. Exemplu de clase care respect principiul nchis-deschis.

3
n Fig. 3.4 se prezint acelai design ca i n Fig. 3.3, dar de aceast dat principiul deschis-nchis este
respectat. n acest caz a fost introdus clasa abstract AbstractServer, iar clasa Client folosete aceast
abstractizare. Totui n spate clasa Client va folosi de fapt clasa Server care implementeaz clasa
AbstractServer. Dac n viitor se dorete folosirea unui alt tip de server tot ce trebuie fcut va fi s se
implementeze o nou clas derivat din clasa AbstractServer, de aceast dat clientul nu mai trebuie
modificat.

1.3 Principiul Substituiei Liskov (Liskov Substitution Principle)

Enun: Functions that use pointers or references to base classes must be able to use objects of derived
classes without knowing it.
(Funciile care utilizeaz pointri sau referine la clase de baz trebuie s poat folosi instane ale
claselor derivate fr s i dea seama de acest lucru.)

Pentru a evidenia importana acestui principiu se va considera o funcie care nu respect acest
principiu. Acest lucru nseamn c funcia folosete o referin la o clas de baz, dar c trebuie s tie
care sunt toate clasele derivate din acea clas de baz. Aceast metoda n mod evident ncalc principiul
deschis-nchis ntruct funcia trebuie modificat de fiecare data cnd este creat o nou clas derivat
din clasa de baz.
n Fig. 3.5 este prezentat o diagram de clase care nu respect principiul substituiei Liskov. Problema
n aceast diagram const n faptul c, clasa PersistentSet dei este derivat din clasa abstract Set nu
poate folosi dect instane de clase derivate din clasa PersientObject pentru c doar acele obiecte pot fi
scrise/citite ntr-un/dintr-un flux, n timp ce clasa Set poate folosi orice tip de obiect. De fiecare dat
cnd o funcie adaug un obiect intr-o instana a clasei Set trebuie s verifice dac clasa este de tipul
PersistentSet pentru a filtra elementele adugate n list.

Fig 3.5. Diagram de clase care ncalc principiul lui Liskov.


n Fig 3.6 este prezentat soluia pentru aceast problem. Practic funcionalitatea comun claselor Set
i PersistentSet a fost a fost pus n dou noi clase abstracte: IterableContainer i MemberContainer. De
asemenea clasa PersistentSet numai este derivat din clasa Set; ambele clase avnd aceeai clas de
baz. Practic diferena ntre clasele Set i PersistentSet const n ceea ce privete metoda care adaug un

4
element, i anume, n cazul clasei Set este acceptat orice tip de obiect, n timp ce metoda de adugare
pentru clasa PersistentSet nu accept dect instane de clase derivate din clasa PersistentObject.

Fig 3.6. Diagram de clase care nu ncalc principiul lui Liskov.

1.4 Principiul Segregrii Interfeei (Interface Segregation Principle)

Enun: Clients should not be forced to depend upon interfaces that they dont use.
(Clienii nu trebuie s depind de interfee pe care nu le folosesc)

Acest principiu scoate n eviden faptul c atunci cnd se definete o interfa trebuie avut grij ca doar
acele metode care sunt specifice interfeei s fie puse n interfa. Dac ntr-o interfa sunt adugate
metode care nu am ce cuta acolo, atunci clasele care implementeaz interfaa vor trebui s
implementeze i acele metode. De exemplu, dac se consider interfaa Muncitor care are metoda
IaPrnzul, atunci toate clasele care implementeaz aceast interfaa vor trebui s implementeze metoda
IaPrnzul. Ce se ntmpl ns dac muncitorul este un robot? Interfeele care conin metode
nespecifice se numesc interfee poluate sau grase.
n Fig. 3.7 este prezentat o diagram de clase care conine: interfaa TimerClient, interfaa Door i clasa
TimedDoor. Interfaa TimerClient trebuie implementat de orice clas care are nevoie s intercepteze
evenimente generate de un Timer. Interfaa Door trebuie s fie implementat de orice clas care
implementeaz o u. Avnd n vedere c a fost nevoie de modelarea unei ui care se nchide automat
dup un anumit interval de timp n Fig. 3.7 este prezentat o soluie n care a fost introdus clasa
TimedDoor derivat din interfaa Door, iar pentru a dispune si de funcionalitatea din TimerClient a fost
modificat interfaa Door astfel nct s moteneasc interfaa TimerClient. Aceast soluie ns
polueaz interfaa Door astfel c toate clasele care vor moteni aceast interfaa vor trebui s
implementeze i funcionalitatea din TimerClient.

5
interface Door
{
void Lock();
void Unlock();
IsDoorOpen();
}

class Timer
{
public void Regsiter(int timeout, TimerClient client);
}

interface TimerClient
{
void TimeOut();
};

Fig. 3.7. Diagram de clase care nu respect principiul segregrii interfeelor.

Fig. 3.8. Diagram de clase care respect principiul segregrii interfeelor.

6
O soluie foarte simpl (Fig. 3.8) care respect principiul segregrii interfeelor const n a deriva clasa
TimedDoor att din interfaa Door ct i din interfaa TimerClient. n acest fel clasa TimedDoor va
implementa funcionalitatea dorit n timp ce celelalte clase care extind interfaa Door nu vor trebui s
implementeze funcionaliti care nu sunt necesare.

1.5 Principiul Inversrii Dependenei (Dependency Inversion Principle)

Enun:
A) High level modules should not depend upon low level modules. Both should depend upon
abstractions.
(Modulele de pe nivelurile ierarhice superioare nu trebuie s depind de modulele de pe
nivelurile ierarhice inferioare. Toate ar trebui s depind doar de module abstract.)
B) Abstractions should not depend upon details. Details should depend upon abstraction.
(Abstractizrile nu trebuie s depind de detalii. Detaliile trebuie s depind de abstractizri.)
Acest principiu spune faptul c modulele de pe nivelul ierarhic superior trebuie s fie decuplate de cele
de pe nivelurile ierarhice inferioare, aceast decuplare realizndu-se prin introducerea unui nivel de
abstractizare ntre clasele care formeaz nivelul ierarhic superior i cele care formeaz nivelurile
ierarhice inferioare. n plus principiul spune i faptul c abstractizarea nu trebuie s depind de detalii, ci
detaliile trebuie sa depind de abstractizare. Acest principiu este foarte important pentru reutilizarea
componentelor software. De asemenea, aplicarea corect a acestui principiu face ca ntreinerea codului
s fie mult mai uor de realizat.
n Fig. 3.9 este prezentat o diagram de clase organizat pe trei niveluri. Astfel clasa PolicyLayer
reprezint nivelul ierarhic superior, ea acceseaz funcionalitate din clasa MechanismLayer aflat pe un
nivel ierarhic inferior. La rndul ei clasa MechanismLayer acceseaz funcionalitate din clasa UtilityLayer
care de asemenea se afl pe un nivel ierarhic inferior. n concluzie, este evident faptul c n diagrama de
clase prezentat nivelurile superioare depind de nivelurile inferioare. Acest lucru nseamn c dac
apare o modificare la unul din nivelurile inferioare exist anse destul de mari ca modificarea s se
propage n sus spre nivelurile ierarhice superioare. Ceea ce nseamn c nivelurile superioare mai
abstracte depind de nivelurile inferioare care sunt mai concrete. Aa dar se ncalc principiul inversri
dependenei.

Fig. 3.9. Ierarhie de clase care nu respect principiul inversrii dependenei.

7
n Fig. 3.10 este prezentat aceeai diagram de clase ca i n Fig. 3.9, dar de aceast dat este respectat
principiul inversrii dependenei. Astfel, la fiecare nivel care acceseaz funcionalitate dintr-un nivel
ierarhic inferior a fost adugat o interfa care va fi implementat de nivelul ierarhic inferior. n acest
fel interfaa prin care dou niveluri comunic este definit n nivelul ierarhic superior astfel c
dependena a fost inversat, i anume nivelul ierarhic inferior depinde de nivelul ierarhic superior.
Modificri fcute la nivelurile inferioare nu mai afecteaz nivelurile superioare, ci invers. n concluzie
diagrama de clase din Fig. 3.10 respect principiul inversrii dependenei.

Fig. 3.10. Exemplu de clas care respect principiul inversrii dependenei

Bibliografie

[1] Robert Martin. Agile Software Development: Principles, Patterns, and Practices. Editura Prentice Hall.
2002
[2] http://www.objectmentor.com/resources/articles/ocp.pdf
[3] http://www.objectmentor.com/resources/articles/lsp.pdf
[4] http://www.objectmentor.com/resources/articles/dip.pdf
[5] http://www.objectmentor.com/resources/articles/isp.pdf
[6] http://www.oodesign.com/design-principles.html