i i “1151-Marincek-0” — 2010/7/19 — 9:50 — page 1 — #1 i i i i i i List za mlade matematike, fizike, astronome in računalnikarje ISSN 0351-6652 Letnik 20 (1992/1993) Številka 6 Strani 358–365 Jože Marinček: UVOD V SVET OBJEKTOV Ključne besede: računalništvo, programiranje. Elektronska verzija: http://www.presek.si/20/1151-Marincek.pdf c© 1993 Društvo matematikov, fizikov in astronomov Slovenije c© 2010 DMFA – založništvo Vse pravice pridržane. Razmnoževanje ali reproduciranje celote ali posameznih delov brez poprejšnjega dovoljenja založnika ni dovo- ljeno. UVOD V SVET OBJEKTOV Objekti Vsi poznamo zapise (record-e) v pascalu . Omogočajo nam , da zberemo skupaj med seboj povezane podatke. Objekt dobimo, če dodamo še pod- programe, s katerimi obdelujemo te podatke . Tem podprogramom pravimo metode. Podatki določajo, kaj objekt ve. Metode določajo, kaj objekt zna. Na primer : celo število (integer) sicer ve za svojo vrednost , a si z njo ne zna pomagati . Naredimo objekt , ki bo svojo vrednost znal izpisati : type IntegerPlus = object vem : integer; procedure znam; end ; procedure IntegerPlus.znam ; begin write(' ' ,vem ,' ') end; {In tegerPlu5.znam} Enostaven primer uporabe bi bil: var 1: IntegerPlus; begin I.vem := 1; I.znam; end. { izpi še 'U lU " } Seveda so lahko podatki, ki jih združuje objekt , mnogo bolj zapleteni kot so v našem primeru . Zato poznamo dve posebni metodi , konstruktor in destruktor. Konstruktor je metoda, ki naj bi jo uporabili prvo. Ponavadi jo izkoristimo za prireditev začetnih vrednosti . Destruktor naj bi uporabili kot zadnjo. Dedovanje Dedovanje je pomembna lastnost objektov. Omogoča nam , da ustvarimo nove objekte, ki "vedo" in "znajo" vse, kar znajo stari objekti, lahko pa vedo ali .znajo še kaj več. Naredimo objekt celega števila, ki se zna izpisati , zna pa tudi povedati, ali je praštevilo : 359 ty pe Int egerPlu s Plus = object(l nt eger Plus ) functio n Prastevilo: boolean; end ; func t ion Int egerP lus Plus.Praste vilo: boolea n; va r test : boolean ; i: integer; begin test := Odd(vem) ; { Pre šte vilo je vedno liho. } i := 3; while test and (S qr(i) <= vem ) do begin test = vem m o d i <> O; i := i + 2 ; { Ker j e vem liho število. Im a le lihe de litelje . } end ; Prastevilo := tes t en d ; { IntegerPlusPlus .Prastevilo} va r 1: IntegerPlusPlus; begin I.vem := 1; I.zna m; writeln(' 'Je prastevilo ?', I.Prast evilo) end . { izpi še 'U ru :' } { izp iše "Je U praš tevilo?U TRUE"} Vidimo, da je me toda la hko tud i funk cijski podprogram . Vsaka metoda poz na vse, kar obje kt ve , tor ej vse njegove pod a tke , in j ih lahko poljubno uporab lja. . . ln sprem inja . O bjekt IntegerPlusPlus ve in zna vse, kar zn a objekt IntegerPlus. Zato nam me tode znam ni bilo treba še enkrat dek larirati in kasneje nap isati . 5 e v eč , objekt IntegerPlus lahko zapremo v neko enoto (unit) in prijateljici od stopimo sa mo prevedeno datoteko ( .TPU) , pa bo ona še vedno mog la def inira t i obj ekt Integ erPlusPlus , hk rati pa ne bo mogla šariti po našem prog ramu ( kar še posebej velja za njenega mlajšega brata). Pravimo , da je Int egerPlusPlus naslednik objekta IntegerPlus. Pascalu to sporoči mo t ako , da za besedi co object napi šemo v oklepaju im e prednika . Tud i vsem morebitn im nasledniko m objekta IntegerPlusPlus bomo rekli nasled niki objekta IntegerPlus . Torej je lah ko vsa k objekt o če , ded , praded ... cele dru žine objektov. Zarad i dedovanja o bj ektov so post ala druga če str oga pascalova pravi la o prirejanju nekoliko ohlapnejša . Do sedaj je veljalo , da lahko spremenij ivki 360 določenega tipa priredimo samo spremenljivko oziroma izraz istega tipa . Edi- na izjema so bile spremenljivke tipa Real, ki smo jim mogli prirediti tudi celoštevilske izraze . Pri objektih pa velja, da mu smemo prirediti objekt istega tipa ali njegovega naslednika. l.e objekt, ki mu prirejamo vrednost, ne ve ali zna vsega, kar mu prirejamo, se odvečna informacija enostavno izgubi . Pravilo je torej enostavno: izraz mora biti sposoben zapolniti celoten objekt. Drugače bi lahko ostalo kakšno polje brez prirejene vrednosti, čemur pa se moramo na vsak način ogniti . Nasledniki objekta lahko metode objekta (eno ali več) tudi spremenijo . Prepišimo objekt IntegerPlusPlus tako, da bo pred praštevilom izpisana zvezdica! Podprogram Pokazi pa bo izpisal vrednost enemu ali drugemu objektu . type IntegerPlusPlus = object(lntegerPlus) procedure znam; function Prastevilo: boolean; end; function IntegerPlusPlus.Prastevilo: boolean; var test: boolean ; i: integer; begin {... kot prej ...} end; procedure IntegerPlusPlus.znam; begin write(' '): if Prastevilo then write(, *') ; write(vem: ') end; {/ntegerPlusPlus.znam) procedure Pokazi(var o: IntegerPlus) ; { Sprejme tudi argument tipa IntegerPlusPlus in sploh vseh naslednikov. } begin o.znam end ; {Pokazi} var 1: IntegerPlus; J : IntegerPlusPlus; I begin I.vem 17 ; J .vem .- 23 ; I.znam; J .znam ; I := J; I.znam; Pokazi(I); Pokazi(J); end. 361 { izpiše 'U 17U " } { izpiše 'U *23U " } { izpiše 'U 23U " } { izpiše 'U 23U " } { izpiše 'U 23U" } Bodimo pozorni! Nasledniki lahko spreminjajO metode (in dodajajo nove) , podatkovnemu polju objekta pa tipa ni moč spreminjati . Nasledniki lahko le dodajajo nova podatkovna polja . Navidezne metode Pri objektih ločimo dve vrsti metod : statične in navidezne . S statičnimi meto- dami se ukvarja prevajalnik. Ko pri prevajanju sreča objekt, ki uporabi neko svojo statično metodo, reče : "Aha! Točno vem , kaj hočeš" in to tudi naredi . Včasih pa to ni najbolje . Tako podprogram Pokazi ne more izkoristiti tega, da je ob drugem klicu parameter J pravzaprav tipa IntegerPlusPlus , ki zna izpisati zvezdico pred praštevilom . Ko je pascal prevajal podprogram Pokazi, mu je bilo povsem jasno, da ima opravka z objektom t ipa IntegerPlus , torej je tudi "vedel" , katero metodo bo uporabil. Hoteli bi, da se prevajalnik šele ob klicu metode odloči, katero metodo bo uporabil : t isto iz objekta IntegerPlus , ono iz objekta IntegerPlusPlus , ali pa morebitno tretjo metodo iz nekega naslednika teh dveh objektov. To nam omogočajo navidezne metode . Trik je enostaven : za imenom metode napišemo rezervirano besedico virtual. Ko objekt pokliče tako metodo, si prevajalnik samo zapiše , da na tem mestu od objekta pričakujemo določeno akcijo . Vsak objekt, ki uporablja vsaj eno navidezno metodo , se mora na tako delo pripraviti. To stori s posebno metodo , konstruktorjem . Ta poskrb i, da se podatki o objektu z api šejo v posebno tabelo, brez katere navidezne metode ne znajo delati . Prepišimo sedaj naša objekta z navideznimi metodami : type IntegerPlus = object vem: integer; constructor dobro.jutrof n: integer) ; procedure znam ; virtual ; end ; 362 Int eg erPlusPlus = object(lntegerPl us) co nst ructor dobro_jutro(n : inte ge r) ; procedure znam ; virtual ; function Prastevilo: bo olean; end ; constructor lntegerPlus .dobro.jutrof n: int eg er); begin vem := n end; { /n tegerPlus.dobro_i utro} constructor IntegerPlusPlus .dob ro _jutro(n: inte ge r) ; begin vem := n end ; { /n tegerPlusPlus.dobro_i utro} Seveda ni potrebn o , da konstruktor nastavi za četne vrednosti objekta . Kode, ki konstruktor l o č i od ostalih metod , tako ali tako nikoli ne vidimo, saj jo doda prevajalnik. Konstruktor zato svoje delo opravi, tudi če je v pascalu povsem prazen . Zato mora imeti vsak objekt , ki ima vsaj eno navidezno metodo, svoj konstruktor (tisti, ki ga ima oče, ni dober) . Poglejmo, kaj si o nov ih objektih misli isti podprogram Pokazi: begin I.dobro _jut ro(17); J .dobro_jut ro(23); Pokazi(I); Pokazi(J ) end . { Post avi vrednost polja I.vem na 17. } { Postavi vrednost polja J.vem na 23. } { izpiše 'U i 7U " } { izpiše 'U * 23U" } V zadnjem primeru se nikoli nismo dotaknili polja vem. Pravilo lepe- ga programiranja pravi, da poda tkovna polja objekta uporabljajo izključno metode tega objekta . Skladno s tem moramo objektu IntegerPlus dodati vsaj še dve metodi : type Int eger Plus = object vem : int eg er ; constructor dobro.jutro]n: integ er ) ; procedure priredi(n: int eg er) ; function vredn ost : intege r; procedure znam ; vir tual ; end ; I procedure IntegerPlus .priredi(n: begin vem := n end ; { /n tegerPlus .priredi} function IntegerPlus .vredn ost( n: begin vredn ost := vem end ; { /n tegerPlus. vredn ost } int eger) ; int eg er); 363 Sedaj že vemo, da isti metodi poznajo t udi objekti tipa IntegerPlusPlus . Na čin , na katerega se objekti tipa IntegerPlus in IntegerPlusPlus kažejo podprogramu Pokazi, imenujemo polimorfizem. Ime metode je vedno enako , v našem primeru je to znam. Kaj pa se v resn ici zgodi , je odvisno od objekta , kateremu metoda pripada . Razvijmo za vajo še soroden objekt , ki bo poznal znak in ga bo znal izpisati : type Char Plu s = object vem : char; constructor do bro.j ut ro ]c: ch ar ) ; procedure priredi(c : char) ; function vrni : cha r; procedure znam ; virtual ; end; constructor Cha rPl us .dobro_jutro(c: char) ; begin vem := c end ; { CharPlus .dobro_jutro} procedure Cha rP lus .priredi(c: cha r}; begin vem := c end ; { CharPlus.priredi} function Char P lus .v rni : c ha r ; begin vrni := vem end ; { CharPlus.vrni} procedure CharPlus .znam; begin writ eln(' ' ,vem ,' ' ) end ; { CharPlus .znam } 364 Vidimo, da sta si objekta IntegerPlus in CharPlus zelo podobna. Kako bi lahko to sorodnost zapisali tudi v pascalu? Objekti močno poudarijo staro pravilo (katerega nihče ne posluša), da velja temeljito razmisliti in proučiti problem , predno se lotimo programiranja . Obema objektoma , IntegerPlus in CharPlus , je skupno to , da znata izpisati neki podatek. Kateri podatek izpišeta in kako to naredita , pa je seveda njuna stvar. Sedaj sku šajrno vse sorodnosti potegniti iz obeh objektov v nov, skupni objekt: type Osnova = object constructor dobro.jutro: procedure znam ; virtual ; end; constructor Osnova .dobro.jutr o: begin end; {Osnova .dobro_jutro} procedure Osnova .znam; begin end ; { Osnova.znam} Metoda Osnova . znam je prazna , saj osnovni objekt ne nos i nobene informa- cije. Njegova naloga je samo to, da opiše , kaj je skupnega vsem njegovim naslednikom . Vsak naslednik pa bo že poskrbel , da se bo izpisal na prav- ilen način . Sorodnost med objekti (v našem primeru med IntegerPlus in CharPlus) bomo ohranili z dedovanjem , drugačne izpeljave metod pa nam bodo prinesle razlike. type Int eg erPlus = object(Osnova ) vem: integer; constructor dob ro_jutro(n: integer) ; procedure prired i(n: int eger); function vrednost: int eg er ; procedure znam; virtual ; end ; CharPlus = object(Osn ova) vem: ehar; constructor dobro.jutro] n: char) ; procedure priredi(n : char) ; function vrednost: ehar; procedure znam ; virtual ; end; Manjkajore metode lahko bralec napize Sam. Sedaj lahko spremenimo It podprogram Pokazi: procedure Pokazi(var 0: Osnova); begin o.znam end; Tako napisan podprogram pa pravilno izpiSe objektc vseh v Elanku ornenjcnih tipov. Tako nam obj tkt i omogoEijo, da nam ni treba pisati enega podpro- grarna za izpis celih Itevi l , drugega za ixpis znaka, Ee tretjcga za nov tip, ki ga ncnadoma potrebujemo. Dovolj je, da ga napiEemo cnkrat, vsakemu od tch tipov pa raxlofimo, kakEen jc v resnici njegov izpis. Poenostavitve raradi dela z objekti pridcjo do izraxa, ko se postopki zapletejo. Tako narn objekti prihranijo ogromno dela, ko se lot imo risanja grafiEnih objtktov (datjicc. krogi itd.). Prav pri takJnih postopkih objekti resnizno zai ivi jo. Jose Marintek