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]:

Aceasta definitie cuprinde trei parti importante, si anume:
  • 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.

    ImportantTrebuie 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:

    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]:
  • 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:
  • 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


  •     "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
  • Din aceasta perspectiva putem intelege definitia lui Booch asupra modularitatii [Boo94]: 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.  Cele mai importante ierarhii de clase in paradigma obiectuala sunt:
  • 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

    Hosted by www.Geocities.ws

    1