Povezivanje klasa (Inheritance)

14.6 Povezivanja klasa (Inheritance) 

Sada kada smo se malo i upoznali sa OOP načinom programiranja, hajde da vidimo još dodatnih mogućnosti koje nam ono pruža. Jedna od glavnih prednosti objektno orijentisanog programiranja je ponovna upotreba istog dela programa u različitim situacijama. Ovo se ni po čemu na razlikuje od upotrebe modula i funkcija – i to si već video, ali, upotrebom OOP-a, nama se pruža jedan fantastičan način da ovo postignemo - mehanizam povezivanja klasa (inheritance). Povezivanje klasa se najbolje može predstaviti kao određivanje odnosa između tipova i podtipova između klasa. Na primer, u određenim problemima ti možeš da razmišljaš u ovom smeru – direktor je radnik, sekretarica je isto radnik. Znači stvorićeš klasu radnik sa zajedničkim karakteristikama (plata, ime firme itd...), a zatim kreiraš klasu direktor sa karakteristikama tipa kontakti sa drugim direktorima, socijalne veze, znanje rukovođenja, sektori u kojima je zadužen. Zatim bi kreirali klasu sekretarica koja bi imala neke svoje karakteristike. I klasa direktor i klasa sekretarica bi imale matičnu klasu radnik, tako da u ovim podklasama ne bi smo morali definisati ono što smo već u matičnoj (tzv super) klasi definisali, već bi ove podklase automatski kupile metode superklase (tipa plata, firma itd).
Drugi primer bi bio neki proizvođač kompjuterske opreme. Sam proizvođač je superklasa, a može da ima podklase koje bi se zvale PC, tablet, smartphone itd.
Treći primer ćemo da obrađujemo u ovom poglavlju. Pretpostavimo da imamo neki problem za čije rešavanje želimo da napišemo program koji vodi evidenciju roditelja i dece u porodicama. U početnom razmišljanju stvaramo dve klase – klasu roditelji i klasu deca. Ove klase imaju neke zajedničke osobine kao što su ime, starost i adresa. Ove karakteristike možemo da stavimo u novu klasu – porodica, iz koje  će biti izvedene klase roditelji i deca. Ove klase imaju specifične karakteristike kao što su prihod, zanimanje itd za roditelje i škola i džeparac za decu. 
Mi bi smo mogli kreirati dve nezavisne klase za roditelje i decu, ali ako želimo da dodajemo neke nove zajedničke karakteristike, koje smo u međuvremenu uočili, bi za nas značilo da je moramo dodati u obe od ovih nezavisnih klasa. Ovo bi ubrzo postalo veoma naporno, a i sam program bi postao preglomazan. 
No, sada znamo da je bolji način rešavanja ovakvog problema da se stvori zajednička klasa porodica, pa da se potom kreiraju klase roditelj i dete, koje su povezane sa (izvedene iz) klase porodica, tj roditelj i dete će postati podtipovi tipa porodica, i zatim mi možemo da dodajemo uočene specifične karakteristike adekvatnom podtipu, a ako uočimo zajedničke karakteristike – dodajemo ih u superklasu porodica. 
Postoje mnoge prednosti, ali i mane, ukoliko se odlučujemo za ovakav pristup rešavanja problema. Ako mi želimo da dodajemo ili menjamo nekakve funkcionalnost klase porodica, to se automatski odražava u svim njenim podtipovima. Na primer, možemo da dodamo neko novo polje koje predstavlja broj zdravstvenog kartona, koji imaju i roditelji i deca, tako što ga jednostavno definišemo u klasi porodica. Međutim, ukoliko izvršimo promene unutar neke klase koja je podtip – ta promena nemože da utiče na ostale klase koji su podtipovi iste superklase. Još jedna prednost ovakvog pristupa je ta da se možmo odnositi prema objektima koje su kreirale klase roditelj ili dete, kao objektima koje je kreirala klasa porodica, što može da bude korisno u mnogim situacijama, npr, kad nam je potreban broj članova porodice itd. Ovo se u programiranju naziva polimorfizam – u svakom trenutku se može očekivati da će neki podtip biti zamenjen sa glavnim tipom – tj objekat podtipa je uvek i objekat matične klase. 
Ako je terminologija malo nejasna: klasa porodica se, u ovoj situaciji, naziva osnovna ili super klasa (jer od nje sve kreće). A klase roditelj i dete se nazivaju izvedene ili podklase (jer se nalaze „ispod“ klase porodica). 
Sada ćemo ovaj primer oživeti i videti kako da ga pretvorimo u program koji možemo da sačuvamo pod nazivom porodica.py.
Prvo što treba da znamo je kako da „napravimo“ inheritaciju. Da bi smo koristili povezivanje klasa u našim programima, mi prvo moramo definisati superklasu kao što smo i do sada radili. Zatim, prilikom definisanja izvedenih klasa, mi moramo navesti ime bazne klase u zagradi koju stavljamo odmah iza imena izvedene klase. To treba da izgleda ovako:
class Superklasa:
    telo_klase
class PodKlasa(Superklasa):
    telo_klase
Sledeće što treba da znamo da prilikom inicijalizacije (kreiranja) objekta koji je podtip – tj u __init__ metodi podklase mi moramo da pozovemo i __init__ metod super klase, tako što mu moramo proslediti sam objekat podklase – i to znamo šta znači: koristimo promenljivu self. To nam služi da inicijalizujemo i deo za koji je zadužena super klasa objekta. Ovo je veoma važno, i treba to uvek da imaš na umu - Python nikada neće automatski da pozove konstruktor bazne klase (__init__ metod), već ga ti moraš pozvati prilikom inicijalizacije podklase (ili na nekom drugom željenom mestu).
Možda deluje konfuzno, ali to jednostavno treba da izgleda ovako:
class PodKlasa(Superklasa):
    def __init__(self, parametri_podklase):
        Superklasa.__init__(self, parametri_superklase)
        neki_drugi_rad
Takođe, treba da znaš da se pozivanje metoda koje pripadaju baznoj klasi radi na taj način što se navodi klasno ime kao prefiks, koji je ispraćen pozivom metode, sa parametrima u kojima je prisutna i self promenljiva, a zatim je ona praćena sa nekim argumentima (ako su potrebni toj metodi). Ovo je slično onome što smo uradili sa __init__ metodom superklase.
Vreme je da počnemo našu konstrukciju:
#!/usr/bin/env python3 

'''Skripta koja simulira porodicu.'''

class Porodica:
'''Predstavlja bilo kog člana porodice.'''
def __init__(self, ime, godine):
'''Kreira člana porodice.'''
self.ime = ime
self.godine = godine
print('Kreiran je član porodice: {}'.format(self.ime))
def prikaz(self):
'''Prikazuje detalje.'''
print('\tIme:\t{0}\n\tGodina:\t{1}'.format(self.ime, self.godine))

class Roditelj(Porodica):
'''Predstavlja roditelje.'''
def __init__(self, ime, godine, zarada, opis):
'''Kreira roditelja.'''
Porodica.__init__(self, ime, godine)
self.zarada = zarada
self.opis = opis
print('Kreiran je roditelj: {0}, koji je {1}.'.format(self.ime, self.opis))
def prikaz(self):
'''Prikaz detalja.'''
Porodica.prikaz(self)
print('\tZarada:\t{0}\n\tOpis:\t{1}'.format(self.zarada, self.opis))

class Dete(Porodica):
'''Predstavlja decu.'''
def __init__(self, ime, godina, džeparac, opis):
'''Kreira dete.'''
Porodica.__init__(self, ime, godina)
self.džeparac = džeparac
self.opis = opis
print('Kreirano je dete {} koje je {}'.format(self.ime, self.opis))
def prikaz(self):
'''Prikaz detalja.'''
Porodica.prikaz(self)
print('\tTroši:\t{0}\n\tOpis:\t{1}'.format(self.džeparac, self.opis))

Što nam u Python interaktivom promptu daje izlaz:
>>> import porodica 
>>> njonje = [('Pera Ložač', 65, 12000, 'otac'), ('Keramika Ložač', 30, 96000, 'mati')]
>>> šonje = [('Kenija Tarzan', 45, 45000, 'kći'), ('Tarzan Ložač', 6, 0, 'sin')]
>>> rod = []
>>> for i in njonje:
... a, b, c, d = i
... rod.append(porodica.Roditelj(a, b, c, d))
...
Kreiran je član porodice: Pera Ložač
Kreiran je roditelj: Pera Ložač, koji je otac.
Kreiran je član porodice: Keramika Ložač
Kreiran je roditelj: Keramika Ložač, koji je mati.
>>> for i in šonje:
... a, b, c, d = i
... rod.append(porodica.Dete(a, b, c, d))
...
Kreiran je član porodice: Kenija Tarzan
Kreirano je dete Kenija Tarzan koje je kći
Kreiran je član porodice: Tarzan Ložač
Kreirano je dete Tarzan Ložač koje je sin
>>> for i in rod:
... i.prikaz()
...
Ime: Pera Ložač
Godina: 65
Zarada: 12000
Opis: otac
Ime: Keramika Ložač
Godina: 30
Zarada: 96000
Opis: mati
Ime: Kenija Tarzan
Godina: 45
Troši: 45000
Opis: kći
Ime: Tarzan Ložač
Godina: 6
Troši: 0
Opis: sin
>>>

Obrati pažnju da smo instance od Roditelj klase ili klase Dete smatrali kao da su instance klase Porodica kada koristimo metodu prikaz(). Za domaći ti ostavljam da izmeniš gornji kod – da dodaš neke nove metode u svaku od klasa, da self.opis polje prebaciš u superklasu (zajednička je za podklase, šta će onda u njima?) itd.
Takođe, obrati pažnju na metod prikaz() podklase u kom se poziva istoimena metoda superklase. Ovo se mora uraditi, jer bi u suprotnom metoda prikaz() sakrila metod sa istim imenom u superklasi. No, čak i da imamo neku podklasu u kojoj nismo definisali metodu prikaz(), ukoliko bi smo na kreiranom objektu te klase primenili objkat.prikaz() - funkcija bi radila! Python uvek kreće u potragu za traženom metodom u trenutnom tipu (klasi koja je kreirala objekat), što, u našem primeru on i radi. Ako nije mogao da pronađe taj metod u toj podklasi, on kreće da pretražuje metode koje pripadaju superklasi.
Jedna napomena, izvedena klasa ne mora da ima jednu superklasu – ona zapravo može imati niz superklasa sa kojima inherituje. Ako je ovakav slučaj u pitanju, Python će pretraživati sve superklase, jednu po jednu, redosledom kojim su navedene u zagradama prilikom definisanja izvedene klase. 
Terminologija koja se ovde često koristi - ako je više od jedne klase navedeno u zagradama prilikom definisanja povezivanja klase, onda se to naziva multi-povezivanje (multiple inheritance). 
Povezivanje više klasa može da bude konfuzno i nepregledno – neki put ne znamo koji metod imamo na raspolaganju i koji metod je pružila koja klasa, da li će neki metod imati isto ime kao neki drugi u nekoj od superklasa itd. Zbog svega toga – ovo treba izbegavati!

14.5 Promenljive koje pripadaju
klasi ili objektu
Indeks14.7 Rezime

Коментари