Intre obiectele lumii care ne inconjoara exista, de multe ori, anumite relatii. Spre exemplu, putem spune despre un obiect autovehicul ca are ca si parte componenta un obiect motor. Pe de alta parte, putem spune ca motoarele diesel sunt un fel mai special de motoare. Din exemplul secund deriva cea mai importanta relatie ce poate exista intre doua clase de obiecte: relatia de mostenire. Practic, relatia de mostenire reprezinta inima programarii orientate pe obiecte.
La fel ca notiunile de abstractizare si incapsulare, ierarhizarea este un concept fundamental in programarea orientata pe obiecte. Rolul procesului de abstractizare (cel care conduce la obtinerea unei abstractiuni) este de a identifica si separa, dintr-un punct de vedere dat, ceea ce este important de stiut despre un obiect si ceea ce nu este important. Rolul mecanismului de incapsulare este de a permite ascunderea a ceea ce nu este important de stiut despre un obiect. Dupa cum se poate observa, abstractizarea si incapsularea tind sa micsoreze cantitatea de informatie disponibila utilizatorului unei abstractiuni. O cantitate mai mica de informatie conduce la o intelegere mai usoara a respectivei abstractiuni. Dar ce se intampla daca exista un numar foarte mare de abstractiuni?
Intr-o astfel de situatie, des intalnita in cadrul dezvoltarii sistemelor software de mari dimensiuni, simplificarea intelegerii problemei de rezolvat se poate realiza prin ordonarea acestor abstractiuni, formandu-se astfel ierarhii de abstractiuni.
Ierarhia de obiecte este o ierarhie de tip intreg “\parte”. Sa consideram un obiect dintr-o astfel de ierarhie. Pe nivelul ierarhic imediat superior acestui obiect se gaseste obiectul din care el face parte. Pe nivelul ierarhic imediat inferior se gasesc obiectele ce sunt parti ale sale.
Este simplu de observat ca o astfel de ierarhie descrie relatiile de tip part of dintre obiecte. In termeni aferenti programarii orientate pe obiecte, o astfel de relatie se numeste relatie de agregare.
In secventa de cod de mai jos se poate vedea cum este transpusa o astfel de relatie in codul sursa Java.
class Tastatura { //Elemente specifice unei tastaturi } class Monitor { //Elemente specifice unui monitor } class Calculator { //Fiecare calculator trebuie sa aiba o tastatura Tastatura tastatura; //Fiecare calculator trebuie sa aiba un monitor Monitor monitor; public Calculator() { //O posibila solutie de instantiere this.monitor = new Monitor(); this.tastatura = new Tastatura(); } }
Ierarhia de clase este o ierarhie de tip generalizare”\”” specializare. Sa consideram o clasa B
care face parte dintr-o astfel de ierarhie. Pe nivelul ierarhic imediat superior se gaseste o clasa A
care defineste o abstractiune mai generala decat abstractiunea definita de clasa B
. Cu alte cuvinte, clasa B
defineste un set de obiecte mai speciale incluse in setul de obiecte definite de clasa A
. Prin urmare, putem spune ca B
este un fel de B
.
Dupa cum se poate observa, ierarhia de clase este generata de relatii de tip “is a” dintre clasele de obiecte, aceasta relatie numindu-se relatie de mostenire. In cadrul unei astfel de relatii, clasa A
se numeste superclasa a clasei B
, iar B
se numeste subclasa a clasei A
.
Dupa cum am spus inca de la inceput, mostenirea este inima programarii orientate pe obiecte. Este normal sa apara intrebarea: de ce? Ei bine, limbajele de programare orientate pe obiecte, pe langa faptul ca permit programatorului sa marcheze explicit relatia de mostenire dintre doua clase, mai ofera urmatoarele facilitati:
In cadrul acestui laborator, ne vom ocupa de primele doua aspecte enuntate, cunoscute si sub numele de mostenire de clasa, respectiv mostenire de tip.
class nume_subclasa extends nume_superclasa { //continutul subclasei (membrii + metode) }
Mostenirea de clasa este o facilitate a limbajelor de programare orientate pe obiecte care permite sa definim implementarea unui obiect in termenii implementarii altui obiect. Mai exact, subclasa preia sau “mosteneste” reprezentarea interna (“datele”) si comportamentul (“metodele”) de la superclasa. Dupa cum se poate observa, acest principiu POO permite reutilizarea de cod.
Pentru a intelege mai bine conceptul de mostenire de clasa, dar si pentru o exemplificare a regulilor de vizibilitate a membrilor de clasa, se propune analizarea urmatoarei secvente de cod Java. Se poate observa ca, din perspectiva unui client, nu se face distinctie intre membrii mosteniti de o clasa si cei specifici ei.
class SuperClasa { public int super_a; private int super_b; protected int super_c; } class SubClasa extends SuperClasa { public void metoda(SuperClasa x) { super_a = 1; //Corect super_b = 2; //Eroare la compilare super_c = 3; //Corect x.super_a = 1; //Corect x.super_b = 2; //Eroare la compilare x.super_c = 3; //Corect (clase in acelasi pachet) } } class Client { public void metoda() { SuperClasa sp = new SuperClasa(); SubClasa sb = new SubClasa(); sp.super_a = 1; //Corect sp.super_b = 2; //Eroare la compilare sp.super_c = 3; //Corect (clase in acelasi pachet) sb.super_a = 1; //Corect sb.super_b = 2; //Eroare la compilare sb.super_c = 3; //Corect (clase in acelasi pachet) } }
In cazul in care exista date care au acelasi nume si in subclasa, dar si in superclasa, este posibil sa se produca anumite confuzii, datorate acestui inconvenient.
In exemplul de mai jos, ce camp de date este initializat (cel din subclasa sau cel din superclasa)? Care ar putea fi explicatia acestui raspuns?
class A { int nr; } class B extends A { int nr; public B() { //Obiectul din subclasa (B) this.nr = 1; //Obiectul din superclasa (A) super.nr = 2; } }
class Paralelogram { public int lungime, latime; public Paralelogram(int lungime, int latime) { this.lungime = lungime; this.latime = latime; } } class Patrat extends Paralelogram { public Patrat(int latura) { super(latura, latura); } } class Test { public static void main(String args[]) { Paralelogram p; Patrat p1 = new Patrat(10); p = p1; } }
Deoarece o clasa derivata mosteneste membrii clasei de baza, atunci cand este instantiat un obiect din clasa derivata, trebuie apelat constructorul clasei de baza pentru a initializa membrii care provin din clasa de baza. Initializarea membrilor din clasa de baza trebuie realizata explicit in apelul constructorului clasei derivate. In caz contrar, pentru membrii care provin din clasa de baza se apeleaza automat constructorul implicit al clasei de baza.
Pentru o intelegere mai buna a modului in care se apeleaza constructorii in cazul mostenirii, se recomanda consultarea exemplelor oferite in cadrul cursului si analizarea exemplului urmator.
class D { public D() { System.out.println("Constructor D"); } } class A extends D{ public A() { this(10); System.out.println("Constructor 1 - A"); } public A(int nr) { System.out.println("Constructor 2 - A " + nr); } } class B extends A { public B(int nr) { System.out.println("Constructor 1 - B " + nr); } } class C extends B { public C() { super(7); System.out.println("Constructor - C"); } } class Test { public static void main(String args[]) { C obj = new C(); } }
Supraincarcarea si supradefinirea metodelor sunt doua concepte extrem de utile ale programarii orientate pe obiecte, cunoscute si sub denumirea de polimorfism, si se refera la:
class A { void metoda() { System.out.println("A: Metoda 1"); } //Supraincarcare void metoda(int nr) { System.out.println("A: Metoda 2 " + nr); } } class B extends A { //Supradefinire void metoda() { System.out.println("B: Metoda supradefinita"); } }