Ce este Programarea
Orientata pe Obiecte ?
Evolutia
Paradigmelor de Programare. Problema complexitatii.
Modalitatile
de programare au evoluat in mod spectaculos de la inventarea
calculatorului, principala
cauza a acestei permanente schimbari fiind necesitatea acomodarii la cresterea
complexitatii programelor[Sch97]. Astfel, programarea a evoluat
de la stadiul initial al introducerii instructiunilor direct in cod binar,
continuand cu inventarea limbajelor de asamblare, care permiteau reprezentarea
simbolica a instructiunilor pentru masina. Atunci cand limbajele de asamblare
n-au mai putut face nici ele fata complexitatii crescande a programelor
a fost necesara utilizarea unor limbaje de nivel mai inalt -- limbajele de
generatia intai si a doua -- ca instrumente care sa faciliteze gestionarea
acelui nivel de complexitate. Reprezentatul cel mai de seama al
acestei perioade este fara indoiala limbajul FORTRAN I.
Deceniul
sase a adus cu sine aparitia programarii structurate, ceea ce a
constituit unul dintre pasii semnificativi in evolutia ingineriei software,
aceasta paradigma de programare dominand o buna bucata de timp lumea
programarii. Programarea structurata este sustinuta de limbaje de generatia
a treia cum sunt C-ul si Pascal-ul, principala caracteristica
a lor fiind utlizarea subprogramelor ca modalitate de gestionare
a complexitatii. Programarea structurata s-a dovedit a fi o modalitate
adecvata de abstractizare a operatiilor si a algoritmilor,
dovedindu-si
eficienta in gestionarea programelor a caror complexitate putea fi controlata
de catre un singur programator sau de catre un numar restrans de
programatori.
Odata
cu cresterea dimensiunii proiectelor software a devenit tot mai clar ca
pentru a controla complexitatea un rol substantial il joaca abstractizarea
datelor [Boo94] si ca in acest scop programarea structurata este
ineficienta. In 1984 Shankar afirma ca: "Natura
abstractizarilor ce pot fi obtinute prin utilizarea procedurilor este adecvata
descrierii operatiilor abstracte, dar nu este adecvata descrierii obiectelor
abstracte. Aceasta este o carenta majora de vreme ce in multe aplicatii
complexitatea obiectelor de date care trebuiesc manipulate contribuie substantial
la complexitatea globala a problemei."
S-a
impus deci gasirea unui nou model de programare capabile sa depaseasca
limitarile programarii structurate si care sa fie capabila sa realizeze
abstractizarea adecvata a datelor. Asa s-a nascut clasa limbajelor bazate
pe obiecte si apoi a celor orientate pe obiecte. Dintre acestea
cele mai raspandite sunt Ada si CLOS (bazate
pe obiecte), respectiv Smalltalk, C++, Eiffel si mai de curand
Java (orientate pe obiecte). Elementul fizic de constructie in aceste
limbaje este modulul care contine o colectie de clase
si obiecte. Diferenta fundamentala intre programarea structurata si cea
bazata/orientata pe obiecte poate fi formulata plastic astfel: "Daca
procedurile si functiile sunt verbe, iar blocurile de date sunt substantive,
un program procedural este organizat in jurul verbelor, in timp ce un program
orientat pe obiecte este organizat in jurul substantivelor"[Boo94].
Astfel, un program orientat pe obiecte are putine date globale, intrucat
datele si operatiile sunt unite intr-un mod nou care face ca blocurile
logice fundamentale ale sistemului sa nu mai fie algoritmii, ci clasele
si obiectele.
Definitia
Programarii Orientate pe Obiecte
Booch
ne ofera urmatoarea definitie a programarii orientate pe obiecte
(prescurtat POO sau OOP) [Boo94]:
Programarea
orientata pe obiecte este o metoda de implementare in care programele sunt
organizate ca si colectii de obiecte ce coopereaza intre ele, fiecare obiect reprezentand
instanta unei clase; fiecare clasa apartine unei ierarhii de clase, clasele
fiind unite prin relatii de mostenire.
Aceasta
definitie cuprinde trei parti importante, si anume:
-
obiectele
si nu algoritmii sunt blocurile logice fundamentale;
-
fiecare
obiect este o instanta a unei clase;
-
clasele
sunt legate intre ele prin relatii de
mostenire.
Limbaj
de Programare Bazat pe Obiecte. Programare cu Tipuri de Date Abstracte.
Un
limbaj de programare care ofera suport pentru utilizarea claselor si a
obiectelor, dar care nu are implementat mecanismul relatiilor de mostenire
intre clase este un limbaj de programare bazat pe obiecte.
Programarea bazata pe clase si pe obiecte, care nu face uz de relatia
de mostenire se mai numeste programare cu tipuri de date abstracte.
Important:
Trebuie
sa intelegem ca prin simpla invatare a unui limbaj care suporta programarea
orientata pe obiecte NU invatam automat sa programam corect conform
modelului obiectual! Pentru a invata acest lucru trebuie sa intelegem
si sa aplicam conceptele si mecanismele care stau la baza
acestui model. Si tocmai acest lucru ni-l
propunem.
Concepte
Fundamentale in Programarea Orientata pe Obiecte
Fiecare
model de programare impune un anumit stil de programare aflat in stransa
legatura cu conceptele fundamentale ce caracterizeaza respectivul model.
Principalele concepte ce stau la baza programarii orientate pe obiecte
sunt:
-
Abstractizarea
-
Incapsularea
-
Modularitatea
-
Ierarhizarea
Vom discuta
in continuare fiecare dintre aceste concepte:
Abstractizarea
este una din caile fundamentale prin care noi, oamenii, ajungem sa intelegem
si sa curpindem complexitatea. O abstractiune buna este cea care scoate
in evidenta toate detaliile semnificative pentru perspectiva din care este
analizat un obiect, suprimand sau estompand toate celelalte
caracteristici ale obiectului.
In contextul programarii orientate pe obiecte, Booch ne ofera urmatoarea
definitie a abstractiunii [Boo94]:
O
abstractiune exprima toate caracteristicile esentiale ale unui obiect,
care fac ca acesta sa se
distinga de alte obiecte; abstractiunea ofera o
definire precisa a granitelor conceptuale ale obiectului, din
perspectiva unui
privitor extern.
Comportament
vs. Implementare
Asadar
in procesul de abstractizare atentia este indreptata exclusiv spre aspectul
exterior al obiectului, adica spre comportarea lui,
ignorand implementarea acestei comportari. Cu alte cuvinte
abstractizarea ne ajuta sa delimitam ferm
"CE
face obiectul" de "CUM face obiectul ceea ce face".
Modelul
Client-Server.
Responsabilitati.Protocol.
Comportarea unui obiect se caracterizeaza printr-o suma de
servicii sau resurse pe care el le pune la dispozitia altor obiecte. Un
asemenea
comportament, in care un obiect, numit server, ofera
servicii altor
obiecte, numite clienti, este descris de asa-numitul
model
client-server.
Totalitatea serviciilor oferite de un obiect server constituie un
contract sau o responsabilitate a obiectului
fata de alte obiecte.
Responsabilitatile sunt indeplinite prin intermediul unor
operatii
(alte denumiri folosite: metode, functii
membru). Fiecare operatie a unui
obiect se caracterizeaza printr-o semnatura unica, formata
din: nume, o
lista de parametri formali si un tip returnat. Multimea operatiilor unui
obiect,
impreuna cu regulile lor de apelare constituie protocolul
obiectului.
Incapsularea
este conceptul complementar abstractizarii. Daca rezultatul operatiei de
abstractizare pentru un anumit obiect este identificarea protocolului sau,
atunci incapsularea are de a face cu selectarea unei
implementari
si tratarea acesteia ca pe un secret al respectivei abstractiuni. Prin
urmare, incapsularea este procesul in care are loc ascunderea
implementarii
fata de majoritatea obiectelor-client. Determinarea
protocolului pentru un obiect trebuie sa preceada decizia privind implementarea
sa. Sintetizand putem defini incapsularea astfel:
Incapsularea
este procesul de compartimentare a elementelor care formeaza structura
si comportamentul unei abstractiuni ; incapsularea serveste la separarea
interfetei "contractuale" de implementarea acesteia.
Interfata
vs. Implementare
Din
definitia de mai sus rezulta ca un obiect este format din doua parti distincte:
interfata (protocolul) si respectiv implementarea
acestei interfete.
Abstractizarea este procesul prin care este definita interfata obiectului,
in timp ce incapsularea defineste reprezentarea (structura) obiectului
impreuna cu implementarea interfetei. Ascunderea structurii obiectului
si a implementarii metodelor sale este ceea ce se intelege prin notiunea
de ascundere a informatiei.
Beneficiile
incapsularii
-
Separarea
interfetei de reprezentarea unui obiect permite modificarea reprezentarii
fara a afecta in vreun fel pe nici unul din clientii sai, intrucat acestia
depind de interfata si nu de reprezentarea obiectului-server.
-
Incapsularea
permite modificarea programelor intr-o maniera eficienta, cu un efort limitat
si bine localizat.
"Scopul descompunerii in module este reducerea costurilor prin
posibilitatea
de a proiecta si revizui modulele in mod independent"
(Parnas & Britton)
Clasele
si obiectele obtinute in urma abstractizarii si incapsularii trebuie
grupate si apoi stocate intr-o forma fizica, denumita modul.
Modulele pot fi privite ca si containere
fizice in care declaram clasele si obiectele rezultate in urma proiectarii
la nivel logic. Modulele formeaza asadar
arhitectura fizica a programului. Modularizarea consta
in divizarea programului intr-un numar de module care pot fi compilate
separat, dar care sunt conectate (cuplate) intre ele. Limbajele care suporta conceptul
de modul fac in acelasi timp distinctia intre interfata
modulului
si implementarea sa. Putem spune ca incapsularea si
modularizarea
merg mana in mana.
Concret,
in C++ modulele nu sunt altceva decat fisiere ce pot fi compilate separat.
Practica uzuala este ca interfata modulului sa fie plasata intr-un
fisier header (extensii uzuale sunt: ".h" , ".hpp",
".hh"),
in timp ce implementarea acestuia se va regasi intr-un fisier
sursa
(extensii uzuale sunt: ".cc", ".cpp", ".c"). Dependentele intre
module vor fi exprimate utlizand directivele "#include". Pentru detalii despre
utilizarea modulelor in C++ si compilare in UNIX a proiectelor formate
din mai multe module, cititi anexa "Compilarea
proiectelor in UNIX. Utilizarea Makefile"
Reguli
Generale de Modularizare
1.
Structura fiecarui modul trebuie sa fie suficient de simpla pentru a putea
fi complet inteleasa.
2.
Implementarea unui modul trebuie sa depinda doar de interfetele altor module.
Cu alte cuvinte trebuie sa fie posibila modificarea implementarii unui modul
fara a avea cunostinte despre implementarea altor module si fara a afecta
comportarea celorlalte module.
3.
Detaliile sistemului care se presupune ca se vor modifica independent vor
fi plasate in module diferite.
4.
Singurele legaturi intre module vor fi acelea a caror modificare este improbabila.
5.
Orice structura de date este incapsulata intr-un modul; ea poate fi accesata
direct din interiorul modulului dar nu poate fi accesata din afara modului
decat prin intermediul obiectelor si claselor continute in acel modul.
Din aceasta
perspectiva putem intelege definitia lui Booch asupra modularitatii [Boo94]:
Modularitatea
este proprietatea unui sistem care a fost descompus intr-un set de module
coezive si slab cuplate.
Abstractizarea
este un lucru bun, dar in majoritatea aplicatiilor - exceptie facand doar
aplicatiile banale - vom descoperi mai multe abstractiuni decat putem
cuprinde la un moment dat. Incapsularea ne ajuta sa tratam aceasta complexitate
prin ascunderea interiorului abstractiunilor noastre. Modularitatea ne
ajuta si ea, oferindu-ne o modalitate de a grupa abstractiuni legate logic
intre ele. Toate acestea desi utile, nu sunt suficiente. Adesea un grup
de abstractiuni formeaza o ierarhie, iar prin identificarea acestor ierarhii,
putem simplifica substantial intelegerea problemei.
Ierarhizarea
reprezinta o ordonare a abstractiunilor.
Cele
mai importante ierarhii de clase in paradigma obiectuala sunt:
-
Ierarhia
de clase (relatie de tip "is
a")
-
Ierarhia
de obiecte (relatie de tip "part
of")
Mostenirea
(ierarhia de clase)
Mostenirea
defineste o relatie intre clase in care o clasa impartaseste structura
si comportarea definita in una sau mai multe clase (dupa caz vorbim de
mostenire simpla sau multipla). Asa cum aminteam mai sus,
relatia de mostenire este cea care face diferenta intre programarea orientata
pe obiecte si cea bazata pe obiecte.
Semantic,
mostenirea indica o relatie de tip "is a" ("este un/o").
De exemplu un urs "este un" mamifer si deci intre clasa ursilor
si cea a mamiferelor exista o relatie de mostenire. Si in cazul programarii
acesta este cel mai bun test pentru a detecta o relatie de mostenire intre
doua clase A si B: A mosteneste pe B daca si numai daca putem spune ca
"A este un fel de B". Daca A "nu este un" B atunci A nu ar trebui
sa mosteneasca pe B. Prin urmare, mostenirea implica o ierarhie de tip
generalizare/specializare, in care clasa derivata specializeaza
structura si comportamentul mai general al clasei din care a fost derivata.
Detalii in lucrarea "Relatia de Mostenire".
Agregarea
(ierarhia de obiecte)
Agregarea
este relatia intre doua obiecte in care unul dintre obiecte apartine celuilalt
obiect. Agregarea reda apartenta unui obiect la un alt obiect. Semantic,
agregarea indica o relatie de tip "part of" ("parte din").
De exemplu intre o roata si un automobil exista o astfel de relatie, intrucat
putem spune ca "o roata este o parte din automobil". Detalii
in lucrarea "Tablouri. Pointeri.Referinte"
Probleme
1. Sa consideram ca se doreste implementarea unui dictionar, intr-o maniera
obiectuala. Identificati intrebarile care se pun si deciziile care trebuie
luate in procesul de abstractizare, respectiv in cel de incapsulare.
2. Care
credeti ca sunt - din perspectiva timpului de compilare - implicatiile
nerespectarii celei de
doua reguli de modularizare, pentru o aplicatie
de foarte mari dimensiuni (de ordinul a sute de module) ?
3*.
Exista vreo legatura intre relatia de mostenire si cea de agregare? Ar putea fi modelate una prin
cealalta?
Bibliografie
[Boo94]
G. Booch - "Object-Oriented Analysis and Design with Applications.
Second Edition", Addison-Wesley,
1994
[Sch97]
H. Shildt - "C++. Manual complet", Editura TEORA, 1997