Strukture Klase
Kako se može vidjeti iz prethodno prkazanih listina, strukturu klase čini nekoliko dijelova:
– kontruktori i destruktori
– tipovi,
– metode.
Svaka klasa posjeduje specijalne metode koje se pozivaju prilikom instanciranja i prije samog uništavanja objekta. U principu najmanje jedan konstruktor i desktruktor može postojati u klasi, odnosno moguće je definisati više od jednog konstruktora, dok jedan destruktor može postojati u klasi. Osnovna osobina konstruktora i destruktora jeste naziv koji mora biti isti kao naziv pripadajuće klase. Kod destruktora ispred naziva se nalazi specijalan simbol tilda. Konstruktor kao metoda nema mogućnost vraćanja vrijednosti, dok može prihvatati argumente kao i svaka druga metoda. Konstruktor koji ne prihvata niti jedan argument zovemo defoltni konstruktor. Konstruktore razlikujemo ne po nazivu (jer svi imaju isti naziv) već po njihovim argumentima. Npr. ukoliko imamo klasu Osoba, koja posjeduje dva konstruktora: Osoba() i Osoba(string ime, string datum). Drugi konstruktor se razlikuje od prvog po argumentima ime i datum. Klasa može imati samo jednog desktruktora, koji se poziva kada se objakat unuštava iz memorije. Tom priliko delociraju se članovi klase koje smo instancirali u heap memoriji ili neki drugi memorijski resursi. Na donjem listingu vidimo kako se deklarišu konstruktori odnosno desktruktor.
Svaka klasa može definisati proizvoljan broj svojih članova. Članovi klase mogu biti osnovni tipovi poput int, char, i dr, kao i svaki drugi izvedeni tip poput time_t odnosno svaka druga klasa. Svaki tip koji je definisan unutar određene klase postaje članom te klase. Kako smo ranije naglasili ono što klasu razlikuje od osnovnih tipova jeste mogućnost deklaracije metoda unutar same klase. Metode koje se deklarišu unutar klase zovemo metode članice, ili kratko članice klase. Metode članice deklarišu se na isti način kao i kod proceduralnog programiranja. Dijelovi metode članice jeste: nivo pristupa, tip vraćanja rezultata, naziv članice, argumenti koje metoda članica prihvata. Kod C++ u tijelu klase samo deklarišemo metodu članicu dok se njena implementacija nalazi izvan klase, s tim što ispred naziva metode nevedemo naziv klase, a zatim naziv metode (vidi donji listing).
Kod C# unutar klase istovremeno deklarišemo i implementiramo klasu. Primjeri deklaracije i definicije klase vidimo na litnigu 8.1, 8.2 i 8.3.
8.1.3 Enkapsulacija koda (en. Encapsulation)
Prvi i osnovni koncept OOP jeste enkapsulacija, koja podrazumijeva implementaciju ponašanja objekta unutar samog tipa. Ovo znači da globalne metode koje se definišu kod proceduralnog programiranja više ne koriste za implementaciju, već da se implementacija isključivo radi preko metoda članica klase. Pored toga što se implementacija enkapsulira u sami tip, uvodi se i kontrola pristupa ovako enkapsulirane implementacije na način da je objekte lakše kontrolisati i zabraniti neovlašten pristup. Gledano sa strane kontrole pristupa članovima klase postoje osnovna tri stepena pristupa koji posjeduju svaki OO jezik:
1. public – javni pristup članu klase. Svaki član koji je dekorisan sa public može biti korištem sa svakog nivoa pristupa tokom programiranja.
2. protected- zaštićeni pristup. Osim članova klase u kojoj je definisan, ovom članu može prostupiti samo još članovi izvedene klase.
3. private – zaštićeni pristup. Ovako označenom članu klase mogu pristupiti samo članovi iste klase.
Specifično za određene programske jezike (c++, c# ili java) postoje još neki nivoi pristupa, poput internal koji dopušta pristup svim članovima istog prostora imena. Međutim ovdje ćemo se zadržati isključivo na osnovna tri pristupna nivoa jer su podržani u svakom OOP jeziku.
Možemo kazati da enkapsulacija vrši preslagivanje koda u metode članice klase koje se označavaju raličitim nivoima pristupa, što kod čini sigurnijim, lakše razumljivijim i lakšim za održavanje. Instanca klase odnosno objekat pristupa samo javno označenim metodama članica. Kako je ranije naglašeno ovakav pristup omogućava lakše organizovanje tima programera u razvijanju istog projekta. Svaki član tog tima lahko može koristiti sve objekte koji su drugi članovi tima razvili, poznavajući samo načine komunikacije sa objektima, bez poznavanja njihove implementacije.
Pokušajmo fenomen enkapsulacije odnosno OOP koncepta objasniti preko realnog primjera. Pretpostavimo jedan fakultet na univerzitetu koji ima nekoliko smjerova. Na fakultetu rade profesori, asistenti i ostali zaposlenici. Oni su svi u službi obrazovanja studenata. Student nakon završetka srednje škole upisuje fakultet. Na samom početku student u studentsku službu predaje osnovne podatke o rođenju, državljanstvu, općem ispjehu u srednjoj školi. Zaposlenica u studentskoj službi otvara novi karton studenta u koji će upisivati sve relevantne informacije o studentovom uspjehu na fakultetu. Kada se otvori novi karton studenta, počinje njegov studentski život ali ne i studiranje. Da bi student imao validan status studenta potrebno je da upiše semestar. U koliko je samo upisao fakultet upisuje prvi semestar. Zatim student pohađa nastavu i polaže kolokvije, pismene ispite, a na kraju za svaki predmet polaže usmeni ispit kojim zaključuje pohađanje predmeta i dobija konačnu ocjenu. Nakon polaganja određenog broja predmeta, student završava semestar i upisuje naredni, i sve tako do kraja studija. Na kraju studija radi diplomski rad kao završni rad. Tokom studiranja student može imati i druge aktivnosti na fakultetu npr. može biti član neke studentske organizacije ili pak neke neformalne grupe za promicanje znanja u nauci ili sportu. Može biti član nekog sportskog tima sa fakulteta i sl. Sve u svemu student kroz studiranje ima pregršt prepreka a i mogućnosti da sa fakulteta izađe kao osposobljena osoba i budući zaposlenik neke kompanije ili državne ustanove. Na osnovu ovog kratkog opisa jednog procesa postavlja se zadatak da modeliramo ovaj proces i napišemo program.
Na samom početku analize ovog procesa možemo identifikovati nekoliko entiteta: fakultet, zaposlenici, profesori, asistenti, smijerovi, predmeti, semestri, student, i drugi manje značajni entiteti. Naravno u ovom primjeru nećemo modelirati sve identifikovane entitete, niti ćemo u detalje opisivati osobine i ponašanje entiteta. Kao primjer uzet ćemo entitet “student” i napisati program koji će opisati neke od njegovih osobina.
Entitet student ima ime prezime, datum rođenja, i ostale detalje koji nisu isključivo vezani samo za studenta, oni mogu biti vezani i za nastavnika kao i za ostale zaposlenike na fakultetu. Stoga kao zajednička osobina svih definisat ćemo klasu Osoba koja će posjedovatri zajedničke osobine studenta, zaposlenika, profesora i asistenata. Primjer ovako definisanog tipa možemo vidjeti na listinzima 1 i 2 za C++, a u listingu 3 za C# programski jezik. U pomenutim listinzima vidimo kako su podaci strukturirani u samom tipu. Svaka osoba posjeduje ime i prezima, datum i mjeto rođenja, bilo da se radi o studentu, profesoru ili nekom drugom entitetu.
Da bi uvidjeli razlike u implementaciji između OOP i proceduralnog programiranja, napisat ćemo kako bi izgledao ovaj program kad bi koristili proceduralno programiranje. U tom smislu definisali bi strukturu Osoba u koju bi stavili osnovne podatke studenta, a pored strukture u glavnom programu bi napisali nekoliko procedura koje bi nam koristile za promjenu statusa studenta, polaganje ispita, upisa godine i druge operacije. Ovo znači da bi glavni program kontrolisao i mijenjao status studenta zajedno sa svim drugim entitetima koje smo pobrojali na početku analize. U C++ gore rečeno mogli bi implementirati na sljedeći način:
struct Osoba { string imePrezime; time_t datumRodjenja; string mjestoRodjenja; }; Osoba osoba; void InitOsobe(string ime, string datum) { osoba.imePrezime = ime; //konverzija string u datum struct tm tm = { 0 }; strptime(datum.c_str(), "%d/%m/%Y", &tm); osoba.datumRodjenja = mktime(&tm); } string Status() { return "Osoba"; }
Uspoređivanjem OOP implementacije sa implementacijom prikazanom na listingu 8.4, možemo uočiti nekoliko značajnih razlika, prvenstveno načinom definisanja određenih dijelova koda, sigurnosti implementacije, mogućnosti pojavljivanja logičke i kompajlerske greške i drugim aspektima. Kada se porede ove dvije implementacije moguće je zapaziti nekoliko vrlo važnih značajki:
1. Kod OOP koncepta glavni program ne posjeduje implementaciju oko mijenjanja ponašanja objekta tipa Osoba u odnosu na proceduralnu implementaciju,
2. Kod OOP glavni program i svaki drugi modul ne može pristupiti implementaciji sve dok objekat tipa Osoba nije instanciran,
3. Samo instanca Osobe može pristupiti i promijeniti njegovo stanje.
4. Pristup članovima klase:ime, datum i mjesto rođenja, … postižemo kontrolisanim načinom.
Sve navedeno označava prvu karakteristiku OOP koncepta koju zovemo enkapsulacija ili enkapsuliranje implementacije u samu klasu. Vanjski objekti pristupaju metodama klase samo preko njegove instance, dok nekim dijelovima prstup je zabranjen. Proceduralna implementacija koju smo prikazali u Listingu 4 predstavlja sasvim otvorenu implementaciju te svaki dio implementacije može biti mijenjan. S druge strane metode koje smo implementirali se mogu pristupiti u svakoj situaciji bez obzira da li imamo instancu osobe ili ne.
Nasljeđivanje (en. Inheritance)
Dva su načina definisnja nove klase u OOP. Prvi način je da deklarišemo novu klasu definišući njen naziv, a zatim ostale dijelove, odnosno impelmentiramo metode članice. Drugim načinom klasu definišemo tako što ćemo iskoristiti već postojeću klasu i naslijediti svu njenu implementaciju. Drugim načinom uveliko iskorištavamo postojeću implementaciju koju smo već napisali, ili koju je već neko drugi napiso, a zatim dodajemo novu implementaciju koja je potrebna. U procesu nasljeđivanja nova klasa koja se formira zovemo izvedena klasa, dok se baznom klasom zove klasa iz koje smo naslijedili postojeću implementaciju.
Iz same definicije nasljeđivanja vidimo da je ono usmjereno na ponovno korištenje već postojećih implementacija, što je bio jedan od osnovnih problema modularno-proceduralnog programiranja. Sa ovim fenomenom OOP sada možemo izvoditi nove tipove podataka koji imaju već implementiranu osnovnu logiku. Nasljeđivanje najbolje objašnjavamo na primjerima. Ukoliko se vratimo na naš primjer, uočavamo da entiteti student, profesori, asistenti i zaposelnici imaju dosta zajedničnih atributa poput imena, datuma i mjest arođenja i sl. U tom kontekstu možemo iskoristiti baznu klasu Osoba u kojoj su određene zajedničke osobine svih osoba na fakultetu i formirati specijalnu klasu Student koja će reprezentirati isključivo osobine vezane za studente fakulteta. Implementacija ovako zamišljene klase prikazuje donji listing.
class Student : public Osoba { //konstruktori klase public: Student(); Student(string ime, string datum); ~Student(); //clanovi klase /atributi private: bool aktivan; int semestar; //javne metode clanica public: float ProsjekOcjena(); //javne metode clanice public: virtual string Status(); };
Pri definisanju nove klase nasljeđivanjem postoje određena pravila koja je potrebno imati na umu. U principu pri definisanju izvedene klase cijelokupna implementacija bazne klase se nasljeđuje osim konstruktora i privatno označenih članova. Zato je ovo dobro imati na imu tokom implementacije da se određeni članovi klase označe korektnim pristupnim novoom. U slučaju proceduralne implementacije uočavamo da ovaj fenome se ne može implementirati na ovakav način, pa bi u slučaju entiteta Student morali definisati drugi potpuno novu strukturu u koju bi stavili sve članove strukture Osoba, i dodali done članove.