Principiile programarii orientate pe obiecte

Programarea orientată pe obiecte (eng. Object Oriented Programming - OOP) este o tehnică de dezvoltare a aplicaţiilor software bazată pe obiecte. Obiectul este conceputul fundamental al aceste tehnici. Un obiect este un model software al unei entităţi sau concept din realitate.

Principiile OOP sunt (sursa: Wiki):

  • Abstractizarea – Este posibilitatea ca un program să ignore unele aspecte ale informației pe care o manipulează, adică posibilitatea de a se concentra asupra esențialului. Fiecare obiect în sistem are rolul unui “actor” abstract, care poate executa acțiuni, își poate modifica și comunica starea și poate comunica cu alte obiecte din sistem fără a dezvălui cum au fost implementate acele facilitați. Procesele, funcțiile sau metodele pot fi de asemenea abstracte, și în acest caz sunt necesare o varietate de tehnici pentru a extinde abstractizarea.
  • Încapsularea – numită și ascunderea de informații: Asigură faptul că obiectele nu pot schimba starea internă a altor obiecte în mod direct (ci doar prin metode puse la dispoziție de obiectul respectiv); doar metodele proprii ale obiectului pot accesa starea acestuia. Fiecare tip de obiect expune o interfață pentru celelalte obiecte care specifică modul cum acele obiecte pot interacționa cu el.
  • Polimorfismul – Este abilitatea de a procesa obiectele în mod diferit, în funcție de tipul sau de clasa lor. Mai exact, este abilitatea de a redefini metode pentru clasele derivate. De exemplu pentru o clasă Figura putem defini o metodă arie. Dacă Cerc, Dreptunghi, etc. vor extinde clasa Figura, acestea pot redefini metoda arie.
  • Moștenirea – Organizează și facilitează polimorfismul și încapsularea, permițând definirea și crearea unor clase specializate plecând de la clase (generale) deja definite - acestea pot împărtăși (și extinde) comportamentul lor, fără a fi nevoie de a-l redefini. Aceasta se face de obicei prin gruparea obiectelor în clase și prin definirea de clase ca extinderi ale unor clase existente. Conceptul de moștenire permite construirea unor clase noi, care păstrează caracteristicile și comportarea, deci datele și funcțiile membru, de la una sau mai multe clase definite anterior, numite clase de bază, fiind posibilă redefinirea sau adăugarea unor date și funcții noi. Se utilizează ideea: ”Anumite obiecte sunt similare, dar în același timp diferite”. O clasă moștenitoare a uneia sau mai multor clase de bază se numește clasă derivată. Esența moștenirii constă în posibilitatea refolosirii lucrurilor care funcționează.

Un obiect este compus din atribute şi metode ( denumirea echivalentă a unei metode din programarea liniară este funcţie sau procedură). Atributele caracterizează starea obiectului la un moment dat. Se pot efectua acţiuni asupra obiectelor, poate fi alterată starea acestora sau acestea oferă servicii altor obiecte prin intermediul metodelor.

Obiectele sunt construite pe baza unor şabloane sau template-uri numite clase. O clasă specifică caracteristicile (atributele şi metodele) pe care le va avea un obiect ce va fi creat pe baza respectivului şablon. Procesul de construire a unui obiect se mai numeşte şi operaţie de instanţiere. Toate obiectele construite pe baza aceleiaşi clase împărtăşesc caracteristicile definite de clasă (au atributele şi metodele definite în cadrul clasei) dar se diferenţiază prin valorile atributelor pe care le are fiecare obiect in parte.

Obiectele sunt instanţe concrete ale claselor în memorie. Obiectele interacţionează între ele în scopul îndeplinirii unui anumit set de funcţionalităţi.

Clase si obiecte Java

În java o clasă este definită prin intermediul cuvântului cheie class urmat de numele concret al clasei.

class Senzor{
            int value;
            boolean active;
}

In exemplul de mai sus am definit clasa Senzor ce contine 2 atribute.

Singura modalitate prin care pot fi construite obiecte in java este cu ajutorul operatorului new. Singura exceptie de la aceasta regula este clasa String - care permite construirea de obiecte si intr-un alt mod.

Obiectele sunt tipuri referinţă. Aceasta înseamnă ca obiectele sunt efectiv construite în memorie de abia în momentul în care este folosit operatorul new. În condiţiile în care o variabilă de tip obiect este doar declarată variabila de tip obiect va avea valoarea null (este cazul variabilei s2 declarată mai sus). Această valoare specifică faptul că variabila de tip obiect nu referă către nici un obiect în memorie. Încercare de apelare a unui atribut sau a unei metode din cadrul unui obiect care nu a fost încă construit va determina generarea erorii NullPointerException.

Pentru a putea avea un exemplu complet functional vom modifica clasa Senzor si vom adauga metoda main conform exemplului de mai jos.

public class Senzor{
    int value;
    boolean active;
 
    public static void main(String[] args) {
		Senzor s1 = new Senzor();
		Senzor s2 = new Senzor();
		Senzor s3 = null; 
 
		System.out.println("Senzor s1 value="+s1.value);
		System.out.println("Senzor s2 value="+s2.value);
		System.out.println("Senzor s3 value="+s3.value);
	}
}

Observatie Am declarat specificatorul public in fata numelui clasei deoarece aceasta clasa contine metoda main. Aceasta metoda trebuie apelata de catre masina virtuala java in momentul pornirii programului. Daca clasa nu ar fi publica atunci clasa main nu ar putea fi gasita.

In urma rularii exemplului de mai sus vom obtine la iesire urmatorul rezultat:

Senzor s1 value=0
Senzor s2 value=0
Exception in thread "main" java.lang.NullPointerException
	at Senzor.main(Senzor.java:13)

Accesarea unui membrul (atribut sau metodă) a unui obiect se face prin intermediul numelui obiectului urmat de caracterul punct „.” şi apoi de numele membrului ce se doreşte a fi accesat. Incercarea de a accesa un membru al obiectului s3 va genera o eroare de tip NullPointerException deoarece aceasta variabila este de tip referinta (de tip obiect) si nu a fost inca initializata.

In general nu se recomanda accesarea directa a atributelor unui obiect. Daca se doreste manipularea atributelor atunci acest lucru trebuie facut prin intermeidul metodelor. Pentru a demonstra acest lucru vom modifica clasa senzor si vom adauga in ea metode.

public class Senzor{
    int value;
    boolean active;
 
    void setActive(){
    	active = !active;
    	System.out.println("Sensor active status="+active);
    }
 
    void setValue(int k){
    	if(k<0 || k>100)
    		System.out.println("Invalid value. Senzor value unchanged!");
    	else{
    		value = k;
    		System.out.println("Sensor value="+value);
    	}
    }
 
    int getValue(){
    	return value;
    }
 
    public static void main(String[] args) {
		Senzor s1 = new Senzor();
		Senzor s2 = new Senzor();
 
		s1.setActive();
		s1.setValue(190);
		s1.setValue(67);
 
		System.out.println("Senzor s1 value="+s1.getValue());
	}
}

Am adaugat in clasa Senzor 3 metode prin intermediul carora manipulam starea obiectului (metodele setActive() si setValue()), respectiv preluam starea unui atribut al obiectului (metoda getValue()).

Pentru a respecta principiul incapsularii, accesul la starea interna a unui obiect nu trebuie sa se faca decat prin intermediul metodelor acestuia. Programatorul are la dispozitie o modalitate prin care poate limita accesul la diferiti membri ai clasei folosind specificatorii de acces.

Pentru a demonstra ca obiectele java sunt de tip referinta (adica variabila de tip obiect refera de fapt catre o adresa in memorie unde este localizat obiectul, si nu contine efectiv obiectul) vom modifica programul de mai sus in felul urmator:

Senzor{
...
 public static void main(String[] args) {
		Senzor s1 = new Senzor();
		Senzor s2 = new Senzor();
		Senzor s3 = null;
 
		s1.setActive();
		s1.setValue(190);
		s1.setValue(67);
 
		System.out.println("Senzor s1 value="+s1.getValue());
		System.out.println("Senzor s2 value="+s2.getValue());
		s2 = s1;
		s3 = s1;
		System.out.println("...");
		System.out.println("Senzor s2 value="+s2.getValue());
		System.out.println("Senzor s3 value="+s3.getValue());
    }
 }    
 

Executia clasei senzor in varianta modificata va produce urmatorul rezultat:

Sensor active status=true
Invalid value. Senzor value unchanged!
Senzor s1 value=0
Sensor value=67
Senzor s1 value=67
Senzor s2 value=0
...
Senzor s2 value=67
Senzor s3 value=67

Se observa ca atat s2 cat si s3 au valoarea senzorului egala cu 190. Aceasta deoarece prin atribuirile s2 = s1 si s3 = s1 am facut ca variabilele s2 si s3 sa pointeze catre acelasi obiect ca si s1. In acest moment avem practic 3 variabile (s1, s3 si s3) care pointeaza catre acelasi obiect in memorie.

Prin atribuirea s2 = s3 am facut ca variabila s2 sa pointeze catre acelasi obiect ca si s1, dar in acelasi timp, obiectul catre care referea variabila s2 a ramas fara nici o referinta catre el. In acest moment acel obiect nu maai poate fi accesat si devine eligibil pentru a fi eliberat din memorie de catre componenta garbage collector.