Uvod v enkapsulacijo

To poglavje je bistveno pri spoznavanju objektno usmerjenega programiranja. Enkapsuliranemu objektu pogosto pravimo abstraktni podatkovni tip. Smisel enkapsulacije je, skrijemo oziroma zavarujemo del programske kode tako, da je ne moremo pomotoma pokvariti. Pravimo, da del podatkov skrijemo.

Primer programa ------> OPEN.CPP

Program OPEN.CPP je v bistvu neumen, saj ne počne nič pametnega, vendar omogoča razumevanje ideje o skrivanju podatkov.

V vrsticah 4 do 7  je definiran tip preproste strukture, ki vsebuje eno samo spremenljivko tipa  int . V vrstici 11 imamo definirane tri take strukture.  V samem programu  so spremenljivke teh struktur nastavljene in kasneje izpisane. Njihovo obliko in vsebino prikazuje spodnja slika.

SKRIVANJE PODATKOV

Primer programa ------> CLAS.CPP

Program  CLAS.CPP kaže, kako lahko podatke skrijemo. Podoben je prejšnjemu, vendar opazimo nekaj razlik.

Tako smo v vrstici 4 namesto besedice struct uporabili class. S to besedico deklariramo razred (class). V razliko od struktur, pri katerih so komponente  načeloma javne (public), so komponente razredov načeloma privatne in tako skrite zunanjemu svetu.

V našem primeru smo v razredu one_datum spremenljivki  data_store dodali še dve funkciji:  set() in get_value(). Ti dve funkciji sta prav tako kot sama spremenljivka komponenti deklariranega razreda.

KAJ JE PRIVATNA SEKCIJA?

Vsi podatki (ali bolje komponente ali členi), ki začenjajo seznam razreda, so privzeto privatni. Zato niso dostopni izven tega razreda. Spremenljivke data_store ki je del objekta (pojem objekta bomo kmalu spoznali) z imenom dog1, v programu  main() ne bi mogli uporabiti. Med nami in to spremenljivko je nekakšen "zid", ki ga ponazoruje spodnja slika.


 V tem zidu pa sta "luknji", ki nam omogočata dostop do funkcij set() in get_value(). Ti luknji smo dobili tako, da smo obe funkciji deklarirali v javni sekciji (public) razreda.
 

KAJ JE  SEKCIJA PUBLIC?

Vrstica 7 uvaja ključno besedo public. Ta pove, da bo vse, kar sledi tej besedi, javno oziroma dostopno programu, ki uporablja objekte tega razreda. Tako smo lahko definirali dve funkciji, ki nam edini omogočata posredni dostop do skrite  spremenljivke. Takim funkcijam, ki so člani nekega razreda, prvimo članske funkcije (member functions).

Potem, ko smo deklarirali take funkcije, moramo še povedati, kaj te funkcije sploh delajo. V našem primeru storimo to v vrsticah  12 - 20. Tu je vsaka funkcija definirana na običajen način, novo je le to, da smo pred ime vsake funkcije dodali ime razreda, vmesno ločilo pa je dvojno podpičje. Tema dvema definicijama funkcij pravimo implementacija funkcij. Ime razreda potrebujemo zato, ker lahko uporabimo enako ime funkcije tudi v drugih razredih in mora prevajalnik vedeti, kateremu razredu pripada neka implementacija funkcije.

Bistveno je, da so privatni podatki nekega razreda dostopni funkcijam tega razreda (ne pa funkcijam drugih razredov).

V splošnem lahko definiramo tako v sekciji privat kot v sekciji public tako spremenljivke kot funkcije. Običajno pa je, da so spremenljivke večinoma skrite v privatni sekciji, funkcije pa  so večinoma definirane v javni sekciji.

V C++ imamo štiri dosege veljavnosti (scope) spremenljivk, global, local, file in class. Globalne spremenljivke so dostopne povsod, tako v datoteki, kjer so definirane, kot v drugih datotekah.  Spremenljivke dosega "File" so tiste, ki so definirane izven vseh funkcij. Dostopne so kjerkoli znotraj datoteke, v kateri so definirane. Spremenljivka  v dosegu razreda (class) je dostopna samo znotraj razreda, vključno z implementacijsko kodo, vendar nikjer drugje več. V našem primeru ima tak doseg spremenljivka data_store.
 

ŠE NEKAJ BESED O IZRAZOSLOVJU
 

Sedaj lahko v luči teh spoznanj nadaljujemo s proučevanjem programa CLAS.CPP. Imamo torej razred z eno spremenljivko in dvema metodama. Metodi delujeta na spremenljivki, ki je vsebovana v razredu, in to takrat, ko dobita  ustrezni zahtevek oziroma  sporočilo.

Pozor na vrstici 8 in 9. V njih sta podana podana prototipa dveh metod, kar navedemo kar v definiciji razreda. Metoda z imenom set() terja en parameter tipa int in ne vrača nič (zato je njen return tip void. Metoda  get_value() pa je brez parametrov in vrača vrednost tipa  int.

POSREDOVANJE SPOROČIL

Oglejmo si program, ki uporablja ta razred! V vrstici  24 so definirani trije objekti, dog1, dog2, in dog3 iz razreda one_datum. Vsak objekt ima podatek, ki pa ga lahko nastavljamo le posredno z uporabo metode set() ali beremo njegovo vrednost z uporabo metode get_value(). Direktno do takega podatka ne moremo priti, ker je "skrit". Pravimo, da pošiljamo objektu  sporočila (messages). Tako v vrstici  27 pošiljamo objektu dog1 sporočilo, da naj nastavi svojo interno vrednost na 12. Posredovanje sporočil je na videz podobno funkcijskim klicem.  Podobno nastavimo s posredovanjem ustreznih sporočil tudi vrednosti objektov dog2 in dog3.

Vrstici 32 in 33 have sta zakomentirani, ker vsebujeta nelegalno kodo. Spremenljivka  data_store je namreč označena kot  private in je zato koda izven objekta ne doseže. Povejmo še, da  podatka v objektu  dog1  metode v objektih   dog2 in dog3 ne dosežejo, ker so pač to drugi objekti.

Program vsebuje  tudi eno navadno spremenljivko,  piggy, ki je v programu deklarirana in uporabljena v ilustracijo, da lahko mešano uporabljamo objekte in navadne spremenljivke.
 
 

PROBLEMATIČEN PROGRAM

Primer programa ------> OPENPOLE.CPP

Oglejmo si primer programa, v katerem se srečamo s problemi, ki jih enkapsulacija lahko reši.

Imamo deklariran tip strukture  rectangle. V programu sta definirani dve strukturi tega tipa: box in square.


V programu množimo višino in širino (height in width).Običajno pomeni tak produkt površino nekega lika. V primeru množenja širine enega objekta z višino drugega pa dobimo nesmisel. Je pa tak zapis s stališča jezikov C in C++ povsem legalen. Uporaba objektno usmerjenega programiranja lahko take nesmisle prepreči.
 

OBJEKTI ŠČITIJO PODATKE

Primer programa ------> CLASPOLE.CPP

V tem programu je rectangle spremenjen v razred (class) z enakima spremenljivkama kot prej, le da sta sedaj private, dodani sta še dve metodi, ki omogočata uporabo teh privatnih podatkov. Eno metodo uporabljamo za inicializacijo vrednosti teh podatkov, druga metoda pa računa njun produkt in s tem  površino.

Čeprav sta oba objekta,  square in box  iz istega razreda, so njuni podatki skriti in do  zmešnjav ne more priti.
 

STE TO TEHNIKO ŽE UPORABLJALI?

Dober primer uporabe tehnike enkapsulacije je že znano delo z datotekami. Podatke iz datotek lahko dobimo le s pomočjo vnaprej določenih funkcij, ki jih daje na voljo prevajalnik. Sami direktno do podatkov na fizičnih naslovih diska ne moremo priti. Pridemo lahko do podatkov, ki jih res potrebujemo, do drugih (sistemskih) podatkov pa ne, kar preprečuje rušenje datotečnega sistema na disku.

Podobnih primerov je še več. Vidimo, da temelji objektno usmerjenega programiranja pravzaprav obstajajo že dolgo.
 
 

KONSTRUKTORJI IN DESTRUKTORJI

Primer programa ------> CONSPOLE.CPP

Ta program uvaja pojem konstruktorjev in destruktorjev, je pa sicer podoben prejšnjemu.

Konstruktor ima vedno enako ime kot sam razred. Deklariran je v vrstici 9, definiran pa v vrsticah 15 do 19. Konstruktor kliče sistem C++ avtomatično vsakokrat, ko je deklariran nek objekt iz tega razreda. To preprečuje uporabo neinicializiranih spremenljivk. Ko v vrstici 48 definiramo objekt  z imenom box, je konstruktor klican avtomatično. Konstruktor nastavi v objektu box spremenljivki height in width na 6. To lahko kontroliramo s testnim izpisom v vrsticah 51 in 52. Podobno se zgodi, ko v vrsticfi 48 definiramo objekt square. Tudi tu avtomatsko poklicani konstruktor nastavi vrednosti spremenljivk height in width objekta square na vrednost 6.
 

Konstruktor  ima v svoji definiciji enako ime kot njemu ustrezen razred. V našem primeru se oba imenujeta rectangle. Konstruktorju ne navajamo tipa return, ker že ima vnaprej določen tip, to je tip kazalca na tvorjeni objekt. V našem primeru konstruktor sicer spremenljivkam obeh objektov dodeli začetne vrednosti, vendar jih v vrsticah 60 in 61 spremenimo. Ker opravi inicializacijo že konstruktor, je ime metode  initialize() morda neprimerno in bi ga bilo bolje spremeniti.

Destruktor je konstruktorju precej podoben, klican pa je avtomatično, ko gremo iz območja obsega  (scope) danega objekta.  Spomnimo se, da imajo avtomatske spremenljivke omejeno življensko dobo in obstojajo le v bloku, v katerem so bile deklarirane. Podobno je z objektom. Ko naj bi bil dealociran, je avtomatsko klican ustrezen destruktor, če seveda obstaja. Tudi destruktor nima tipa return .

V našem primeru je destruktor klican v vrstici 12 in definiran v vrsticah od 32 do 36. V našem primeru destruktor le priredi spremenljivkam, predno so te dealocirane, vrednost 0. Torej se v bistvu ne zgodi nič. Če pa bi v objektu imeli na primer dinamično alocirane bloke pomnilnika, bi jih morali v destruktorju sprostiti, preden zgubimo kazalce nanje. Tako bi tak pomnilnik lahko sprostili in pozneje v programu ponovno uporabljali.

Zanimivo je, da v primeru objekta, ki ga kot globalne spremenljivke deklariramo pred funkcijo main(), ustrezen konstruktor izveden pred  programom main(). In destruktor takega objekta se bo izvedel po zaključku programa oziroma funkcije main().
 

PAKIRANJE OBJEKTOV

Primer programa ------> BOXES1.CPP

Ta primer prikazuje, kako pakiramo objekt s ciljem njegove splošne uporabnosti. Tak način zadošča v primeru res majhnih programov, za večje programe, ki jih običajno delimo v več datotek, pa bomo kasneje spoznali bolj primerno metodo pakiranja.

V našem primeru je razred deklariran v vrsticah 4 do 13, implementacijo razreda pa podajajo vrstice 16 do 35. Razred je uporabljen v vrsticah  38 do 52.
 

IMPLEMENTACIJA INLINE

Metoda v vrstici  11 vsebuje implementacijo metode kar v  delu njene deklaracije, saj je le-ta preprosta. Pri tem spoznamo še nekaj, pogosto uporabljenega pri programiranju v jeziku  C++. Če je implementacija vključena v deklaracijo, bo prevedena "inline", torej povsod, kjer bomo funkcijo klicali. Tak program bo hitrejši, saj ne pride do siceršnjega funkcijskega klica. Kodiranje "inline" je v tem smislu  analogno uporabi makrojev v jeziku C.

Oglejmo si naslednje tri primere, ki ponavljajo naš program v malo drugačni obliki.
 

ZAGLAVNA DATOTEKA RAZREDA

Primer  ------> BOX.H

V datoteki  BOX.H  zasledimo definicijo razreda. Pri tem niso podane podrobnosti, kako naj bi bile implementirane posamezne metode, razen seveda za metodo inline z imenom get_area( ). Razred je torej popolno definiran, vendar brez implementacijskih podrobnosti. V bistvu smo uporabili vrstice  4 do 13 prejšnjega programa, podanega v datoteki BOXES1.CPP. Novi datoteki pravimo zaglavna datoteka (header file) in je same po sebi ne moremo niti prevajati, kaj šele izvajati.
 

IMPLEMENTACIJSKA DATOTEKA RAZREDA

Primer ------> BOX.CPP

Oglejmo si datoteko BOX.CPP. V njej so implementirane metode, deklarirane v zaglavni datoteki razreda. Zato je zaglavna datoteka (ki vsebuje prototipe teh metod in definicije spremenljivk)  vanjo vključena v vrstici 2. Kodo v vrsticah 16 do 35 datoteke BOXES1.CPP uporabimo v datoteki box.c za  samo implementacijo metod.

To datoteko lahko prevedemo, ne dobimo pa izvršljivega programa saj ne vsebuje vstopne točke main, kot to zahtevajo programi, pisani v C ali C++. Pri prevodu te datoteke dobimo objektno kodo, ki bo shranjena (kot takoimenovana objektna datoteka) v istem direktoriju in bo na voljo drugim programom. Pozor: pojem objektne kode in objektne datoteke nima nobene zveze s pojmom objektno usmerjenega programiranja oziroma z objekti.

Ločitev definicije in implementacije je momemben korak v programskem inženirstvu. Pisec novega programa potrebuje za uporabo nekega razreda le ustrezno definicijsko datoteko Ne zanima ga, kakšna je konkretna implementacija uporabljenih  (implementiranih) metod. Če  bi imel na voljo tudi implementacijsko datoteko (njeno izvorno kodo), bi jo lahko proučil, našel kakšen programerski trik in morda napisal svoj program bolj učinkovito, dolgoročno pa bi to lahko povzročilo nekompatibilnosti, ko bi avtor implementacijske datoteke v le-tej kaj spreminjal. Smisel objektno usmerjenega programiranja je tudi v skrivanju implementacije, ki naj ne vpliva na nič izven njenih, dobro definiranih meja oziroma takoimenovanega vmesnika (interface)
 

UPORABA OBJEKTA BOX

Primer programa ------> BOXES2.CPP

Oglejmo si datoteko BOXES2.CPP in videli bomo, da je v njej uporabljen pravkar definiran razred. V bistvu so zadnje tri datoteke skupaj enakovredne programu BOXES1.CPP, ki smo ga najprej proučili.

Datoteko BOX.H smo v zadnji, programski datoteki vključili v vrstici 3, ker potrebujemo definicijo razreda  box , če naj deklariramo take objekte in uporabimo njihove metode. Program se bo po prevodu obnašal tako kot njegov predhodnik, vendar je med  BOXES1.CPP in BOXES2.CPP vseeno velika razlika.

Predvsem zamenjajmo izrazoslovje. Ne govorimo več o klicanju funkcij temveč o posredovanju osporočil. Med obema operacijama je bistvena razlika. Ker so podatki nekega objekta zelo tesno vezani na ta objekt, lahko do teh podatkov pridemo le preko metod in pošiljamo objektu sporočila, kakšne operacije naj naredi na teh podatkih. Pri klicu funkcij pa ji posredujemo podatke kot parametre, saj funkcija ne vsebuje lastnih podatkov (Pošteno povedano je ta razlika zelo majhna).

Preizkusimo sedaj program: Najprej moramo datotekoko prevesti. Prevedeno datoteko moramo povezati z datoteko BOX.OBJ. Tako dobimo izvršljivo programsko datoteko, ki jo preizkusimo.

Ta primer nas torej hkrati uvaja v tehniko programiranja z uporabo večjega števila manjših datotek. Primer tudi ponazarja metodo skrivanja podatkov, kar bistveno vpliva na razvoj kompleksne programske opreme.
 

ABSTRAKTNI TIPI PODATKOV

V začetku poglavja smo že omenili abstraktne tipe podatkov. Abstraktni tip podatkov je skupina podatkov, pri kateri lahko vsak pomni vrsto verednosti, in skupina metod oziroma funkcij, ki lahko delujejo na teh podatkih. Podatki so zaščiteni pred zunanjimi vplivi, so torej zaščiteni (protected) oziroma enkapsulirani. Interakcija med podatki v taki skupini je lahko zelo velika, interakcija z zunanjim svetom pa zelo skromna.

Objekt je skromno povezan z zunanjim svetom tudi s svojimi metodami. Vzdrževanje takega objekta je torej relativno enostavno.
 
 

PRIJATELJSKE FUNKCIJE

Funkcijo izven nekega razreda lahko definiramo kot razredu prijateljsko funkcijo ( friend function), ki lahko dostopa do privatnih članov nekega razreda. To v bistvu odpre majhno luknjo v zaščitni ščit razreda . Zato moramo biti precej previdni.  So primeri, ko postane tako program bolj razumljiv in ko želimo vsaj skromen in nadzorovan dostop do podatkov. Prijateljske funkcije bomo med tečajem spoznali na več primerih, tu jih imenjamo le zaradi popolnosti poglavja. Prijateljsko drugim razredom lahko deklariramo eno ločeno funkcijo ali člane drugih razredov. Celo cel razred lahko dobi status prijatelja. Konstruktorji in destruktorji pa ne morejo biti prijateljske funkcije.

KONSTRUKT struct V C++

Konstrukt struct je  v C++ še vedno na voljo in deluje enako kot pri  ANSI-C, vendar  z majhnim dodatkom. V strukture lahko dodajamo tudi metode, ki pa delujejo na podatkih enako kot pri razredih. V razliko od razreda so v strukturi tako metode kot podatki privzeto javni (public). Lahko pa jih opredelimo kot privatne z definicijo privatne sekcije v strukturi. Strukture uporabljajmo le za konstrukte, ki so resnične strukture.  Tudi pri uporabi najbolj preprostih objektov raje uporabljajte razrede za njihovo definicijo.

ZELO PRAKTIČEN RAZRED

Primeri enkapsulacije v tem poglavju so bili zelo enostavni. Za študij bolj kompleksnih primerov uvedimo razred  date.  Ta razred naj omogoča branje trenutnega datuma in njegov izpis v obliki niza  ASCII v enem od štirih vnaprej določenih formatov. Uporabimo ga lahko tudi za pomnenje poljubnega datuma in njegovo formatiranje za prikaz.

Primer  ------> DATE.H

Oglejmo si dobro komentirano datoteko  DATE.H, ki je zaglavna datoteka  razreda date. V vrstici 13 na novo opazimo rezervirano besedo  protected. Njen pomen bomo spoznali v naslednjih poglavjih , zaenkrat pa si zamislimo, da ima isti pomen kot beseda  private. Kodo v vrsticah  8 in 9 ter 58 bomo spoznali na kratko. Zaenkrat jih kar ignorirajmo. Tudi besedo  static, uporabljeno v vrsticah 18 in 19 bomo spoznali kasneje. Ti konstrukti so uvedeni za kasnejše spoznavanje dedovanja.

Pred nadaljevanjem tečaja dobro proučite to zaglavno datoteko.

Primer programa ------> DATE.CPP

Datoteka DATE.CPP uporablja razred date in ne predstavlja nič posebnega. Predstavlja preprosto implementacijo pomnenja in uporabnega formatiranja datuma. Proučite to implementacijsko datoteko pred prehodom na primer uporabe razreda  date v glavnem programu

Implementacija konstruktorja v vrsticah  14 do 25 uporablja sistemske klice  DOS za ugotovitev trenutnegga datuma.  To kodo lahko tudi spremenite glede na operacijski sistem, ki vam je na voljo. Namen te kode je le prikaz enkapsulacije in konstruktorjev, ne pa resnično branje realnega časa.

Primer programa ------> USEDATE.CPP

Ta preprost program uporablja razred date za izpis trenutnega datuma v dveh različnih formatih.
 



Kazalo