Uvod
Objektno orijentisano programiranje je jedna od paradigmi u programiranju, odnosno, jedan od pristupa u projektovanju računarskih programa, koji podrazumeva da se problemi rešavaju upotrebom "objekata":
- u idejnom smislu, pojedinačni objekat predstavlja određeni aspekt konkretnog problema
- u tehničkom smislu, objekat se može shvatiti kao funkcionalna celina u programskom kodu koju odlikuje određeno stanje i ponašanje
- objekti nastaju instanciranjem klasa (postupak izvođenja objekata sa konkretnim svojstvima, shodno unapred definisanom planu mogućih svojstava)
- klasa predstavlja programski kod preko koga se definišu moguće odlike budućih objekata
Može se reći da simulacije objekata iz spoljnjeg sveta u računarskim sistemima (razne simulacije vožnje, letenja, elektronskih kola, industrijskih procesa i sl), predstavljaju verovatno najočigledniji i najrazumljiviji primer objektno orijentisanog pristupa, ali, svakako ne i jedini.
Objekti su takođe i padajući meniji u browseru u kome trenutno čitate članak, ikone na desktopu, a postoje i brojni drugi objekti koji se ne vide, već 'samo' podržavaju strukturu različitih programa koji se sreću u svakodnevnoj upotrebi.
Tematika objektno orijentisanog programiranja je složena, potrebno je pristupiti joj na ozbiljan način i uložiti odgovarajući trud da bi se postigli pravi rezultati, i stoga ćemo se potruditi da u nastavku predočimo sve što je neophodno za početno razumevanje ....
Još nekoliko uvodnih napomena
Za uspešan početak bavljenja objektno orijentisanim programiranjem potrebno je solidno predznanje o proceduralnom programiranju, nezanemarljivo iskustvo u pisanju jednostavnijih programa, i potrebno je biti upoznat sa tim šta OOP * pristup (uopšte) podrazumeva. **
U slučaju većine čitalaca i drugih pojedinaca koji započinju upoznavanje sa objektno orijentisanim programiranjem, ukoliko su prva dva prethodno navedena uslova zadovoljena, tipično postoji i bar osnovna predstava o smislu i značaju OOP pristupa (vrlo često i o ponekim 'tehnikalijama'), ali, pre nego što pređemo na osnovne pojmove objektno orijentisanog programiranja i primere, prvo ćemo prodiskutovati ("za svaki slučaj"), o razlikama između proceduralnog i objektno orijentisanog pristupa, a iznećemo i kraći istorijat OOP-a (uz osvrt na to koje bi jezike trebalo koristiti a koje izbegavati pri početnom upoznavanju sa metodologijom objektno orijentisanog programiranja) ....
Osnovne postavke, razlike u odnosu na proceduralni pristup
U proceduralnom programiranju (što, da se podsetimo, predstavlja zvanični naziv za "obično programiranje" sa kakvim već imate iskustva), primenjuje se tzv. pristup "odozgo-nadole" (eng. top-down), koji podrazumeva da se problem prvo sagledava u celosti, i zatim po potrebi 'razbija' na potprobleme.
U praktičnom smislu, potproblemi se tipično rešavaju preko funkcija (koje po pravilu ne bi trebalo da sadrže više od tridesetak linija koda).
Navedeni pristup (čitaocima poznat od ranije), primereno je koristiti u mnogim situacijama, ali - ne i uvek.
U velikim projektima, složenost celokupnog posla je tipično tolika da ne dozvoljava da se problem sagleda odjednom (i zatim podeli na manje celine), i stoga se u pomenutim situacijama - umesto top-down pristupa - tipično koristi pristup koji podrazumeva da se prvo detaljno definišu: osnovni delovi sistema ('objekti'), pojedinačni način funkcionisanja svakog od delova, kao i načini za međusobno povezivanje.
Na kraju, celokupan sistem nastaje spajanjem i interakcijom pojedinačnih elemenata, i u pitanju je pristup koji je poznat kao "odozdo-nagore" (eng. bottom-up).
Ako uzmemo dobro poznati primer simulacija vožnje na računaru, lako možemo razumeti da se kao objekti prirodno javljaju automobili i staze, * što znači da je prvo potrebno detaljno definisati pravila po kojima svaki od objekata funkcioniše, a potom i međusobne odnose, koji odgovaraju poznatim zakonitostima fizike (gravitacija, inercija, trenje, detekcija sudara i sl), čime se na kraju kreira funkcionalan sistem.
Ako bismo pokušali da navedeni problem simulacije sagledamo "odjednom" (tj. da primenimo pristup 'odozgo-nadole') .... verovatno bismo izazvali "blago" preopterećenje sopstvenog aparata za razmišljanje. :)
Takođe (pored pojedinosti koje smo do sada naveli), pretpostavljamo da je jasno da pravilno i 'glatko' funkcionisanje prethodno opisanog sistema, u praktičnom smislu zavisi (veoma nedvosmisleno (!)), i od raspoloživih računarskih resursa (tj. od toga da li ima dovoljno memorije za smeštaj podataka, da li procesor može da pokreće simulaciju dovoljnom brzinom i sl), međutim, slični zahtevi važe (naravno) i za "ne-OOP" programe (hardverski zahtevi postoje uvek i, blago uprošćeno, može se reći da ne zavise od izbora metodologije za projektovanje softvera).
Kada koristiti proceduralni pristup, a kada objektno orijentisani
U simulaciji vožnje, na prirodan način smo prepoznavali računarske objekte koji nastaju po uzoru na objekte iz spoljnjeg sveta, ali, sa nekim drugim algoritamskim problemima, to možda ne bi bio slučaj.
Jednostavno rečeno - ne moraju se svi zadaci rešavati korišćenjem OOP pristupa.
OOP pristup nije "bolji ili gori" od proceduralnog, već je samo u određenoj situaciji jedan od dva navedena pristupa primereniji, i oba pristupa - i te kako - imaju mesto u informacionim tehnologijama.
OOP pristup se tipično vezuje za veće projekte u kojima učestvuju (veći) timovi programera, pri čemu je potrebno voditi računa o dobrom dizajnu klasa, čitljivosti koda i održivosti na duže staze.
Naravno, ima i projekata manjeg obima čija je struktura kompleksna, što znači da je za takve projekte primereno koristiti OOP pristup, međutim (da ponovimo), za mnoge manje programe jednostavne strukture (kao i pomoćne skripte i sličan softver), objektno orijentisani pristup nije obavezan, to jest, proceduralni pristup može poslužiti sasvim dobro.
Ali, da bismo dodatno 'zakomplikovali priču', osvrnućemo se na još nekoliko situacija ....
Sa jedne strane, u praksi postoji i veći broj proceduralnih programa koji "nisu mali" (recimo, imaju preko 500, 1000, pa i više linija koda), veoma su kompleksni i zahtevni za razumevanje (čak i ukoliko su dobro optimizovani), mogu biti vrlo značajni u određenom informacionom sistemu, ali - ipak ne zahtevaju OOP pristup.
Sa druge strane, mnogi naoko obimni programi napisani preko OOP tehnika, zapravo su relativno jednostavni i "rutinski" (na primer, mnoge aplikacije koje se povezuju sa bazama podataka, zarad pregleda prometa u maloprodaji i sl), pri čemu, naravno, nema ničeg 'pogrešnog' u tome što je 'tako kako smo naveli'.
Situacija zaista ima raznih. :)
U svakom slučaju, u iole razumnim okolnostima (i pošto steknete dovoljno iskustva), bićete u stanju da razaznate zašto neko "jeste ili nije" izabrao OOP pristup ili proceduralni pristup i, takođe, kao što smo već naglasili: i jedan i drugi pristup imaju svrhu, a poenta OOP-a (naravno) nije da se mali programi učine komplikovani(ji)m, već, da se veliki programi zapišu na pregledniji način, i učine jednostavnijim za održavanje.
Kratak istorijat OOP-a
Postoji svojevrstan "Mandela efekat" kod mnogih programera koji smatraju da istorija OOP-a počinje sredinom 80-ih godina dvadesetog veka, međutim, objektno orijentisani pristup prvi put se pojavio već početkom šezdesetih godina.
U to vreme, dvojica norveških programera, Ole-Johan Dal (Ole-Johan Dahl) i Kristen Najgard (Kristen Nygaard), razvili su programski jezik Simula, koji je, još na početku, * imao većinu odlika današnjih objektno orijentisanih programskih jezika.
Ipak, skromni kapaciteti računara iz šezdesetih i sedamdesetih godina, pomalo su kočili pravi napredak OOP pristupa (zapravo, više nego "pomalo") i - u praktičnom smislu - može se reći da je tek osamdesetih godina, ** a posebno od 1985. kada je danski programer Bjarne Stravstrup (Bjarne Stroustrup) objavio prvu zvaničnu verziju jezika C++, objektno orijentisani pristup počeo da doživljava pravu ekspanziju.
Sredinom devedesetih godina, uporedo sa vrhuncem ekspanzije C++-a, pojavio se programski jezik Java, čije su glavne odlike bile: izvršavanje koda preko virtuelne mašine i (važnije za ovaj članak) - izrazita orijentisanost ka OOP pristupu.
Druga navedena odlika imala je veći (posredan) uticaj i na mnoge programske jezike koji OOP metodologiji do tada nisu pridavali previše značaja, a u opštem smislu (kao što smo na neki način već nagovestili), Java se može smatrati "najočiglednijim" primerom jezika koji je nedvosmisleno usmeren na objektno orijentisani pristup, i stoga ćemo izvesnim osobenostima programskog jezika Java posvetiti još koji red.
Ako C++ (na primer), dozvoljava da se proceduralni pristup koristi samostalno ili u kombinaciji sa OOP pristupom (a ima i drugih jezika koji nude slične mogućnosti), sa Javom to nije slučaj: u Javi sve mora biti definisano preko klasa (pa makar program u praktičnom smislu bio najobičniji proceduralni program od nekoliko instrukcija), svaka klasa mora biti zapisana u zasebnoj datoteci, a svojstvenost ovog jezika su takođe i dugački, deskriptivni nazivi klasa i pripadajućih elemenata.
I zaista, ako pogledamo prost "zdravo svete" program napisan u Javi ....
.... sve deluje pomalo kao "karikatura" na prvi pogled, pogotovo ako se prethodni kod uporedi sa "očiglednim" primerom iz jezika koji nije "izrazito orijentisan na OOP pristup":
(Preskočili smo C/C++ i "zarad efekta" smo koristili Python, koji omogućava da se sve izvede u jednoj jedinoj liniji koda.) *
Međutim, pojasnili smo već da je prava svrha OOP pristupa - dobra organizacija većih projekata (a ne rešavanje 'skroz jednostavnih' zadataka). :)
Java je vrlo očigledan primer jezika koji se "drži OOP-a", neki OOP jezici (kao na primer C++), nisu toliko "isključivi", a postoji i veći broj drugih jezika koji takođe nisu (na očigledan način) 'unapred usmereni' na neki od pristupa (proceduralni ili OOP), ali, svakako podržavaju upotrebu klasa i objekata.
U svakom slučaju (kao što je već pomenuto), tokom vremena, a pogotovo - uporedo sa ekspanzijom Jave i pod uticajem Jave - OOP pristup (bilo kao "glavna atrakcija", ili samo kao "jedna od mogućnosti"), našao je mesto u velikoj većini iole popularnih i korišćenih programskih jezika. **
Kada je u pitanju glavni jezik koji će u članku biti korišćen za primere - izabrali smo C#, s tim da pružamo čitaocima mogućnost da lako promene jezik po želji (opcije su: C#, C++, Java i Python).
C# je jezik koji je usmeren na klase - poput Jave, ali, rekli bismo ne baš u istoj meri, a osim toga: C# je ponešto fleksibilniji, opšti je utisak da sintaksa C#-a deluje preglednije od sintakse drugih popularnih OOP jezika - i upravo zato smatramo da je C# najprikladniji jezik za početno upoznavanje sa OOP pristupom. *
Python je svakako pomalo "ležerniji" po pitanju implementacije OOP tehnika, ** ali, ipak će biti prikazani i primeri iz Python-a ("da pomenemo još koji jezik"), dok je JavaScript znatno ležerniji, i stoga ćemo primere iz JS-a preskočiti, jer OOP pristup svojstven JS-u nikako nije primeren za početno upoznavanje sa tematikom.
Osnovni pojmovi u objektno orijentisanom programiranju
Pošto smo se ukratko upoznali sa najopštijim osobinama OOP-a i sa time kako je i zašto OOP pristup našao mesto u računarskoj industriji, vreme je da se vratimo na 'tehnikalije' i da se upoznamo sa najvažnijim konceptima objektno orijentisanog programiranja.
Teoriju objektno orijentisanog programiranja dodatno ćemo ilustrovati kroz jednostavan primer (sasvim prikladan za sam početak), koji ćemo razvijati (tj. dopunjavati) kroz poglavlja članka, što će biti dobra prilika da proučimo kako tehnike o kojima pišemo funkcionišu u praksi.
Objektno orijentisano programiranje temelji se na sledećim osnovnim pojmovima:
- klasa
- objekat
- apstrakcija podataka
- enkapsulacija
- nasleđivanje
- polimorfizam
Svakom od navedenih pojmova, biće posvećen zaseban odeljak u nastavku.
Klase, objekti i apstrakcija podataka
U idejnom smislu, klasa u objektno orijentisanom programiranju predstavlja obrazac za kreiranje (budućih) objekata.
U praktičnom smislu, u pitanju je programski kod koji je tipično, ali ne i obavezno (videti napomenu ispod), zapisan u zasebnoj datoteci, i sadrži sve elemente koji opisuju moguća stanja i moguće oblike ponašanja objekata koji će biti kreirani upotrebom klase.
Elementi klasa, preko kojih se ostvaruje navedeno, su:
- polja (zapisani podaci koji definišu stanje objekta)
- metode (funkcije koje definišu radnje koje će budući objekti moći da obavljaju nad sopstvenim podacima, ili sa drugim podacima u programu)
Pojam apstrakcije (kao jedan od osnovnih principa u dizajnu klasa), podrazumeva: odabir (samo) onih svojstava koja su bitna za definisanje objekata i izvršavanje programa, i odbacivanje svojstava koja u datom kontekstu nisu bitna.
Recimo, pojam osobe - koji je inače definisan mnogim svojstvima kao što su: telesna građa, duhovne odlike i sklonosti, odnos sa drugim osobama, svetom oko sebe (i mnogo čime još), u dizajnu klase - u smislu odabira podataka (to jest, 'apstrakcije') - bio bi sveden na samo one odlike koje su bitne za izvršavanje programa.
Na primer, u programu za poslovnu administraciju, možemo zanemariti većinu prethodno navedenih svojstava i ostaviti samo: datum rođenja, adresu stanovanja i podatke o učinku na radnom mestu.
Ukratko, pri dizajniranju klase, potrebno je uskladiti prethodno navedene smernice (kao i druge detalje o kojima će tek biti reči), to jest, potrebno je izabrati i opisati:
- koji podaci će biti predstavljeni
- kako će podaci biti predstavljeni
- kako će podaci biti međusobno povezani
.... i (naravno), potrebno je definisati mehanizme za obradu podataka.
Odnos između klase i objekta
Da bismo ilustrovali sve što je do sada navedeno (i da bismo prikazali odnos između klase i objekta, odnosno - objekata (u množini)), za primer možemo uzeti klasu preko koje se definišu pravilni mnogouglovi zarad iscrtavanja na ekranu (znatno jednostavniji primer od onih koje smo do sada pominjali, ali, sasvim ilustrativan):
U smislu apstrakcije podataka, izabrali smo samo ono što je neophodno: opštim matematičkim svojstvima pravilnih mnogouglova, dodata su svojstva koja se koriste za prikaz na ekranu.
Odnos između klase i objekata je sledeći:
- klasa je skup opštih karakteristika, tj. mogućih stanja ('budućih objekata')
- objekat je konkretna realizacija klase, pri čemu je - među mogućim vrednostima različitih svojstava - izabrana određena kombinacija konkretnih vrednosti.
Klasa čija je šema prikazana na slici #3 svakako jeste veoma jednostavna, ali, sadrži specijalizovane podatke koji su izabrani prema konkretnim potrebama, a što se tiče odnosa između klase i objekata u konkretnom slučaju: među opštim/mogućim karakteristikama (3 ili više stranica; poluprečnik predstavljen kao decimalna vrednost veća od 0; boja zadata kao skup RGB vrednosti), objekti (tj. mnogouglovi), ispoljavaju određenu kombinaciju konkretnih svojstava.
Polje (field) - podaci
Pojam polja (klase) u objektno orijentisanom programiranju, označava pojedinačni imenovani podatak koji je zapisan (direktno) unutar klase, i takav podatak može biti:
- primitivni podatak koji pripada nekom od osnovnih tipova (kao što su int, float ili char)
- kolekcija podataka bilo kog tipa (niz, lista, red, stek ...)
- objekat klase (iste ili različite)
- pokazivač ili referenca - na podatak, objekat ili kolekciju bilo kog od prethodno navedenih tipova
Svi navedeni podaci, zajedno (kao što smo već nagovestili ranije), opisuju stanje (budućih) objekata.
U opštem smislu, iako su podaci sami po sebi uvek tačni, upotreba podataka u određenom kontekstu, može biti nekorektna i (takođe), odnosi između podataka - ne moraju obavezno biti ispravni.
Primer #1: brojevi -4.5 i 0 su sami po sebi 'tačni' (tj. 'ispravni'), ali, takva dva broja ne mogu biti stranice pravougaonika (prvi broj je negativan, a drugi je nula).
Primer #2: brojevi 4.5 i 5.5 su (ponovo) sami po sebi tačni i takva dva broja (ovoga puta) mogu biti stranice pravougaonika, ali - ako zabeležimo da je obim pravougaonika 1400, između vrednosti stranica i obima postoji nesklad.
Da bi upotreba podataka u određenom kontekstu bila ispravna, i da bi veze između podataka klase bile korektne, potrebno je pravilno sprovesti postupak enkapsulacije.
U pitanju je postupak preko koga se podaci:
- čuvaju od neovlašćenog pristupa ("diskutabilnih" upisa i sl)
- po potrebi dovode u odgovarajuće međusobne odnose
Ovoj veoma bitnoj temi, posvetićemo mnogo više pažnje u nastavku (pošto se osvrnemo na preostale osnovne odlike klasa).
Metode - obrada podataka
U objektno orijentisanom programiranju, termin "metoda" označava: u najpraktičnijem smislu - funkciju koja je zapisana unutar tela klase, a u idejnom smislu - proceduru za obavljanje operacija nad poljima klase (i, po potrebi, sa spoljašnim podacima).
Kao što smo nagovestili, po svojim 'osnovnim tehničkim karakteristikama', metode zapisane unutar klase - gotovo u potpunosti se poklapaju sa funkcijama sa kakvima ste se mogli susresti u C-u ili nekom sličnom jeziku (pri pozivu funkcije (tj. metode) predaju se argumenti; funkcije (tj. metode) imaju (ili nemaju) povratnu vrednost, i sl). *
Osim navedenih odlika, bitno je napomenuti da metode klase mogu direktno pristupati poljima klase (u primeru na slici #5, metoda PromenaPoluprecinika
pristupa direktno polju r
). **
Da bismo mogli za prave razumeti kako se odvija interakcija između polja i metoda (i pre svega, da bismo što bolje mogli da sagledamo kako klasa dobija željene odlike, a samim tim - i budući objekti), počećemo polako da definišemo jednostavnu klasu i proučavamo šta se sve događa u različitim fazama razvoja klase ....
Struktura jednostavnog primera koji će biti korišćen u članku
Na početku smo se potrudili da potcrtamo da je OOP pristup primereno koristiti (samo) onda kada su u pitanju kompleksni objekti koji takav pristup zavređuju, međutim, primer koji ćemo koristiti za početno 'uhodavanje', biće (kako i dolikuje), ipak mnogo jednostavniji: definisaćemo klasu koja opisuje pravougaonik(e).
Primer je naizgled jednostavan (i ne samo naizgled), ali, sasvim je adekvatan i u praktičnom smislu.
Klasa Pravougaonik
sadržaće:
- polja
a
, b
, O
i P
(podatke o stranicama pravougaonika, obimu i površini)
- metode preko kojih se obim i površina računaju i ažuriraju pri promeni vrednosti stranica
a
i b
- pomoćnu metodu za ispis podataka o pravougaoniku
Uz sve navedeno, potrebno je udesiti i to da četiri polja uvek budu u međusobnim vezama koje odgovaraju pravilima geometrije (a da li će takav poredak biti uspostavljen "sam od sebe" - videćemo u nastavku (mada verujemo da već znate odgovor)) ....
Definisaćemo prvo najosnovnije okvire klase (tj. navešćemo samo polja sa podacima), i stoga klasa na početku ima sledeći oblik:
Preko četiri polja klase Pravougaonik
(četiri promenljive tipa Double
), beleže se stranice pravougaonika (a
i b
), kao i obim i površina (polja O
i P
).
Iako klasa koju smo videli ni iz daleka nije dovršena, niti zapravo spremna za eksploataciju (tek smo počeli), u najosnovnijem (tehničkom) smislu, klasa se već može koristiti za zapis podataka; naravno - uz napomenu da će u svemu biti nepredviđenih okolnosti koje i te kako zahtevaju pažnju. :)
Ali, upravo time se i bavimo: učimo kako se definišu klase i objekti i na šta se sve mora obratiti pažnja, i stoga ćemo pokazati:
- kako se objekti kreiraju (tj. 'instanciraju')
- zašto je neophodno da veze među poljima budu pravilno uspostavljene (kroz postupak 'enkapsulacije')
Kreiranje objekta (rezervisana reč new)
Pozivanjem sledećeg (vrlo jednostavnog) programskog koda ....
.... kreira se objekat P1
, posle čega se može pristupati poljima objekta:
Polja a
i b
sada sadrže vrednosti koje same po sebi "imaju smisla", ali, između polja klase ne postoje veze koje odgovaraju pravilima geometrije i verujemo da se mnogi od vas pitaju zašto je tako?
Iz iskustva znamo da mnogi polaznici (pri prvom susretu sa objektno orijentisanim programiranjem), imaju zamisao da OOP pristup "sam od sebe" ("nekako"), uspostavlja sve potrebne veze između podataka i rešava usputne probleme automatski, međutim (kao što smo već nagovestili) - to nije slučaj.
Klasa nije automatizovan šablon koji "magijskim putem" / "tek tako" / samim tim što smo klasi dali ime koje nama nešto znači (a računaru - baš ništa), definiše sve moguće objekte koji nam mogu pasti na pamet (geometrijska tela, vozila, uređaje i sl), pri čemu važe odnosi iz spoljnjeg sveta.
Klasa je samo mehanizam koji omogućava da sve podatke i metode budućih objekata stavimo "pod istu kapu" (na jedno mesto), zarad preglednosti i bolje čitljivosti, međutim, za uspostavljanje odnosa među podacima - moramo se pobrinuti sami.
Naravno, ništa od navedenog nije "strašno" (ni iz daleka), ali, svakako moramo biti pažljivi i svesni situacije.
Šta se tačno dešava sa podacima koje nismo inicijalizovali sami ('ručno')?
U većini OOP jezika, * ukoliko se sami ne pobrinemo za inicijalizaciju, polja čiji je tip Double
(isto važi i za Int32
i druge brojčane tipove), biće inicijalizovana vrednošću 0
(nula), niske se inicijalizuju kao prazne niske (""
), a reference i pokazivači imaju vrednost null
.
U slučaju objekta P1
, koji je kreiran preko klase Pravougaonik
, ukoliko se inicijalizuju stranice pravougaonika, ali - pri tom se ne inicijalizuju obim i površina - polja O
i P
će imati vrednost 0
(što svakako nije u skladu sa pravilima geometrije i našim prvobitnim očekivanjima).
Budući da su (u primeru koji koristimo), stranice inicijalizovane naknadno, a obim i površina nisu inicijalizovani na odgovarajući način (polja jesu inicijalizovana, ali, po podrazumevanoj metodi koja im dodeljuje vrednost 0), objekat P1
ima sledeće stanje:
Ovakav objekat nema praktično značenje (suštinski, nije "ništa bolji" nego četiri međusobno nezavisne promenljive), i stoga ćemo preduzeti dalje korake da "popravimo situaciju", uvođenjem metoda koje će regulisati stanje različitih polja.
Iako naredni postupak koji ćemo sprovesti neće rešiti "sve probleme", prvo ćemo se pobrinuti da makar početno stanje objekta bude korektno.
U kontekstu proceduralnog programiranja, inicijalizacija promenljivih je jedna od najbitnijih operacija i (kao što znamo), u pitanju je postupak preko koga se promenljivama zadaje početna (tj. "inicijalna") vrednost.
U primeru koji proučavamo, polja a
i b
dobila su vrednosti tek naknadno, a poljima O
i P
nedostaje početno stanje koje odgovara upisanim stranicama, i stoga će inicijalizacija polja biti prvo na šta ćemo obratiti pažnju u nastavku.
Jasno je da računanje (tj. ažuriranje) obima i površine, podrazumeva korišćenje odgovarajućih metoda, i jasno je da se objekat ne može inicijalizovati prostom naredbom dodele (kao obična promenljiva), već - pozivanjem posebne metode.
Konstruktor - metoda za definisanje početnog stanja objekta
Konstruktor (klase) je specijalizovana metoda preko koje se zadaje početno stanje objekta.
Polja se inicijalizuju prostim naredbama dodele (ukoliko je moguće), ili pozivanjem pomoćnih metoda za ažuriranje vrednosti polja (ukoliko vrednosti određenih polja zavise od drugih polja, kao što je u našem primeru slučaj sa obimom i površinom pravougaonika, koji zavise od vrednosti stranica).
U pisanju (u većini C-olikih jezika), konstruktor se prepoznaje po tome što nema povratni tip (za razliku od ostalih metoda), i po tome što ima isti naziv kao i sama klasa.
Dodaćemo konstruktor klasi Pravougaonik
....
.... posle čega se početno stanje objekta može zadati na sledeći način:
Preko rezervisane reči new
(kao što smo već videli), poziva se konstruktor klase, pri čemu se ovoga puta zadaju konkretni argumenti (koji će biti prosleđeni odgovarajućim poljima). *
Međutim, sa objektom je sve (skoro) isto kao malopre: polja a
i b
odmah dobijaju konkretne vrednosti (o rezervisanoj reči this
, koju primećujete u kodu, više ćemo govoriti u sledećem odeljku), ali, polja O
i P
i dalje imaju vrednost 0
(tako smo udesili, naravno, isključivo iz razloga da još jednom potcrtamo da se stvari u objektno orijentisanom programiranju ne dešavaju "same od sebe").
Uvođenjem dve nove metode (za računanje obima i za računanje površine), koje će potom biti pozvane iz konstruktora - problem će biti rešen. *
Sada će (konačno), pri sledećem pozivu ....
.... objekat biti korektno inicijalizovan.
Ako pozovemo sledeći kod ....
.... dobićemo sledeći ispis:
Konačno smo došli do toga da je (bar) početno stanje objekta pravilno zadato, a ako nam prethodni poziv "nije po volji", možemo kreirati i zasebnu metodu za ispis vrednosti polja ....
.... i potom možemo pozvati novu metodu na sledeći način (što je svakako (bar ponešto) elegantnije od navođenja zasebnih instrukcija za ispis):
Programski kod sada jeste znatno pregledniji, ali, definicija klase još uvek nije potpuna ....
Iako dosadašnji zapis omogućava korektnu inicijalizaciju objekta, ne postoji - za sada - mehanizam koji omogućava automatsko ažuriranje obima i površine pri promeni vrednosti stranica, a ne postoje ni mehanizmi koji onemogućavaju unošenje pogrešnih vrednosti stranica (recimo, negativnih brojeva i nule).
Skup OOP tehnika za obavljanje "sigurnosnih provera" preko kojih se rešavaju prethodno navedeni problemi, nosi naziv enkapsulacija, i upravo će enkapsulacija biti glavna tema sledećeg poglavlja, ali, pre toga ćemo napraviti i kratak osvrt na rezervisanu reč this
(koju smo u gornjim primerima koristili u konstruktorima).
Rezervisana reč "this"
Praktična svrha rezervisane reči this
je: rešavanje nedoumica koje mogu nastati u zapisu programskog koda, u situacijama kada parametri konstruktora (kao i drugih metoda), imaju iste nazive kao polja klase.
Da pojasnimo ....
Kada je objekat (već) kreiran, poljima se pristupa uz navođenje identifikatora objekta i polja, pri čemu su identifikatori spojeni preko operatora .
(na primer: P1.a
- kao način pristupa stranici a
objekta P
(koji predstavlja pravougaonik)).
Navedeni način pristupanja poljima i metodama, moguć je ako je objekat već kreiran, ali, u samoj klasi Pravougaonik
, nije moguće koristiti poziv "Pravougaonik.a"!
Rezervisana reč this
koristi se u situacijama kada je, pri definisanju klase, potrebno da se obratimo "budućem objektu" (koji tek treba da nastane instanciranjem klase).
U primeru konstruktora koji smo koristili....
.... upotrebom rezervisane reči this
(u Python-u, umesto rezervisane reči this, koristi se rezervisana reč self), pravi se razlika između polja klase this.a
, i parametra a
iz konstruktora.
Enkapsulacija
Kao što smo ranije nagovestili, termin "enkapsulacija" predstavlja skup tehnika čija je svrha:
- definisanje pravila za pristup poljima i metodama klase (i istovremeno)
- uspostavljanje prikladnih veza između podataka u poljima
Međutim, u još osnovnijem smislu, enkapsulacija u objektno orijentisanom programiranju predstavlja opštu ideju, koja nalaže da pristup podacima treba da bude regulisan tako da "unutrašnji mehanizmi" klase budu skriveni od krajnjih korisnika, ali (istovremeno) - tako da korisnicima klase bude omogućeno da neometano koriste sve neophodne opcije.
Da pojasnimo preko poznatog primera iz spoljnjeg sveta: vozač automobila (u prenesenom značenju - krajnji korisnik klase), na raspolaganju ima volan, menjač i papučice (kvačilo, gas i kočnicu), što znači da je u mogućnosti da upravlja automobilom, ali, nema uticaja na dizajn karburatora, prenosnog mehanizma, vešanja, klipova i drugih delova, već je to posao autoinženjera (u prenesenom značenju - programera koji su zaduženi za dizajn i održavanje klase).
Sa blagodetima enkapsulacije već smo se upoznali u velikoj meri - na posredan način - time što smo posmatrali delove koda koji ne koriste enkapsulaciju (što je zapravo bila prilika da "naslutimo kako bi trebalo da bude").
U nastavku, sledi zvanično upoznavanje sa postupkom enkapsulacije, a počinjemo upravo od primera sa kakvima smo se već sretali ....
Šta se događa ukoliko se ne koristi enkapsulacija?
Ako se setimo naredbe P1.a = 12.55;
preko koje smo (naknadno) zadali vrednost 12.55
polju a
objekta P1
, može nam pasti na pamet da napravimo i sledeće pozive ....
Iako je objekat prethodno bio korektno inicijalizovan, naredbe kakve smo videli (ako ih izvršimo), ponovo će poremetiti objekat: obim postaje mnogo veći nego što bi trebalo, a površina je negativan broj.
Međutim, u prethodnoj situaciji se pre svega moramo zapitati - da li uopšte treba da postoji mogućnost direktnog zadavanja vrednosti obima i površine?!
Ukoliko postoje samo dve moguće kombinacije vrednosti polja a
i b
posle promene obima ili površine, ceo objekat (praktično) postaje vrlo neodređen, a potencijalnih kombinacija svakako ima "više od dve" (prema matematici - beskonačno mnogo, a u praktičnoj implementaciji na računaru, iako broj nije beskonačan, kombinacija ima izrazito mnogo).
U nastavku slede postupci preko kojih se rešavaju dva problema sa kojima smo se prethodno susreli:
- potrebno je postići da obim i površina budu automatski ažurirani pri promeni stranica
a
i b
- potrebno je ukinuti mogućnost ručnog upisivanja vrednosti obima i površine, ali (istovremeno) - ostaviti mogućnost čitanja obima i površine
U tehničkom smislu (što ćemo videti i u slučaju klase Pravougaonik
), enkapsulacija se obavlja kritičkom upotrebom specifikatora pristupa - u kombinaciji sa javnim metodama koje regulišu pristup privatnim poljima.
Upravo smo izneli 'podosta' novih/nepoznatih pojmova, pa ćemo se potruditi da ih u nastavku sve detaljno razjasnimo i (naravno), iako će nam novopomenute tehnikalije u prvom trenutku pomalo "zakomplikovati život", takođe će uneti i red kao što je neophodno (pogotovo u dugoročnom smislu), i stoga ćemo konačno biti u stanju da "doteramo" klasu Pravougaonik
kako dolikuje.
Specifikatori pristupa (private, public, protected)
Specifikatori pristupa su rezervisane reči koje stoje uz deklaracije polja i metoda, * i određuju mogućnost pristupa ** elementima klase:
private
- poljima i metodama moguće je pristupati samo unutar klase i nije moguće pristupati im iz spoljnih delova koda
public
- poljima i metodama moguće je neposredno pristupati iz spoljnih delova koda (naravno, i unutar klase)
protected
- poljima i metodama moguće je pristupati unutar osnovne klase, ali i preko nasleđenih klasa (o nasleđivanju ćemo govoriti u nastavku), međutim, nije moguće elementima pristupati iz spoljnih delova koda
Do sada je uz sva polja klase Pravougaonik
stajala odrednica public
, pri čemu je u pitanju rezervisana reč koja (u skladu sa prethodno navedenim pravilima), sugeriše spoljnjim delovima programskog koda da mogu neposredno pristupati svim poljima klase kojima je pripisan navedeni specifikator pristupa.
Dobra strana takve "otvorenosti" je to što se poljima može pristupati neposredno, a loša strana, to što mogućnost direktne izmene polja koja imaju specifikator pristupa public
, ostavlja prostora za naredbe dodele koje mogu (bar u najgorem slučaju), dovesti u pitanje korektnost celog objekta.
(Kao recimo malopre, kada smo upisali da je površina pravougaonika -19.25, što nije matematički moguće, pa smo takvom naredbom dodele praktično obesmislili ceo objekat.)
Prvi korak u enkapsulaciji, tipično podrazumeva da se preko specifikatora pristupa private
onemogući neposredan pristup određenom polju.
U slučaju klase Pravougaonik
, izvešćemo to na sledeći način:
Međutim, pošto su polja a
, b
, O
i P
proglašena privatnim elementima klase, spoljni delovi koda više im ne mogu pristupati.
U opštem smislu, nemogućnost pristupa može, ali i ne mora, predstavljati problem, pa ako nemogućnost pristupa poljima ne predstavlja problem, ne moraju se preduzimati dalji koraci.
U slučaju klase Pravougaonik
(koja simbolički predstavlja tipičan slučaj projektovanja klase), * nemogućnost pristupa poljima - predstavlja problem (razumno je pretpostaviti da će bar povremeno postojati potreba za ažuriranjem stranica pravougaonika i, pogotovo, za neposrednim čitanjem vrednosti pojedinačnih polja), i stoga - preduzećemo korake da omogućimo pristup poljima a
, b
, O
i P
- na adekvatan način.
Za početak, razmotrićemo (u sledećem odeljku), na koji način se privatnim poljima može pristupati preko javnih metoda.
Pristup privatnim poljima preko javnih metoda
Javna metoda za čitanje vrednosti privatnog polja (u primeru koji koristimo), može se definisati na sledeći način:
Specifikator pristupa public
omogućava pristup metodi iz spoljnih delova koda, a preko povratnog tipa Double
omogućava se direktno čitanje vrednosti polja a
u izvornom obliku (ne samo tekstualni ispis).
Za upis (tj. ažuriranje) vrednosti polja a
, koristićemo sledeću javnu metodu:
Preko postupka koji smo izabrali, efikasno su rešena dva problema (koje smo prethodno pominjali):
- ulazni podatak ne prihvata se bezuslovno - već se proverava (na primer, ukoliko je unet negativan broj ili nula, ne ažurira se vrednost polja
a
, niti se pozivaju metode za ažuriranje obima i površine)
- ažuriranje obima i površine pri zadavanju nove vrednosti stranice - obavlja se (praktično) automatski
Klasa je sada konačno spremna za upotrebu, i možemo se osvrnuti na šematske prikaze klase koju smo definisali i objekta koji nastaje instanciranjem klase.
Klasa:
Objekat:
Deluje da klasa sadrži 'više', međutim, tako je samo naizgled (objekat takođe sadrži sve metode koje sadrži i klasa).
Preko konkretnog primera (na gornjim slikama), prikazan je opšti princip o kome smo ranije pisali: objekat "ispod haube" sadrži 'sve što treba', ali, direktno se može pristupati samo određenim metodama.
Za kraj odeljka o enkapsulaciji, razmotrićemo i primer enkapsulacije u programskom jeziku C# preko tzv. svojstava, što je pristup kojim se postiže još veća preglednost (ali, nažalost, u pitanju je i pristup koji nije dostupan u ostalim jezicima koje koristimo za primere).
Enkapsulacija preko svojstava u programskom jeziku C# (pristupnici get i set)
Videli smo da je pristup privatnim poljima moguć preko javnih metoda (tj. metoda koje imaju specifikator pristupa public
), a videli smo i da je glavni razlog zašto se polja klase uopšte "skrivaju", to što postoje situacije u kojima su vrednosti određenih polja uslovljene vrednostima drugih polja.
U opštem smislu:
- u određenim situacijama je potrebno očuvati veze između podataka (npr. obim i površina pravougaonika zavise od vrednosti stranica)
- u drugim situacijama, potrebno je 'zaštititi' sadržaj samih polja (npr. vrednost stranice pravougaonika ne sme biti negativan broj ili nula; vrednosti polja O i P ne smeju se upisivati 'ručno' i sl)
Pristup koji smo opisali u prethodnom odeljku može se koristiti u većini programskih jezika koji podržavaju OOP (praktično: u svim OOP jezicima koji su nama poznati), ali, neki jezici nude i dodatne mogućnosti.
U jeziku C#, moguće je definisati tzv. "svojstva" (eng. properties).
Gledano sa 'spoljne' strane, svojstva u C#-u podsećaju na polja, ali, u pitanju su specijalni elementi klasa koji takođe poštuju i enkapsulaciju (postoje jasna pravila u smislu mogućnosti čitanja i upisa - što ćemo u nastavku objasniti podrobnije).
Princip definisanja svojstava u C#, u suštinskom smislu je veoma sličan principu upotrebe javnih metoda zarad pristupa privatnim poljima, ali - zapis je ponešto elegantniji.
Za upoznavanje sa svojstvima koristićemo dosadašnji primer (pri čemu ćemo napraviti i izvesne (manje) izmene, i prikazaćemo odmah kako upotreba svojstava obično izgleda u klasama):
Da pojasnimo šta znače dopune koje smo videli u primeru.
Sledeći deo klase ....
.... definiše svojstvo a
koje će biti dostupno spoljnim delovima koda (specifikator pristupa svojstva a
je public
), a unutar zagrada su definisana dva bloka.
Preko prvog bloka, get
....
.... određeno je da će programski kod koji zatraži vrednost svojstva a
, dobiti (zapravo) vrednost polja _a
.
Na primer, preko sledećeg koda ....
.... dobija se ispis stranice a
(pozivanjem svojstva P1.a
dobija se vrednost polja P1._a
, a zatim se poziva opšta funkcija ToString()
preko koje se ispisuje vrednost).
Preko drugog bloka, set
....
.... određeno je da će polje _a
dobiti predatu vrednost (value
), ali - samo u slučaju da je predata vrednost veća od nule.
Pre svega, primećuje se sličnost sa pristupom iz prethodnog odeljka (kada je korišćena metoda Upis_a
), a verujemo da pažnju čitalaca najviše privlači rezervisana reč value
(koja može delovati pomalo "zbunjujuće").
Posmatrajmo to ovako (postavimo sebi pitanje): pod kojim okolnostima se 'sadržaj' određenog polja može promeniti?
Odgovor je - preko naredbe dodele, a naredba dodele sadrži: sa jedne strane, identifikator promenljive (može naravno biti i polje klase), a sa druge strane .... ništa drugo nego izračunatu vrednost.
Upravo to je razlog zašto se koristi rezervisana reč value
(makar u programskom jeziku C#), u smislu: kakva god vrednost da se nađe sa desne strane naredbe dodele, biće prosleđena svojstvu a
.
Da rezimiramo ukratko: u praktičnom smilu, svojstvo predstavlja svojevrsnu kombinaciju polja i metode; spolja gledano (tj. u situacijama kada mu se pristupa iz spoljnih delova koda), svojstvo izgleda kao polje, ali, "iznutra" funkcioniše kao metoda.
U primeru koji razmatramo (koji služi kao tipičan primer upotrebe svojstava):
- dodela nije bezuslovna
- posle dodele pozivaju se metode za ažuriranje
.... i (takođe), u opštem smislu:
- jedan od dva bloka može se i izostaviti
Na primer:
Svojstva O
i P
su "read only", odnosno, moguće je samo čitati vrednosti obima i površine (znamo da se stranice a
i b
ne mogu nikako odrediti na nedvosmislen način, ako se unese vrednost za obim ili površinu).
Takođe, primetili smo i to da sada - kada se spoljnim delovima koda "otkrivaju" svojstva koja nose prethodne nazive polja (a
, b
, O
i P
) - sama polja nose nazive sa prefiksom: _a
, _b
, _O
i _P
.
U pitanju je konvencija (i uobičajena praksa) u programiranju, koju svakako treba uvažiti.
Pred kraj, pozabavićemo se temom nasleđivanja u objektno orijentisanom programiranju, kao i preopterećivanjem metoda i preklapanjem operatora.
Nasleđivanje i polimorfizam
Naveli smo ranije da je jedan od najvažnijih zadataka objektno orijentisanog programiranja (možda i glavni cilj) - dobra organizacija podataka.
Da bismo razumeli svrhu nasleđivanja u OOP-u (u prethodno navedenom kontekstu), zamislićemo sledeću situaciju:
- potrebno je definisati klasu
Osoba
, * sa nekoliko opštih polja kao što su Ime
, Prezime
, DatumRodjenja
i Starost
- potrebno je definisati klasu
Radnik
koja sadrži sva polja prethodne klase, ali - sadrži i polja koja su specifično vezana za radnike (npr. RadnoMesto
, PocetakRada
i RadniStaz
)
Možemo definisati dve potpuno nezavisne klase, međutim, možemo primeniti i racionalniji pristup koji omogućava određene pogodnosti.
Nasleđivanje
U najprostijem smislu, nasleđivanje (kao tehnika OOP-a), podrazumeva definisanje izvedenih klasa koje direktno preuzimaju strukturu postojećih klasa (polja i metode), pri čemu (naravno/očekivano), postoji mogućnost uvođenja novih polja i metoda.
Navedeni pristup koristi se u situacijama kada određene klase (dve ili više), dele zajedničke osobine, iz čega praktično proizilazi da bi višestruko navođenje istih polja i metoda (u dve ili više različtih klasa) - dovelo do nepotrebnog 'gomilanja' programskog koda.
Sve što smo naveli, bolje ćemo razumeti preko praktičnog primera ....
Prvo je potrebno definisati opštiju od dve klase (tj. klasu koja sadrži zajednička polja i/ili metode), što je u našem slučaju klasa Osoba
:
Vidimo da klasa nema previše elemenata, ali, čak i na ovakvom jednostavnom primeru, primećuje se da definisanje klase Radnik
- koje bi podrazumevalo bukvalno "prepisivanje" prethodnog koda - ne deluje kao najpraktičnije rešenje.
Umesto "prepisivanja", klasa Radnik
naslediće klasu Osoba
, čime je praktično omogućeno korišćenje polja i metoda klase Osoba
- u okviru klase Radnik
(a u C#-u, takođe i korišćenje svojstava),
Naravno (kao što smo već naveli), postoji mogućnost da se klasi Radnik
pridodaju i nova/zasebna polja i metode (i svojstva u C#-u).
Kraća analiza
Preko sledećeg programskog koda, definisano je nasleđivanje klase:
U konkretnom primeru, klasa Radnik
nasleđuje klasu Osoba
, što praktično znači da klasa Radnik
ima sva polja, metode (i svojstva) koja odlikuju klasa Osoba
- pri čemu je moguće dodavati nova polja (koja su svojstvena samo klasi Radnik
) i nove metode (koje su takođe svojstvene samo klasi Radnik
), kao i svojstva u C#-u.
Da pojasnimo dodatno: u konstruktoru klase Radnik
....
.... preko sledećeg koda ....
.... praktično se poziva konstruktor klase Osoba
, što podrazumeva inicijalizaciju tri navedena polja i pozivanje metode za računanje starosti, ali, budući da konstruktor klase Radnik
sadrži i druge parametre i poziv metode za računanje radnog staža, vidimo da nije u pitanju klasa Osoba
, već - druga, zasebna klasa (i vidimo da je povezivanje podataka koje je ostvareno na prikazani način, optimalno i praktično).
Polimorfizam
Termin polimorfizam (u prevodu: "višeobličje" ili "višeobličnost"), u objektno orijentisanom programiranju se tipično * odnosi na sledeće pojave:
- u okviru osnovne klase i izvedenih klasa (jedne ili više), moguće je implementirati metode istog naziva, na različite načine (u pitanju je tzv. "dinamički polimorfizam")
- u okviru jedne klase, moguće je (uz poštovanje određenih pravila), definisati više metoda istog naziva ** (u pitanju je tzv. "statički polimorfizam")
- postoji mogućnost ravnopravnog (odnosno "istovremenog") korišćenja osnove klase i izvedenih klasa - u određenim okolnostima, *** tj. pod određenim uslovima, što, u najpraktičnijem smislu, znači da se objekti osnovne klase i izvedenih klasa mogu zajednički smeštati u različtite kolekcije podataka, može im se pristupati preko istih metoda i sl. (u pitanju je tzv. "hijerarhijski polimorfizam")
Razmotrićemo primere u kojima se pojavljuju klase koje smo već koristili.
Dinamički polimorfizam
Ukoliko je u okviru osnovne klase definisana određena metoda, objekti koji nastaju instanciranjem nasleđene klase, mogu pozivati datu metodu, međutim, moguće je, u okviru nasleđene klase, implementirati (novu) metodu sa istim nazivom - tako da implementacija bolje odgovara potrebama nasleđene klase.
U prvom primeru, ukoliko je u okviru klase Osoba
definisana metoda IspisPodataka
, na sledeći način ....
.... u okviru klase Radnik
moguće je definisati metodu IspisPodataka
tako da se u ispisu pojave i podaci koji su svojstveni samo osobama čiji su podaci zapisani preko nasleđene klase:
Hijerarhijski polimorfizam
Kao što smo ranije nagovestili, termini "hijerarhijski polimorfizam" se odnosi na okolnosti u kojima se objekti osnovne klase i izvedenih klasa koriste "istovremeno" - ukoliko je takav pristup moguć.
Da bismo se bolje upoznali sa time šta pojam hijerarhijski polimorfizam predstavlja u praktičnom smislu, poslužićemo se primerom u kome ćemo smatrati da klase Osoba
i Radnik
koristimo zarad organizacije podataka o radnicima sopstvene firme i članova njihove bliže familije:
- podaci o radnicima beleže se preko klase
Radnik
- podaci o ostalim osobama beleže se preko klase
Osoba
Sledeći zahtev je: kreiranje i popunjavanje jedinstvene liste svih osoba (bez obzira na to da li je osoba radnik ili nije), * pri čemu bi se takva lista mogla koristiti (na primer), da se u određenim okolnostima pronađu:
- deca mlađa od 7 godina (uoči Novogodišnjih praznika)
- sve osobe starije od 55 godina (zarad organizacije karaoke večeri za sve seniore, bez obzira na to da li su radnici ili nisu)
- sve žene (uoči 8. marta, radi dodele poklona, bez obzira na to da li su radnici ili nisu)
Ako napravimo listu čiji su elementi, objekti osnovne klase (tj. objekti klase Osoba
) ....
.... takva lista moći će da prima objekte klase Osoba
- ali i objekte klase Radnik
.
Na primer, sledeći kod ....
.... izvršavaće se bez problema.
Završićemo uz napomenu da "obrnuti pristup" jeste moguć (kreiranje liste objekata klase Radnik
u koju bi se dodavali i objekti klase Osoba
), ali - u tom slučaju potrebno je postupati vrlo pažljivo (ako već uopšte moramo da koristimo navedeni pristup (koji je nezanemarljivo rizičan)).
Na primer, sledeći kod ....
.... daje referencu na objekat sa kojim se može postupati na bilo koji ("realno mogući") način, u tom smislu da objekat koji referenciramo, garantovano ima sva polja, metode i svojstva (naravno, u kontekstu toga šta objekat ikako može "imati"/"biti" u programu koji kreiramo).
U "obrnutim okolnostima" (ako smo kreirali listu objekata klase Radnik
) ....
.... može doći do problema ukoliko se preko reference R
referencira objekat klase Osoba
- i pri tom se poziva neko od polja klase Radnik
(na primer RadniStaz
).
Ukoliko se ograničimo na polja/metode/svojstva koja su zajednička za obe klase (ili, za sve izvedene klase, u komplikovanijim slučajevima nasleđivanja), stvari će funkcionisati bez "ispada".
Preopterećivanje metoda
U odeljku koji je posvećen konstruktorima, nagovestili smo da postoji mogućnost da se u okviru klase pojavi više metoda istog naziva (bilo da je u pitanju konstruktor, ili neka od metoda opšteg tipa), a u prethodnom odeljku smo se osvrnuli na to da se pomenuti pristup zvanično prepoznaje kao "statički polimorfizam". Međutim, kao što smo takođe nagovestili, u pitanju je zanimljiva pojava u objektno orijentisanom programiranju koja nema obavezno veze sa "osnovnim i nasleđenim klasama", već se često javlja i u klasama koje nisu deo "hijerarhije", i takva pojava nosi opšti naziv "preopterećivanje metoda" *.
U nastavku, objasnićemo kako je, i pod kojim uslovima, moguće implementirati dve ili više metoda istog naziva, u okviru jedne klase.
Uzmimo za primer klasu koja definiše automobile (prikazaćemo vrlo uprošćen primer takve klase, u kome je akcenat na preopterećivanju metoda), i uzmimo da su unutar klase definisana dva konstruktora:
- konstruktor koji zahteva unos marke i modela (automobila)
- konstruktor koji omogućava da se unese i komentar
Uz preopterećivanje konstruktora, inicijalizacija se može obaviti na različite načine:
U opštem smislu, preopterećivanje metoda moguće je izvesti onda kada je zadovoljen jedan od sledeća dva uslova:
- metode istog naziva imaju različit broj parametara
- metode istog naziva imaju isti broj parametara, ali - parametri su različitog tipa
Navedene principe ilustrovaćemo na primeru metode suma
(koja pripada klasi Kalkulator
):
Preklapanje operatora
Preklapanje operatora * predstavlja način da se standardni unarni i binarni operatori (kao što su na primer operatori sabiranja, oduzimanja i sl), koriste za operacije nad objektima (određene klase), nalik tome kako se isti operatori koriste pri obavljanju operacija sa osnovnim tipovima podataka.
U najosnovnijem tehničkom smislu, iza svega stoji sledeća ideja: za određeni operator (koji se "preklapa"), potrebno je napisati metodu koja definiše način funkcionisanja operatora, ili (što će biti slučaj u primeru koji ćemo prikazati), potrebno je operator povezati sa nekom od postojećih metoda unutar klase.
Zamislimo (na primer), da klasa VelikiBroj
omogućava smeštaj celobrojnih vrednosti koje su veće od smeštajnog kapaciteta 64-bitnih int
promenljivih (tako da se broj zapisuje kao niz cifara), ** i zamislimo da je unutar klase definisana metoda za sabiranje brojeva ....
Pozivanje metode sabiranje
(uz prethodno instanciranje dva objekta klase VelikiBroj
) ....
.... ni u kom slučaju ne deluje neintuitivno i "problematično", ali, mogućnost da se veliki brojevi (koji su definisani preko klase VelikiBroj
), sabiraju onako kako se inače sabiraju obične celobrojne vrednosti ....
.... svakako deluje još zanimljivije i prirodnije.
Operator sabiranja (u okviru klase VelikiBroj
), može se "preklopiti", time što će biti povezan sa metodom sabiranje
:
.... posle čega se i veliki brojevi mogu sabirati na prirodan i intuitivan način:
Preklapanje operatora predstavlja svojevrstan uvod u naprednije tehnike objektno orijentisanog programiranja (kao što su apstraktne i statičke klase, delegati, interfejsi i sl), ali, ovoga puta se navedenim temama nećemo baviti, već ćemo to ostaviti za drugu priliku (uobličićemo članak "OOP 2", u kome ćemo se osvrnuti na sve ono na šta smo hteli da se osvrnemo u članku "OOP 1", ali, negde smo ipak morali "podvući crtu", budući da je uvodni članak već dovoljno obiman sam po sebi).
Primer za kraj ....
Za kraj, iako deluje pomalo nepotrebno posle svega što smo naveli, vratićemo se još jednom na pravougaonike i pogledaćemo primer jednostavne procedure preko koje je potrebno ustanoviti koji od dva pravougaonika ima veću površinu: u prvom slučaju, preko klase Pravougaonik
i objekata, a u drugom slučaju, uz korišćenje proceduralnog pristupa (što podrazumeva da se pravougaonici definišu preko zasebnih promenljivih):
Prvo, OOP pristup:
Ako je potrebno pronaći veći među prva dva pravougaonika, ceo kod može se zapisati na sledeći način:
Za poređenje, isti program, bez klasa i objekata:
Razlika nije drastična na ovako malom i jednostavnom primeru (a veći i kompleksniji primer ipak nismo želeli da pišemo zarad očuvanja preglednosti), ali, nije teško uvideti koliko je programski kod u OOP primeru pregledniji.
Međutim, setimo se na ovom mestu i primedbe iz uvodnih pasusa, o tome da OOP pristup ne treba koristiti 'uvek i bez potrebe' (to jest, setimo se toga da je proceduralno programiranje, u određenim okolnostima, takođe krajnje dobar i primeren način za rešavanje problema - neretko i bolji).
Jednostavno, kao što u spoljnoj stvarnosti možemo od Beograda do Novog Sada putovati biciklom ili automobilom, dok bismo do Australije ipak radije putovali avionom, tako i u programiranju nije uputno koristiti OOP pristup "za svaku sitnicu", već samo onda kada zatreba, a - ako zatreba - sada znamo kako se ideje mogu sprovesti u delo. :)