Programarea orientată pe obiecte, sau pe scurt OOP, este o filozofie de programare. OOP consideră obiectele ca unitatea de bază a programelor. Un obiect conține date și funcții pentru datele de operare.

Programarea orientată pe proces vede un program de calculator ca o serie de seturi de comenzi, adică execuția secvențială a unui set de funcții. Pentru a simplifica programarea, funcțiile orientate pe proces continuă să fie împărțite în sub-funcții, adică funcțiile mari sunt tăiate în funcții mici pentru a reduce complexitatea sistemului.

Programarea orientată pe obiecte privește un program de calculator ca pe o colecție de obiecte, iar fiecare obiect poate primi mesaje de la alte obiecte și procesa aceste mesaje. Execuția unui program de calculator este o serie de mesaje transmise între obiecte.

În Python, toate tipurile de date pot fi tratate ca obiecte și, desigur, obiectele pot fi, de asemenea, personalizate. Tipul de date de obiect personalizat este conceptul de clasă în orientare obiect.

Folosim un exemplu pentru a ilustra diferența dintre fluxul de program orientat pe proces și orientat pe obiect.

Să presupunem că vrem să procesăm lista de note a elevului. Pentru a reprezenta nota unui student, programul orientat pe proces poate fi reprezentat printr-un dict:

std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

Procesarea scorurilor studenților poate fi implementată prin funcții, cum ar fi imprimarea scorurilor studenților:

def print_score(std):
    print('%s: %s' % (std['name'], std['score']))

Dacă adoptăm idei de programare orientată pe obiecte, prima noastră prioritate nu este să ne gândim la procesul de execuție al programului, ci ca tipul de date Student să fie privit ca un obiect, care are două atribute: name și score. Dacă doriți să imprimați scorul unui student, trebuie mai întâi să creați un obiect corespunzător studentului și apoi să trimiteți un mesaj print_score obiectului pentru a-i permite obiectului să-și imprime propriile date.

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

Trimiterea unui mesaj către un obiect înseamnă de fapt apelarea funcției asociate corespunzătoare obiectului, pe care o numim Metoda obiectului. Un program orientat pe obiecte este scris astfel:

bart = Student('Bart Simpson', 59)
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()

Ideea de design orientat pe obiecte vine din natură, deoarece în natură, conceptele de clasă și instanță sunt foarte naturale. Clasa este un concept abstract. De exemplu, Clasa — Student pe care o definim se referă la conceptul de studenți, în timp ce Instanța este un anumit Student. De exemplu, Bart Simpson și Lisa Simpson sunt doi studenți specifici.

Prin urmare, ideea de proiectare orientată pe obiect este de a abstractiza Clasa și de a crea Instanță bazată pe Clasă.

Nivelul de abstractizare orientat pe obiect este mai mare decât cel al funcțiilor, deoarece o clasă conține atât date, cât și metode de operare a datelor.

Clasă și Instanță

Cele mai importante concepte de orientare pe obiecte sunt Clasa și instanță. Trebuie amintit că o clasă este un șablon abstract, cum ar fi clasa Student, iar o instanță este un „obiect” specific creat pe baza clasei. Fiecare obiect are aceeași metodă, dar datele individuale pot diferi.

Luând în continuare clasa Student ca exemplu, definirea unei clase în Python se face prin cuvântul cheie class:

class Student(object):
    pass

Imediat după class este numele clasei, adică Student. Numele clasei este de obicei un cuvânt care începe cu majusculă, urmat de object, indicând din ce clasă este moștenită clasa. Despre conceptul de moștenire vom vorbi mai târziu. Dacă nu există o clasă de moștenire adecvată, trebuie doar să utilizați clasa object, care este clasa pe care toate clasele o vor moșteni în cele din urmă.

După definirea clasei Student, puteți crea o instanță a lui Student pe baza clasei Student. Crearea unei instanțe se realizează prin numele clasei + ():

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

După cum puteți vedea, variabila bart indică o instanță de Student, iar următorul 0x10a67a590 este adresa de memorie. Adresa fiecărui obiect este diferită, iar Student în sine este o clasă.

Puteți lega liber atribute la o variabilă de instanță. De exemplu, legați un atribut name la instanța bart:

>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'

Deoarece clasele pot funcționa ca șabloane, atunci când creăm o instanță, putem completa cu forță unele atribute care credem că trebuie legate. Prin definirea unei metode speciale __init__, atunci când se creează o instanță, name, score și alte atribute sunt legate de aceasta:

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
★ There are two underscores before and after the special method "__init__"! ! !

Rețineți că primul parametru al metodei __init__ este întotdeauna self, care reprezintă însăși instanța creată. Prin urmare, în cadrul metodei __init__, puteți lega diverse atribute la self, deoarece self indică la instanța creată în sine.

Cu metoda __init__, atunci când creați o instanță, nu puteți trece parametri gol. Trebuie să treceți parametrii care se potrivesc cu metoda __init__, dar self nu trebuie să fie transmis. Interpretul Python va trece în sine variabilele de instanță:

>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59

În comparație cu funcțiile obișnuite, singura diferență între funcțiile definite într-o clasă este că primul parametru este întotdeauna variabila de instanță self, iar acest parametru nu trebuie să fie transmis la apelare. În afară de aceasta, metodele de clasă nu sunt diferite de funcțiile obișnuite, așa că puteți utiliza în continuare parametrii impliciti, parametrii variabili, parametrii cheie și parametrii cheie numiți.

Încapsularea datelor

O caracteristică importantă a programării orientate pe obiecte este încapsularea datelor. În clasa Student de mai sus, fiecare instanță are propriile sale date name și score. Putem accesa aceste date prin funcții, cum ar fi tipărirea notei unui student:

>>> def print_score(std):
...     print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59

Deoarece instanța Student însăși deține aceste date, pentru a accesa aceste date, nu este nevoie să le accesați din funcții externe. Puteți defini direct funcția de accesare a datelor în interiorul clasei Student, încapsulând astfel „datele”. Aceste funcții care încapsulează datele sunt asociate cu clasa Student însăși, pe care o numim metode de clasă:

class Student(object):

def __init__(self, name, score):
        self.name = name
        self.score = score
    def print_score(self):
        print('%s: %s' % (self.name, self.score))

Pentru a defini o metodă, este la fel ca o funcție obișnuită, cu excepția faptului că primul parametru este self. Pentru a apela o metodă, trebuie doar să o apelați direct pe variabila de instanță. Cu excepția self, nu trebuie să-l treci. Alți parametri sunt trecuți în mod normal:

>>> bart.print_score()
Bart Simpson: 59

În acest fel, când privim clasa Student din exterior, trebuie doar să știm că name și score trebuie date pentru a crea o instanță, iar modul de imprimare este definit în interiorul clasei Student. Aceste date și logică sunt „încapsulate”. Este ușor de apelat, dar nu trebuie să cunoașteți detaliile implementării interne.

Un alt avantaj al încapsulării este că puteți adăuga noi metode la clasa Student, cum ar fi get_grade:

class Student(object):
    ...

def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

Aceeași metodă get_grade poate fi apelată direct pe variabila de instanță fără a cunoaște detaliile interne de implementare:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >= 60:
            return 'B'
        else:
            return 'C'

Parametrii de intrare:

lisa = Student('Lisa', 99)
bart = Student('Bart', 59)
print(lisa.name, lisa.get_grade())
print(bart.name, bart.get_grade())

Rezultat de rulare:

Lisa A
Bart C