Principii OOP
Programare orientata pe obiecte reprezinta o metoda de proiectarea si dezvoltare a aplicatiilor care are la baza concepte precum clasa, obiectul si interactiunea dintre obiecte prin intermediul mesajelor.
Obiectele reprezinta componente software care modeleaza entitati sau concepte din lumea reala si care detin: o identitate, definita printr-un nume, o stare, definita prin atribute, si un comportament, definit prin metode. Obiectele care descriu aceeasi entitate sau concept pot fi grupate in multimi de obiecte, denumite clase.
Programarea orientata pe obiecte este caracterizata de o serie de principii, precum: abstractizarea, incapsularea, mostenirea si polimorfismul.
Paradigma | Semnificatie |
---|---|
abstractizarea | proces de simplificare a realitatii, pentru construirea unui model |
incapsularea | proprietate care are in vedere opacitatea obiectelor |
mostenirea | proprietare care descrie relatiile existente intre clase |
polimorfismul | abilitate care permite procesarea obiectele in functie de tipul lor |
Clase
O clasa reprezinta o structura logica care defineste comportamentul si starea obiectelor. Dupa definirea clasei, aceasta poate fi utilizata pentru a crea obiecte de tipul respectiv.
In limbajul Python, definirea unei clase se realizeaza prin intermediul cuvantului cheie class, dupa care este precizata denumirea clasei. Corpul clasei cuprinde instructiuni care permit definirea membrilor clasei, atribute de tip data sau metode.
class nume_clasa: """doc string""" instructiuni
Am avut in vedere descrierea clasei Persoana prin intermediul atributelor de instanta nume si varsta si a atributului de clasa numar_persoane. Clasa introduce si o metoda constructor, __init__(), necesara in momentul instantierii clasei (permite crearea de obiecte instanta de tip Persoana).
class Persoana(): | |
"""Clasa Persoana""" | |
numar_persoane = 0; | |
def __init__(self, nume, varsta): | |
self.nume = nume | |
self.varsta = varsta | |
__class__.numar_persoane += 1 | |
def se_prezinta(self): | |
print("Buna! Numele meu este {} si am {} ani.".format(self.nume, self.varsta)) | |
def __str__(self): | |
return "{0}, {1}, {2}".format(self.nume, self.varsta, __class__.numar_persoane) |
Primul element de la nivelul unei clase, denumit si doc string, permite precizarea unei scurte descrieri cu privire la clasa ce urmeaza a fi introdusa. Desi acest sir de caractere (comentariu) nu este obligatoriu, este recomandata utilizarea lui.
La definirea unei clase este creat si un obiect de tip clasa (class object) care detine numele clasei. Obiectul clasa permite accesarea atributelor clasei si contine domeniul de definitii de la nivelul clasei, denumiri de atribute si metode.
>>> Persoana.numar_persoane 0 >>> Persoana.__doc__ 'Clasa Persoana' >>> Persoana.__name__ 'Persoana' >>> Persoana.__bases__ (<class 'object'>,)
Atribut | Semnificatie |
---|---|
__doc__ | comentariul de documentare corespunzator clasei (implicit None) |
__name__ | numele clasei |
__module__ | numele modulului in care clasa este definita |
__dict__ | dictionar corespunzator spatiului de nume al clasei |
__bases__ | tuplu care contine clasele de baza |
Instante
Crearea unei instante (obiect instanta) pentru o clasa se realizeaza prin intermediul constructorului clasei. Rolul principal al constructorului este acela de a initializa variabilele unui obiect, atunci cand acesta este creat. Apelarea constructorului pentru clasa Persoana se realizeaza astfel:
>>> marianpopescu = Persoana("Marian Popescu", 35)
>>> marianpopescu.numar_persoane 1 >>> Persoana.numar_persoane 1
Daca metoda constructor __init__() introduce o lista de parametri, atunci la apelul ei vor fi precizate argumente corespunzatoare.
def __init__(self, nume, varsta): self.nume = nume self.varsta = varsta __class__.numar_persoane += 1
Obiectele instanta pot referi variabile instanta/atribute de tip data si functii de instanta/metode. Primul argument pentru metodele de instanta ale unei clase este intotdeauna obiectul curent, denumit self. Prin intermediul lui self sunt accesate atributele de instanta pentru obiectul curent.
>>> marianpopescu.nume 'Marian Popescu' >>> marianpopescu.varsta 35 >>> marianpopescu.se_prezinta() Buna! Numele meu este Marian Popescu si am 35 ani.
Adaugarea, stergerea sau modificarea atributelor pentru obiectele de tip clasa sau instanta se poate realiza in orice moment.
>>> marianpopescu.inaltime = 1.78 >>> print(marianpopescu) Marian Popescu, 35, 1 >>> marianpopescu.inaltime 1.78 >>> del marianpopescu.inaltime >>> marianpopescu.inaltime Traceback (most recent call last): File "<pyshell#14>", line 1, in <module> marianpopescu.inaltime AttributeError: 'Persoana' object has no attribute 'inaltime'
De asemenea, accesarea atributelor obiectelor instanta se poate realiza si prin intermediul urmatoarelor functii: getattr(), hasattr(), setattr() si delattr().
Functie | Semnificatie |
---|---|
getattr(obiect, atribut) | returneaza valoarea atributului pentru obiectul precizat |
hasattr(obiect, atribut) | verifica prezenta atributului pentru obiectul precizat |
setattr(obiect, atribut) | actualizeaza valoarea atributului pentru obiectul precizat |
delattr(obiect, atribut) | sterge atributul pentru obiectul precizat |
>>> hasattr(marianpopescu, 'nume') True >>> getattr(marianpopescu, 'nume') 'Marian Popescu' >>> setattr(marianpopescu, 'nume', 'Marian C. Popescu') >>> getattr(marianpopescu, 'nume') 'Marian C. Popescu' >>> delattr(marianpopescu, 'varsta') >>> marianpopescu.varsta Traceback (most recent call last): File "<pyshell#39>", line 1, in marianpopescu.varsta AttributeError: 'Persoana' object has no attribute 'varsta'
Mostenire
In programarea orientata pe obiecte mostenirea permite construirea de ierarhii de clase. Mostenirea reprezinta o proprietate a claselor prin intermediul careia poate fi definita o noua clasa, care sa preia atributele unei clase mai vechi. Aceasta proprietate este implementata cu ajutorul claselor derivate.
Clasa derivata, sau subclasa, se gaseste intotdeauna pe un nivel inferior celui corespunzator superclasei, sau clasei de baza. In limbajul Python poate fi utilizata mostenirea simpla sau multipla, adica fiecare clasa derivata are una sau mai multe clase de baza (parinte).
class A: instructiuni class B(): instructiuni class C(A, B): instructiuni
Relatia de derivare este precizata la definirea clasei derivate si are in vedere precizarea uneia sau mai multor clase parinte. In exemplul precedent, clasa C mosteneste toate atributele de la nivelul claselor parinte (A, respectiv B).
Clasele derivate mostenesc atributele claselor de baza la care adauga noi atribute. Atributele care nu sunt gasite la nivelul unei clase sunt verificate (cautate) la nivelul claselor de baza. O clasa derivata poate supradefini datele si metodele membre de la nivelul parintelui.
class Student(Persoana): | |
"""Clasa Student""" | |
def __init__(self, nume, varsta, facultate, an): | |
super().__init__(nume, varsta) | |
self.facultate = facultate | |
self.an = an | |
def __str__(self): | |
return "{0}, {1}, {2}".format(super().__str__(), self.facultate, self.an) | |
def se_prezinta(self): | |
super().se_prezinta() | |
print("Sunt student la facultatea {} in anul {}".format(self.facultate, self.an)) |
Polimorfism
Polimorfismul reprezinta abilitatea de a procesa obiectele diferit, in functie de tipul lor. Polimorfismul descrie situatia in care un nume se refera la doua metode diferite.
In limbajul Python, polimorfismul permite definirea de metode cu acelasi nume la nivelul claselor aflate in relatie de mostenire. In acest caz vorbim de un polimorfism de supradefinire.
class Persoana(): | |
"""Clasa Persoana""" | |
numar_persoane = 0; | |
def __init__(self, nume, varsta): | |
self.nume = nume | |
self.varsta = varsta | |
__class__.numar_persoane += 1 | |
def se_prezinta(self): | |
print("Buna! Numele meu este {} si am {} ani.".format(self.nume, self.varsta)) | |
def __str__(self): | |
return "{0}, {1}, {2}".format(self.nume, self.varsta, __class__.numar_persoane) |
La nivelul clasei Student sunt supradefinite/redefinite metodete se_prezinta si __str__(). Ambele metoda sunt definite si la nivelul clasei de baza, Persoana. Si la nivelul clasei Persoana este supradefinita metoda __str__(), metoda care este definita la nivelul clasei object.
class Student(Persoana): | |
"""Clasa Student""" | |
def __init__(self, nume, varsta, facultate, an): | |
super().__init__(nume, varsta) | |
self.facultate = facultate | |
self.an = an | |
def __str__(self): | |
return "{0}, {1}, {2}".format(super().__str__(), self.facultate, self.an) | |
def se_prezinta(self): | |
super().se_prezinta() | |
print("Sunt student la facultatea {} in anul {}".format(self.facultate, self.an)) |
Incapsulare
Incapsularea exprima proprietatea de opacitate a obiectelor cu privire la structura lor interna si modul de implementare a metodelor. Fiecare element al sistemului poate executa actiuni, isi poate modifica starea si poate comunica cu alte elemente fara a dezvalui facilitatile detinute.
La nivelul clasei Persoana putem implementa incapsularea prin prefixarea numelui atributelor de instanta (nume, varsta)cu underscore. Limbajul Python protejeaza membrii declarati cu dublu underscore prin modificarea numelor acestora cu numele clasei.
class Persoana(): | |
"""Clasa Persoana""" | |
numar_persoane = 0; | |
def __init__(self, nume, varsta): | |
self.__nume = nume | |
self.__varsta = varsta | |
__class__.numar_persoane += 1 | |
def __str__(self): | |
return "{0}, {1}, {2}".format(self.__nume, self.__varsta, __class__.numar_persoane) | |
def se_prezinta(self): | |
print("Buna! Numele meu este {} si am {} ani.".format(self.get_nume(), self.__varsta)) | |
def set_nume(self, nume): | |
self.__nume = nume | |
def get_nume(self): | |
return self.__nume |
Pentru citirea sau modificarea datelor membre protejate in clase pot fi definite metode de instante de tip set()/get(), care pot fi apelate din orice punct al domeniului de definitie al clasei.
>>> marianpopescu = Persoana("Marian Popescu", 35) >>> marianpopescu <__main__.Persoana object at 0x03B0A970> >>> print(marianpopescu) Marian Popescu, 35, 1 >>> marianpopescu.nume Traceback (most recent call last): File "<pyshell#3>", line 1, in marianpopescu.nume AttributeError: 'Persoana' object has no attribute 'nume' >>> marianpopescu.get_nume() 'Marian Popescu' >>> marianpopescu.set_nume('Marian C. Popescu') >>> print(marianpopescu) Marian C. Popescu, 35, 1