python

Programimi i orientuar në objekte

Hyrje

Në kapitullin e mëparshëm u shpjeguan konceptet kryesore të programimit të orientuar në objekte. Klasat dhe instancat e saj, atributet e klasave dhe të instancave, janë koncepte të rëndësishme të programimit të orientuar në objekte. Disa nga përparësitë e këtij lloj programimi, ndryshe nga ai që kemi bërë para se të flisnim për kapitullin Objektet dhe klasat, që ishte programimi funksional pasi bazohet tek funksionet, janë polimorfizmi, trashëgimia dhe enkapsulimi ( një huazim/përshtatje kjo nga anglishtja encapsulation që mund të shpjegohet si një folje që përshkruan mbylljen e diçkaje sikur e fusim në një kapsulë).

Python është një gjuhë e krijuar që në fillim për të qenë gjuhë e orientuar në objekte. Në python çdo gjë është objekt madje edhe klasat vetë janë objekte. Në këto dy dekadat e fundit sidomos, i është dhënë shumë përparësi kësaj mënyre programimi, gjithsesi mbetet ende i hapur debati si cila metodologji programimi është më e mira. Programimi i orientuar në objekte ofron më shumë avantazhe në programet e mëdha, ku rreshtat e kodit nuk mund të shkruhen nga një programues i vetëm. Mbi të gjitha tri janë parimet që ofrojnë përparësitë më të mëdha të programimit të orientuar në objekte:

Se cila është më e rëndësishmja prej tyre nuk është e vendosur rigorozisht. Programues të ndryshëm konsiderojnë polimorfizmin ose trashëgiminë të parë gjithashtu nuk përjashtohet enkapsulimi si më i rendesishmi nga programues të tjerë.

Polimorfizmi

Polimorfizmi është një teknikë që përdoret në programimin e orientuar në objekte dhe ofron përparësi mbi programimin funksional. Deri tani ne kemi parë shembuj kodesh vetëm me një klasë, por gjatë programimit të orientuar në objekte zakonisht kemi të bëjmë me shumë klasa. Për të shpjeguar polimorfizmin para se ta përkufizojmë si koncept le të marrim një shembull të thjeshtë: Le të kemi dy klasa të ndryshme: klasa Katror dhe klasa Trekendesh. Nëse kemi një metodë vizato, me anë të polimorfizmit ajo vepron tek të dyja klasat dhe vizaton gjëra të ndryshme. Pra kuptimi i metodës varet nga klasa. Kur vepron mbi instanca të klasës Katror, metoda vizato, jep një katror, ndërsa me instancat e klasës Trenkendesh vizaton një trekëndësh.

Polimorfizmi mundëson përdorimin e një metode tek objekte të klasave të ndryshme.

Le të shikojme një shembull të thjeshtë të përdorimit të polimorfizmit:

>>> class Katror(object):
        def vizato(self):
            print 'Katror'
>>> class Trekendesh(object):
        def vizato(self):
            print 'Trekendesh'
>>> def vizato_forme(forma):
        forma.vizato()
>>> katror = Katror()
>>> trekendesh = Trekendesh()
>>> vizato_forme(katror)
Katror
>>> vizato_forme(trekendesh)
Trekendesh

Të dyja klasat Katror dhe Trekendesh kanë nga një metodë vizato, e cila shfaq në ekran sipas klasës një rresht: Katror ose Trekendesh. Metoda tjetër vizato_forme() që merr si parametër një instancë të njërës prej klasave tona, thërret metoden vizato që gjendet tek të dyja klasat. Falë polimorfizmit mund të realizojme këtë metode që punon sipas objektit të klasës që i përket. Në rastin e mësipërm metoda vizato_forme() merr njëherë instancën e klasës Katror dhe shfaq ne ekran “Katror” dhe më pas instancën e klasës Trekendesh që shfaq ne ekran “Trekendesh”. Pra metoda vizato_forme thërriti në të dyja rastet metodën vizato të instancave, por kjo metodë veproi ndryshe në varesi të instancës së klasës që i përket.

Trashëgimia

Trashëgimia ashtu si polimorfizmi është një tjetër përparësi e programimit të orientuar në objekte. Trashëgimia nga vetë emri na bën të kuptojmë se kemi të bëjmë me trashëgimi dhe në fakt trashëgimia në programim përdoret ne rastet kur duam të shtojmë klasa me karakteristika të përgjithshme të ngjashme (metoda dhe ndryshore) me ato ekzistuese. Kështu shmangim shkrimin e kodit nga e para. Shembulli më klasik që jepet për të shpjeguar trashëgiminë në programim është krahasimi me sistemin e klasifikimit të kafshëve dhe bimëve. Psh kemi klasën mëmë Kafshë dhe klasat bija Gjitarë, Zvarranikë etj, të cilat kanë përkatësisht klasat bija Ketër, Kangur, etj. dhe Gjarpër, Breshkë etj.

Një tjetër shembull klasik janë personazhet e një loje kompjuterike. Mendoni një lojë me jashëtokësorë. Si klasë kryesore mund të kishim klasën Alien. Ndërsa klasa që do derivoheshin nga kjo klasë mund të ishin: Marsian, Plutonian etj.

Klasat bija Marsian dhe Plutonian kanë trasheguar nga klasa mëmë Alien, metodat dhe ndryshoret. Këto dy klasa quhen nënklasa të klasës mëmë Alien. Ndërsa klasa Alien është superklasë e nënklasave Marsian dhe Plutonian. Nëse kjo lojë do të kishte si qëllim luftën mes këtyre Alienve, atëherë nga klasa mëmë Alien, këto klasa do të trashëgonin shumë metoda si psh. ec, gjuaj, vrapo etj. Gjithashtu objekte të klasave të ndryshme mund të kishin përveç këtyre metodave të trashëguara edhe metoda të tjera të vetat për të arritur pikërisht qëllimin e lojës: disa personazhe mund më të ishin fortë a më të shpejtë sesa disa të tjerë.

Trashëgimia mundëson ripërdorimin e kodit, duke trashëguar nga klasa mëmë tek nënklasat, metodat dhe ndryshoret.

Sintaksa që përdoret për të krijuar një klasë bijë është:

class KlasaBije(KlasaMeme)

Le të shikojmë një shembull:

>>> class Person(object):
        def __init__(self, emri, mosha):
            self.emri = emri
            self.mosha = mosha

        def detajet(self):
            print "Emri: %s" % self.emri
            print "Mosha: %d" % self.mosha

>>> class Student(Person):
        def __init__(self, emri, mosha, kursi):
            Person.__init__(self, emri, mosha)
            self.kursi = kursi

        def printo_kursin(self):
            print "Kursi: %d" % self.kursi

>>> s = Student('Rajna', 19, 301)
>>> s.detajet()
Emri: Rajna
Mosha: 19
>>> s.printo_kursin()
Kursi: 301

Klasa Person ka dy metoda: një konstruktor dhe metodën detajet. Kjo metodë printon emrin dhe moshën e personit. Përveç kësaj klase na lind nevoja të krijojmë një klasë tjetër, e cila të përmbajë këto metoda dhe ndryshore, dhe gjithashtu të shtojë edhe një të dhënë tjetër specifike vetëm për këtë lloj klase. Pra klasa e re Student trashëgon metodat dhe ndryshoret e klasës Person dhe shton si të dhënë të re kursin që studenti po ndjek.

Në konstruktorin e klasës Student ne nuk kemi nevojë të krijojmë sërish ndryshoret për të dhënat emri dhe mosha. Këto atribute trashëgohen nga klasa mëmë Person. Ne mjafton të thërrasim konstruktorin e klasës mëmë si në rreshtin:

Person.__init__(self, emri, mosha)

për t'u hedhur vlerë këtyre ndryshoreve. Kjo është e nevojshme vetëm sepse ne duam t'i kalojmë konstruktorit të klasës Student një vlerë të re për kursin. Nëse nuk do të ishte nevoja për të krijuar këtë ndryshore të re, në klasën Student nuk do të kishim nevojë fare të krijonim konstruktorin __init__ me këto argumenta, ai thjesht do të trashëgohej nga klasa Person.

Po kështu metoda "detajet" e klasës Person trashëgohet vetvetiu në klasën bijë Student, prandaj ne mund ta përdorim atë direkt me kodin s.detajet() pa qenë nevoja që ta krijojmë këtë metodë në klasën Student.

Ndryshimi i vetëm i klasës bijë është informacioni për kursin që studenti ndjek. Për këtë arsye në konstruktorin e klasës Student ne krijojmë ndryshoren self.kursi dhe më pas shtojmë metodën e re, unike vetëm për klasën bijë, printo_kursin e cila shfaq në ekran vlerën e kësaj ndryshoreje.

Sintaksa e përdorur më sipër për të thirrur konstruktorin e klasës mëmë nga konstruktori i klasës bijë nuk është e vetmja mënyrë për ta realizuar këtë. Kodi:

Person.__init__(self, emri, mosha)

mund të shkruhej edhe kështu:

super(Student, self).__init__(emri, mosha)

Një avantazh i dukshëm i kësaj sintakse është fakti që nuk kemi nevojë të shkruajmë direkt emrin e klasës ( në këtë rast "Person"). Kjo është një lehtësi pasi nëse duhet ta ndryshojmë më vonë emrin e klasës mjafton ta ndryshojmë vetëm në një vend dhe jo në klasat bija që mund ta kenë përdorur. Kjo sintaksë ofron gjithashtu avantazhe të tjera kur kemi të bëjmë me trashëgimi të shumëfishtë e cila shpjegohet më poshtë.

Trashëgimia e shumëfishtë

Në disa raste lind domosdoshmëria e trashëgimit të më shumë se një klase. Sintaksa është e ngjashme me rastin e trashëgimisë së thjeshtë:

class emri_klases(klase, klase_tjeter, klase_tjeter)

Klasa trashëgon metoda dhe ndryshore nga të tria këto klasa. Mënyra sesi renditen këto klasa për të trasheguar metodat dhe ndryshoret fillon nga e majta në të djathtë.

Le të shikojme një shembull:

>>> class Prind1(object):
        def __init__(self,x,y):
            self.x = x
            self.y = y

        def shuma(self):
            self.shuma = self.x + self.y
            print "Shuma eshte %d" % self.shuma

>>> class Prind2(object):
        def produkti(self):
            self.produkti = self.x * self.y
            print "Produkti eshte %d" % self.produkti

>>> class Femije(Prind1,Prind2):
        pass

>>> objekt = Femije(2,5)
>>> objekt.shuma()
Shuma eshte 7
>>> objekt.produkti()
Produkti eshte 10

Kemi krijuar tri klasa: dy klasa që do t'i përdorim si prindër dhe një fëmijë. Në klasën Femije nuk kemi shkruar asgjë të re, por ajo trashegon konstruktorin dhe metodën shuma nga klasa Prind1 si dhe metodën produkti nga klasa Prind2. Siç e shohim gjatë testimit të programit metodat e përkufizuara tek prindërit ekzekutohen normalisht, pavarsisht se i thërrasim sikur të ishin pjesë e objektit të klasës Femije.

Nga parimet e programimit të orientuar në objekte, trashëgimia, dhe sidomos trashëgimia e shumëfishtë janë ato që kritikohen më shpesh. Këto janë koncepte mjaft të fuqishme, që siç e pamë mund të na lehtësojnë goxha punë sidomos për të shmangur përsëritjen e kodit; por gjithashtu duhet patur kujdes me përdorimin e trashëgimisë pasi ndonjëherë një gjë e tillë mund ta komplikojë kodin në mënyrë të panevojshme. Më poshtë do të shikojmë një shembull që përdor në mënyrë të thjeshtë trashëgiminë.

Shembull

Në kapitullin e Moduleve krijuam shembullin për peshën ideale. Struktura try except nuk ka përjashtime të gatshme për çdo rast. Kështu në këtë moment që kemi mësuar objektet dhe klasat jemi në gjendje të krijojmë një perjashtim tonin për rastin e përzgjedhjes së gjinisë dhe llojit të kockës.

Siç e kemi parë, nëse bëjmë një gabim sintakse si psh të pjesëtojmë me zero, "shkaktohet" automatikisht një gabim i llojit ZeroDivisionError; kjo na lejon që kur kemi një kod që mund të rezultojë në një gabim të tillë ta përfshijmë në strukturën try catch në këtë mënyrë:

try:
    8 / 0
except ZeroDivisionError:
    print 'nuk mund te pjesetohet me zero'

Në programin tonë, nëse përgjigja për gjininë nuk është as 'f' as 'm', kjo përbën një problem për llogjikën e programit tonë; por në këtë rast asnjë gabim i ndonjë lloji të gatshëm nuk do të shkaktohet nga python. Për sa i përket sintaksës, hedhja e një vlere të ndryshme nga 'f' dhe 'm' në një ndryshore me emrin gjinia nuk përbën asnjë problem. Për këtë arsye na duhet të krijojmë një "gabim" tonin për këtë rast:

class GabimGjinie(Exception):
    pass

Klasa GabimGjinie trashëgon nga klasa e gatshme Exception. Këtë klasë mund ta përdorim tani në këtë mënyrë:

def merr_gjinine():
    rezult = raw_input('Zgjidh gjinine femer(f) ose mashkull(m) ')
    if rezult != 'f' and rezult != 'm':
        raise GabimGjinie
    return rezult

Raise (angl. ngre) përdoret për të "shkaktuar" një gabim. Pra nëse inputi nuk është as 'f' as 'm' atëherë ngrejmë këtë përjashtim të krijuar nga ne. Më pas ky përjashtim mund të përdoret në strukturën try except njësoj si përjashtimet e gatshme si ZeroDivisionError.

while True:
    try:
        gjinia = merr_gjinine()
    except GabimGjinie:
        print 'Gjinia duhet te jete f ose m'
    else:
        break

Në mënyrë të ngjashme veprohet edhe me përjashtimin për llojin e kockës. Shembullin e plotë me ndryshimet e bëra mund ta shkarkoni këtu.

Fshehja e informacionit (Enkapsulimi)

Fshehja e informacionit është një nga tre parimet që përmendëm për programimin e orientuar në objekte, dhe e shpjeguam deri diku në fund të kapitullit të kaluar. Fshehja e informacionit nënkupton përdorimin e metodave pa u interesuar se çfarë përmbajtje kanë ato; kjo ka të bëjë gjithashtu me mos lejimin e përdorimit të ndryshoreve apo metodave nga jashtë klasës. Në python kjo gjë nuk është plotësisht e mundur, pasi gjithçka është publike. Gjithsesi deri diku mund t'i bëjmë private ndryshoret apo metodat e një klase.

Për të pasur më të qarte konceptin e fshehjes së informacionit në python, le të themi se jo çdo përdorues kompjuteri i intereson të dijë sesi janë krijuar tastiera, ekrani e të tjera pajisje kompjuteri. Nëse njëra nga këto pajisje do të prishej secili do të shkonte në dyqan të blinte një të re dhe mjafton të vendosë fishat dhe gjithçka i kthehet normalitetit. Përdoruesi nuk interesohet sesi funksionon së brendshmi tastiera; ai mjaftohet me faktin që tastiera është e lehtë për tu përdorur duke lidhur kabllin me kompjuterin. Njësoj ndodh edhe në programim. Përdoruesi i një klase nuk interesohet të studiojë se si funksionojnë metodat dhe ndryshoret e brendshme të nje klase, ai mjaftohet me faktin që klasa mund të përdoret lehtësisht me anë të metodave të posaçme.

Ky parim i programimit në gjuhë të ndryshme mund të jetë i detyrueshëm ose jo. Në python është menduar që enkapsulimi të mos imponohet, pra privatja të mos jetë plotësisht private. Edhe nëse do të na duhej në ndonjë rast të ndalonim aksesimin e metodave apo ndryshoreve, do t'i kthenim ato ne private duke patur parasysh se ato sërish mund të aksesohen.

Fshehja e informacionit ka të bëjë me mos aksesimin e metodave apo ndryshoreve private të një klase nga jashtë klasës.

Le të shikojme një shembull ilustrues të fshehjes se informacionit. Ne shembullin e mëposhtëm ka dy metoda: njëra private dhe tjetra publike. Për të treguar se një metodë është private në python mjafton të shkruajmë njëra pas tjetrës dy viza të poshtme __ dhe më pas emrin e metodes: def __metoda:

Por siç thamë, python nuk e suporton plotësisht fshehjen e informacionit, pra ka një mënyrë për të pasur akses. Për të aksesuar këto metoda private mund të përdorim gjatë testimit para emrit të klasës një vizë të poshtme. Le të shohim shembullin për më shumë qartësi:

>>> class Shembull(object):
    def __metode_private(self):
        print "Informacion privat i klases"
    def metode_publike(self):
        print "Informacion publik"
>>> obj = Shembull()
>>> obj.__metode_private()
Traceback (most recent call last):
  File "<pyshell#3>", line 1, in <module>
    obj.__metode_private()
AttributeError: Shembull instance has no attribute '__metode_private'
>>> obj._Shembull__metode_private()
Informacion privat i klases

Kodi obj._Shembull__metode_private() bën të mundur aksesimin e metodave private në python. Python pra nuk e garanton përmes sintaksës fshehjen e informacionit.