alte concepte ale orientarii pe obiect

7
1 CAPITOLUL 5 Alte concepte ale orientării pe obiect Continuăm prezentarea conceptelor programării orientate pe obiect printr-o sumară introducere în legături statice şi dinamice. Cu ajutorul acestora, vom putea introduce apoi noţiunea de polimorfism ca un mecanism care permite aplicarea unei singure interfeţe pentru mai multe situaţii similare, implementându-se filozofia o singură interfaţă - mai multe metode. Polimorfismul permite scoaterea în evidenţă a relaţiei logice între acţiuni similare. 5.1. Tipuri generice Cunoaştem deja tipurile generice dintr-un capitol anterior, atunci când am discutat despre tipuri de date abstracte generice. Când definim o clasă, noi definim un tip definit de utilizator (user define type). Unele din aceste tipuri pot opera asupra altor tipuri. De exemplu, pot exista liste de mere, liste de maşini, liste de numere complexe şi chiar liste de liste. În momentul în care definim o clasă, trebuie să fim capabili să spunem că acea clasă va defini un tip generic. Totuşi, nu vom ști cu ce tipuri va fi utilizată clasa. În consecinţă, trebuie să definim clasa cu ajutorul unui substitut (placeholder) la care ne vom referi ca fiind tipul asupra căruia operează clasa. Astfel, definirea clasei ne va furniza un șablon (template) al unei clase actuale. Atunci când declarăm un obiect particular, va fi creată definiţia unei clase actuale. Să ilustrăm acest lucru cu următorul exemplu. Să presupunem că dorim să definim o clasă Listă care va fi un tip generic. Astfel, va fi posibil să declarăm obiecte listă pentru mere, maşini, sau alte tipuri. șablon class Listă pentru T { atribute: ... /* Structura de date necesară pentru implementarea */ /* listei */ metode: adaugă(T element) T getFirst() T getNext() bool more() } Clasa șablon Listă arată ca orice altă definiţie de clasă. Totuşi, prima linie declară Listă ca fiind un șablon pentru diferite tipuri. Identificatorul T este utilizat ca subsitut pentru un tip actual. De exemplu, adaugă() ia un element ca argument. Tipul acestui element va fi tipul de date cu care va fi creat un obiect listă actual. De exemplu, putem declara un obiect lista pentru mere dacă există o definiţie a tipului mere.

Upload: bobo-sandu

Post on 20-Feb-2016

6 views

Category:

Documents


2 download

DESCRIPTION

Programare

TRANSCRIPT

Page 1: Alte Concepte Ale Orientarii Pe Obiect

1

CAPITOLUL 5

Alte concepte ale orientării pe obiect

Continuăm prezentarea conceptelor programării orientate pe obiect printr-o sumară introducere în legături statice şi dinamice. Cu ajutorul acestora, vom putea introduce apoi noţiunea de polimorfism ca un mecanism care permite aplicarea unei singure interfeţe pentru mai multe situaţii similare, implementându-se filozofia o singură interfaţă - mai multe metode. Polimorfismul permite scoaterea în evidenţă a relaţiei logice între acţiuni similare.

5.1. Tipuri generice

Cunoaştem deja tipurile generice dintr-un capitol anterior, atunci când am discutat despre tipuri de date abstracte generice. Când definim o clasă, noi definim un tip definit de utilizator (user define type). Unele din aceste tipuri pot opera asupra altor tipuri. De exemplu, pot exista liste de mere, liste de maşini, liste de numere complexe şi chiar liste de liste. În momentul în care definim o clasă, trebuie să fim capabili să spunem că acea clasă va defini un tip generic. Totuşi, nu vom ști cu ce tipuri va fi utilizată clasa. În consecinţă, trebuie să definim clasa cu ajutorul unui substitut (placeholder) la care ne vom referi ca fiind tipul asupra căruia operează clasa. Astfel, definirea clasei ne va furniza un șablon (template) al unei clase actuale. Atunci când declarăm un obiect particular, va fi creată definiţia unei clase actuale. Să ilustrăm acest lucru cu următorul exemplu. Să presupunem că dorim să definim o clasă Listă care va fi un tip generic. Astfel, va fi posibil să declarăm obiecte listă pentru mere, maşini, sau alte tipuri. șablon class Listă pentru T { atribute: ... /* Structura de date necesară pentru implementarea */ /* listei */ metode: adaugă(T element) T getFirst() T getNext() bool more() } Clasa șablon Listă arată ca orice altă definiţie de clasă. Totuşi, prima linie declară Listă ca fiind un șablon pentru diferite tipuri. Identificatorul T este utilizat ca subsitut pentru un tip actual. De exemplu, adaugă() ia un element ca argument. Tipul acestui element va fi tipul de date cu care va fi creat un obiect listă actual. De exemplu, putem declara un obiect lista pentru mere dacă există o definiţie a tipului mere.

Page 2: Alte Concepte Ale Orientarii Pe Obiect

2

Listă pentru Mere listă-mere Mere un-măr, alt-măr listă-mere.adaugă(alt-măr) listă-mere.adaugă(un-măr) Prima linie declară listă-mere ca fiind o listă de mere. În acest moment, compilatorul utilizează definiţia șablon, substituie oriunde apare T cu Mere şi crează o definiţie de clasă actuală pentru aceasta. Acțiunea conduce la o definiţie de clasă similară cu cea care urmează: class Listă { atribute: ... // Structura de date necesară pentru implementarea listei metode: adaugă(Mere element) Mere getFirst() Mere getNext() bool more() } Ceea ce este prezentat mai sus nu este exact ceea ce generează compilatorul. Compilatorul trebuie să se asigure că vom putea crea liste multiple pentru tipuri diferite, în orice moment. De exemplu, dacă avem nevoie de o altă listă, de exemplu de pere, putem scrie: Listă pentru Mere listă-mere Listă pentru Pere listă-pere În ambele cazuri compilatorul generează o definiţie de clasă actuală. Motivul pentru care cele două nu intră în conflict prin numele lor este acela că compilatorul generează nume unice. Întrucât acest lucru nu ne este vizibil, nu intrăm în detalii. În orice caz, dacă declarăm o altă listă de mere, compilatorul va indica faptul că există deja o astfel de definiţie de clasă actuală şi o va utiliza pe aceasta sau, dacă nu există, va crea una nouă. Astfel, Listă pentru Mere o-listă Listă pentru Mere altă-listă Va crea o definiţie de clasă actuală pentru o-listă şi o va reutiliza pentru altă-listă. În consecinţă, ambele vor fi de acelaşi tip. Vom rezuma toate acestea în următoarea definiţie: Definiţie: Dacă o clasă A este parametrizată cu un tip de date B, A este numită clasă șablon. Odată creat un obiect de tip A, B este înlocuit cu un tip de date actual. Aceasta permite definirea unei clase actuale bazată pe specificaţia șablon a lui A şi pe tipul de date actual.

Page 3: Alte Concepte Ale Orientarii Pe Obiect

3

Suntem capabili să definim clase șablon cu mai mult de un parametru. De exemplu, directoarele sunt colecţii de obiecte în care fiecare obiect poate fi referit printr-o cheie. Desigur, un director poate fi capabil să memoreze orice tip de obiect. Dar, sunt posibile, deasemenea, o varietate de chei. De exemplu, cheile pot fi şiruri de caractere sau numere. În consecinţă, vom putea defini o clasă șablon Director care se bazează pe două tipuri de parametri, unul pentru cheie şi unul pentru obiectele memorate. 5.2. Legături statice şi dinamice În limbajele de programare puternice, tipic, se declară variabilele înainte de a fi utilizate. Aceasta implică definirea variabilelor prin care compilatorul rezervă spaţiu pentru variabile. De exemplu, în Pascal o expresie de felul: var i : integer; declară variabila i ca fiind de tip întreg. În plus, ea defineşte un spaţiu de memorie suficient pentru a memora o valoare întreagă. Cu declaraţia vom lega numele i de tipul integer. Legătura este adevărată în scopul în care i este declarat. Aceasta permite compilatorului să verifice în timpul compilării consistenţa tipului. De exemplu, în următoarea asociere va rezulta o eroare de nepotrivire de tip când vom încerca compilarea: var i : integer; ... i := 'string'; Acest tip particular de legătură îl vom numi static deoarece este fixată în timpul compilării. Definiţie: Dacă tipul T al unei variabile este explicit asociat cu numele său N prin declarare, vom spune că numele N este legat static de T. Procesul de asociere este numit legare statică. Există limbaje de programare care nu utilizează tipizarea explicită a variabilelor. De exemplu, unele limbaje permit introducerea de variabile în momentul în care sunt necesare: ... /* Nu apare i */ i := 123 /* Crearea lui i ca un întreg */ Tipul lui i este cunoscut de îndată ce îi este setată valoarea. În acest caz, i este întreg din momentul în care îi atribuim întregul număr. Astfel, deoarece conţinutul lui i este un număr întreg, tipul lui i este integer. Definiţie: Dacă tipul T al unei variabile cu numele N este implicit asociat de conţinutul său, vom spune că N este legat dinamic de T. Procesul de asociere este numit legare dinamică.

Page 4: Alte Concepte Ale Orientarii Pe Obiect

4

Cele două legări diferă prin momentul în care are loc legarea tipului la variabile. Considerăm exemplul de mai jos, care este posibil numai în cazul legării dinamice: if anumită-condiție() == TRUE then n := 123 else n := 'abc' endif Tipul lui n după instrucţiunea if depinde de evaluarea condiţiei anumită-condiție(). Dacă aceasta este TRUE, n este de tip integer, altfel este de tip string. 5.3. Polimorfism Polimorfismul permite unei entităţi (de exemplu variabilă, funcţie sau obiect) să ia o varietate de reprezentări. Va trebui să facem diferenţa între diferite tipuri de polimorfism. Primul tip este similar conceptului de legare dinamică. Aici, tipul variabilei depinde de conţinutul său. Astfel, tipul său depinde de conţinut la un moment dat: v := 123 /* v este integer */ ... /* utilizarea lui v ca integer */ v := 'abc' /* v "comută" la string */ ... /* utilizarea lui v ca string */ Definiţie: Conceptul de legare dinamică permite unei variabile să ia diferite tipuri în funcţie de conţinutul de la un moment dat. Această abilitate a unei variabile este numită polimorfism.

Un alt tip de polimorfism poate fi definit pentru funcţii. De exemplu, presupunem că dorim să definim o funcţie isNull() care returnează TRUE dacă argumentul său este 0 şi FALSE altfel. Pentru numere întregi este simplu: boolean isNull(int i) { if (i == 0) then return TRUE else return FALSE endif } Dacă dorim să verificăm acest lucru pentru numere reale, va trebui să utilizăm o altă comparaţie, datorită problemei de precizie: boolean isNull(real r) { if (r < 0.01 and r > -0.99) then return TRUE else return FALSE endif }

Page 5: Alte Concepte Ale Orientarii Pe Obiect

5

În ambele cazuri dorim ca funcţia să aibă acelaşi nume isNull. În limbajele de programare fără polimorfism pentru funcţii nu vom putea declara aceste două funcţii, deoarece dublarea numelui creează ambiguitate. Dacă limbajul poate lua în considerare parametri pentru funcţii, declararea poate fi posibilă. Astfel, funcţiile (metodele) sunt unic identificate prin:

• numele funcţiei (metodei) şi • tipurile din lista parametrilor.

Deoarece lista parametrilor celor două funcţii isNull diferă, compilatorul este capabil să ofere apelarea corectă a funcţiei prin utilizarea tipului actual al argumentelor: var i : integer var r : real i = 0 r = 0.0 ... if (isNull(i)) then ... /* utilizare isNull(int) */ ... if (isNull(r)) then ... /* utilizare isNull(real) */ Definiţie: Dacă o funcţie (sau o metodă) este definită prin combinaţia

• numele său şi • lista de tipuri pentru parametrii săi

vorbim despre polimorfism. Acest tip de polimorfism permite reutilizarea aceluiaşi nume pentru funcţii (metode) atât timp cât listele parametrilor diferă. Uneori, acest tip de polimorfism este numit supraîncărcare, supradefinire (overloading). Ultimul tip de polimorfism permite unui obiect să îşi aleagă metodele corecte. Considerăm din nou funcţia mută(), care ia un obiect din clasa Punct ca argument. Am utilizat această funcţie cu orice obiect al claselor derivate, datorită relaţiei de tipul este-un/o. Vom considera acum funcţia afișează() care va fi utilizată pentru afişarea obiectelor desenabile. Declaraţia acestei funcţii poate arăta astfel: afișează(ObiectDesenabil o) { ... o.print() ... }

Vom dori să utilizăm această funcţie cu obiecte ale claselor derivate din ObiectDesenabil:

Page 6: Alte Concepte Ale Orientarii Pe Obiect

6

Cerc cerc Punct punct Dreptunghi dreptunghi afișează(punct) /* trebuie să invoce punct.print() */ afișează(cerc) /* trebuie să invoce cerc.print() */ afișează(dreptunghi) /* trebuie să invoce dreptunghi.print() */

Metoda actuală va fi definită de conţinutul obiectului o al funcţiei afișează(). Întrucât este destul de complicat, vom lua un exemplu mai abstract:

class Bază { atribute: metode: virtual funcție_1() funcție_2() } class Derivată moștenește de la Bază { atribute: metod: virtual funcție_1() funcție_2() } demo(Bază o) { o. funcție_1() o. funcție_2() } Bază bază Derivată derivat demo(bază) demo(derivat) În acest exemplu am definit două clase Bază şi Derivată. Fiecare clasă defineşte două metode funcție_1() şi funcție_2(). Prima metodă este definită ca virtuală. Aceasta înseamnă că, dacă această metodă este invocată, definirea ei va fi evaluată prin conţinutul obiectului. Definim apoi o funcţie demo() care ia ca argument un obiect de tip Bază. În consecinţă, putem utiliza această funcţie cu obiecte ale clasei Derivată deoarece este valabilă relaţia este o. Vom apela această funcţie cu un obiect Bază şi respectiv cu un obiect Derivată. Presupunem că funcție_1() şi funcție_2() sunt definite pentru a tipări numele lor şi clasa din care sunt definite. În acest caz la ieşire vom avea:

Page 7: Alte Concepte Ale Orientarii Pe Obiect

7

funcție_1() apelată de Bază. funcție_2() apelată de Bază. funcție_1() apelată de Derivată. funcție_2() apelată de Bază. De ce acest rezultat? Primul apel al lui demo() - demo(bază) utilizează un obiect Bază. Astfel, argumentul funcţiei este ocupat cu un obiect al clasei Bază. În momentul invocării funcţiei funcție_1() , funcţionalitatea ei actuală este aleasă pe baza conţinutului curent al obiectului corespondent o. De această dată este un obiect al clasei Bază. În consecinţă, este apelată funcție_1() cum a fost definită în clasa Bază. Apelul funcție_2() nu este legat de conţinutul său. Funcţia nu este marcată ca virtuală. În consecinţă, funcție_2() este apelată în maniera clasei Bază. Al doilea apel al demo()-demo(derivat) ia ca argument un obiect al clasei Derivată. Astfel, argumentul o este ocupat cu un obiect al clasei Derivată. Argumentul funcței demo(Bază o) este obiect de tip Bază. La apelul funcției demo() de către un obiect al clasei Derivată, acest obiect va fi tratat ca și un obiect al clasei Bază pentru funcții care nu sunt definite ca virtuale Acum, apelul funcție_1() este evaluat prin examinarea conţinutului lui o, astfel încât este apelat în maniera Derivată, iar apelul funcție_2() este, în continuare, evaluat în sensul Bază. (pe baza relației care spune că obiectul derivat este un obiect bază). Definiţie: Obiectele unei superclase pot fi substituite cu obiecte ale subclaselor sale. Operatorii şi metodele subclaselor pot fi definite pentru a fi evaluate în două contexte: 1. Pe baza tipul obiectului, conducând la o evaluare în sensul superclasei. 2. Pe baza conţinutul obiectului, conducând la o evaluare în sensul subclaselor conţinute. Al doilea tip este numit polimorfism.