9. Klase

Do ovog momenta, vidjeli smo različite vrste, ili klase, objekata: string, integer, float, nizove i par specijalnih objekata (true, false, nil) o kojima ćemo nešto kasnije. Klase u Ruby uvijek počinju velikim slovom: String, Integer, Float, Array,... Kad želimo kreirati novi objekat određene klase, onda koristimo new:

a = Array.new + [12345] # Dodavanje niza.

b = String.new + 'hello' # Dodavanje stringa.

c = Time.new

puts 'a = '+a.to_s

puts 'b = '+b.to_s

puts 'c = '+c.to_s

Ispis:

a = 12345

b = hello

c = Sat Jan 31 07:18:32 GMT 2009

Iz razloga što nizove i znakove možemo kreirati pomoću [...] i '...' (respektivno), rijetko koristimo new. (Iako to možda nije očigledno iz pređašnjeg primjera, za kreiranje praznog stringa koristimo String.new, a za prazan niz koristimo Array.new) Treba napomenuti da su brojevi izuzetak/iznimka: ne možete kreirati integer koristeći Integer.new, nego jednostavno taj integer napišete.

Klasa za vrijeme – „Time“

Klasa za vrijeme? O čemu se tu radi?

Objekat Time predstavlja određeni vremenski momenat. Na ovaj objekat možemo dodati ili oduzeti određene (brojčane) vrijednosti, da bismo dobili „novo“ vrijeme, odnosno neki drugi vremenski momenat: ako na trenutno vrijeme dodamo 1.5, onda ćemo kao rezultat dobiti vrijeme 1.5 sekundu kasnije:

time = Time.new # Momenat koji ste dobili u trenutku ispisa.

time2 = time + 60 # Minuta kasnije.

puts time

puts time2

Ispis:

Sat Jan 31 07:18:32 GMT 2009

Sat Jan 31 07:19:32 GMT 2009

Ovaj objekat možete koristiti i da biste kreirali određen vremenski momenat,...koristeći Time.mktime:

puts Time.mktime(2000, 1, 1) # Y2K.

puts Time.mktime(1976, 8, 3, 10, 11) # Datum mog rođenja.

Ispis:

Sat Jan 01 00:00:00 GMT 2000

Tue Aug 03 10:11:00 GMT 1976

Napomena: datum mog rođenja sadrži format Pacific Daylight Savings Time (PDT). Kada je nastupila 2000.-ta godina, vrijeme je dobilo Pacific Standard Time (PST) format, bar za nas sa zapadne obale. Zagrade služe da grupišu parametre mktime. Što više parametara dodate, tačnije vrijeme ćete dobiti.

Možete koristiti poredbene metode da uporedite određena vremena (manja cifra je starije), te ako ih oduzmete jedno od drugog, dobijete broj sekundi između njih. Pokušajte, igrajte se malo!

Par stvarčica za oprobati

    • Milijarda sekundi...napišite program koji će reći prije koliko sekundi ste rođeni (ako umijete). Saznajte kada ćete biti milijardu sekundi stari (ili ste to već)? Program će vam pomoći da to označite na svoj kučnom kalendaru J
    • Sretan rođendan! Napišite program koji će osobu pitati godinu, mjesec i dan rođenja. Otkrijte na taj način koliko je osoba stara, pa joj za svaku godinu dajte jedan...SPANK!

„Hash“ klasa

Još jedna korisna klasa je „Hash“. Ne može se reći da je ovaj objekat poput niza, jer nizovi imaju mnogo slotova, indeksiranih brojevima, počevši od nule, smještenih u redove. Za „hash“ ne možemo reći da su smješteni u redove, a u slotove se može ubaciti svaki objekat, ne samo broj [k.p.: indeksirani su i po nekim drugim vrijednostima, ne samo brojevima, kako je to slučaj kod nizova]. Dobro je koristiti „hash“ kada imate dosta stvari koje želite imati pri ruci, a te se stvari ne mogu baš poredati u niz uređen brojčanim indeksom. Naprimjer, boje koje sam koristio za ovaj tutorial:

colorArray = [] # isto kao Array.new

colorHash = {} # isto kao Hash.new

colorArray[0] = 'red'

colorArray[1] = 'green'

colorArray[2] = 'blue'

colorHash['strings'] = 'red'

colorHash['numbers'] = 'green'

colorHash['keywords'] = 'blue'

colorArray.each do |color|

puts color

end

colorHash.each do |codeType, color|

puts codeType + ': ' + color

end

Ispis:

red

green

blue

strings: red

keywords: blue

numbers: green

Kad koristim nizove, moram pamtiti da je slot 0 za string, slot 1 za integer, itd. Međutim, „hash“ mi dozvoljava da znatno pojednostavim stvari! Slot 'strings' sadrži boju za tekst, naravno. Ništa ne moram pamtiti. Primjetit ćete da, kad smo koristili –each-, program nije ispisao objekte unutar „hash-a“ redoslijedom kojim smo ga mi unosili u programu. (Možda nisu dok sam ja ovo pisao, kod mene. Možda su neki drugi put, nikad se ne zna s „hash“ klasama.) Nizovi su ti koji sve drže u savršenom redu – „hash“ klase nisu za to zadužene.

Iako ljudi većinom koriste string da bi imenovali slotove „hash“ klasa, moguće je umetnuti bilo kakav objekat, čak i nizove ili druge „hash“ klase (iako mi trenutno ne pada na pamet zašto biste radili ovo poslijednje):

weirdHash = Hash.new

weirdHash[12] = 'monkeys'

weirdHash[[]] = 'emptiness'

weirdHash[Time.new] = 'no time like the present'

Hash klase i nizovi su korisni u različitim situacijama, pa je na vama da odaberete šta je bolje koristiti za određeni problem, koji sebi postavite.

Proširivanje klasa

Na kraju prošlog poglavlja, napisali smo metodu koja je „čitala“ brojeve. Međutim, to nije bila metoda koju smo mogli primjeniti na neki integer, nego jednostavno – metoda koja je predstavaljala čitav program. Ne bi li bilo lijepo kad bismo mogli jednostavno napisati nešto kao 22.to_eng umjesto englishNumber 22? Evo kako bi to izgledalo:

class Integer

def to_eng

if self == 5

english = 'five'

else

english = 'fifty-eight'

end

english

end

end

# Dobro bi bilo da testiram na par brojeva...

puts 5.to_eng

puts 58.to_eng

Ispis:

five

fifty-eight

Ja sam testirao program, izgleda da radi. ;)

Dakle, definisali smo metodu za integer „uskačući“ u Integer klasu, definišući metodu unutar nje. Sada će svi integeri biti podvrgnuti ovoj (pomalo nepotpunoj) metodi. Ustvari, ako vam se nije sviđalo kako metoda za stringove -to_s- radi, možete je jednostavno redefinisati na način sličan ovom primjeru...ali vam ne bi preporučio da je dirate, bilo kako bilo!

Predefinisane metode programskog jezika najbolje je ostaviti onakvim kakve jesu i u slučaju da želimo nešto novo – napraviti nove, vlastite metode ili klase.

Zbunjeni...? Da prođemo još jednom kroz ovaj poslijednji program. Sve do sada, kad god bismo izvršili kod ili definirali metode, radili smo to u programu kao objektu. U pređašnjem primjeru programa, program kao objekat smo ostavili i ušli u Integer klasu. Unutar nje smo definisali metodu (što je čini integer metodom) koju mogu koristiti svi integer objekti. Unutar te metode koristimo rezervisanu riječ –self- da bismo ukazali na objekat koji ta metoda koristi (integer).

Kreiranje klasa (classes)

Pred nama su bile brojne klase različitih klasa objekata. Međutim, vrlo je lahko napraviti i onu koja u Ruby inače ne postoji. Na sreću, kreiranje nove klase je podjednako jednostavno kao i proširivanje neke postojeće. Recimo da smo htjeli napraviti kockicu za „Ne ljuti se čovječe“ u Ruby. Ovako bi njena klasa izgledala:

class Die

def roll

1 + rand(6)

end

end

# Da sad napravimo par kockica...

dice = [Die.new, Die.new]

# ...a onda ih „bacimo“.

dice.each do |die|

puts die.roll

end

Ispis:

6

1

(Ako ste preskočili dio u kome smo učili o generisanju slučajnog broja, evo male pomoći za vas,...rand(6) generiše slučajni broj u vrijednosti između 0 i 5.)

To je sve! Imate vlastite objekte. Bacite kockice nekoliko puta (refresh [F5] na originalnog stranici http://pine.fm/LearnToProgram/) i pogledajte šta će se desiti.

Možemo mi definisati razne metode za naše objekte...ali nešto ipak nedostaje. Rad s ovim objektima podsjeća uveliko na programiranje prije upoznavanja s varijablama. Pogledajte, naprimjer, naše kockice. Možemo i „baciti“ i svaki put će nam dati različite brojeve. Ali, ako bismo htjeli ostati na tom broju, onda bi morali napraviti varijablu koja bi taj broj zadržala, dakle, ukazivala na broj. Ukoliko bismo morali pratiti kockicu, ne bismo morali pratiti i brojeve.

Međutim, ako bismo pokušali pohraniti broj koji smo dobili pri bacanju kockice u (lokalnu) varijablu, on bi trajao do završetka bacanja. Treba nam, dakle, neki drugi tip varijable u koji ćemo smjestiti naš broj:

Neposredne varijable

Obično, kad hoćemo govoriti o znakovima/tekstu, onda govorimo o stringu. Međutim, mogli bismo slobodno reći i da je to string objekat. Ponekad programeri nazovu instanca klase String, međutim, to je samo uglađen način (i prilično dug, ako mene pitate) da se kaže string. Instanca neke klase je jednostavno objekt koji pripada toj klasi.

Tako dakle, neposredne varijable (instance) su varijable nekog objekta. Lokalne varijable, koje pripadaju nekoj metodi, traju do završetka metode. S druge strane, neposredne (instance) varijable nekog objekta traju sve dok taj objekat traje. Da bi smo razlikovali obične lokalne varijable od neposrednih, koristimo znak @ koji stavljamo ispred imena varijable:

class Die

def roll

@numberShowing = 1 + rand(6)

end

def showing

@numberShowing

end

end

die = Die.new

die.roll

puts die.showing

puts die.showing

die.roll

puts die.showing

puts die.showing

Ispis:

6

6

6

6

Vrlo dobro! Prilagođena metoda „roll“ baca kockice, a „showing“ nam govori koji broj kockica pokazuje. Međutim, šta ako pokušamo pogledati koje ćemo brojeve dobiti prije nego bacimo kockice (prije nego definišemo @numberShowing)?

class Die

def roll

@numberShowing = 1 + rand(6)

end

def showing

@numberShowing

end

end

# Pošto ovu kockicu neću ponovo koristiti

# ne trebam je ni čuvati u varijabli.

puts Die.new.showing

Ispis:

nil

Hmmm,...ako ništa, nije nam se javila nikakva grješka. Svejedno, nema smisla da nam se kocka „ne baci“, ili šta god da povratna vrijednost „nil“ znači. Bilo bi lijepo kad bi se naše kockica mogla „podesiti“ tik prije nego što je bacimo. Za tu svrhu koristimo –initialize-:

class Die

def initialize

# Jednostavno ću „baciti“ kockicu, iako

# bismo mogli učiniti s njom i štošta drugo

# naprimjer, da stalno pokazuje broj 6.

roll

end

def roll

@numberShowing = 1 + rand(6)

end

def showing

@numberShowing

end

end

puts Die.new.showing

Ispis:

2

Kada se kreira objekat, njegova –initialize- metoda (ako je definisana) uvijek se poziva.

Naše su kocke skoro pa savršene. Jedno što im možda nedostaje je da nam metoda kaže koju stranu kocke pokazuje...zašto ne biste napravili metodu koja upravo to pokazuje. Vratite se čitanju kad završite (i nakon što se svoj program testirali, naravno)! Ne dozvolite da neko podesi kockicu tako da može pokazati i sedmicu!

Tako dakle, pokrili smo ovim poglavljem dosta zanimljivih stvari. Jeste pomalo komplikovano, tako da ću navesti još jedan primjer. Recimo da želimo napraviti jednostavnog virtualnog ljubimca, bebu zmaja. Kao i većina beba, trebala bi biti u mogućnosti da jede, spava i . . . „kaki“, što će reći da ćemo je morati hraniti, staviti u krevet i „izvoditi napolje“. Unutar bebe zmaja trebao bi postojati metod koji će pratiti glad, pospanost i digestiju, ali mi nećemo moći da vidimo sve to, isto kao što bebu ne možemo pitati „Jesi li gladna?“. Dodaćemo takođe još nekoliko zanimljivih sitnica koje će povećati interakciju sa našom bebom zmajem, a kad se rodi daćemo mu ime (šta god da unesete u tu novu metodu, ono se prenosi do –initialize- metode). U redu, da pokušamo:

class Dragon

def initialize name

@name = name

@asleep = false

@stuffInBelly = 10 # Pun stomak.

@stuffInIntestine = 0 # Ne mora ići „vani“.

puts @name + ' is born.'

end

def feed

puts 'You feed ' + @name + '.'

@stuffInBelly = 10

passageOfTime

end

def walk

puts 'You walk ' + @name + '.'

@stuffInIntestine = 0

passageOfTime

end

def putToBed

puts 'You put ' + @name + ' to bed.'

@asleep = true

3.times do

if @asleep

passageOfTime

end

if @asleep

puts @name + ' snores, filling the room with smoke.'

end

end

if @asleep

@asleep = false

puts @name + ' wakes up slowly.'

end

end

def toss

puts 'You toss ' + @name + ' up into the air.'

puts 'He giggles, which singes your eyebrows.'

passageOfTime

end

def rock

puts 'You rock ' + @name + ' gently.'

@asleep = true

puts 'He briefly dozes off...'

passageOfTime

if @asleep

@asleep = false

puts '...but wakes when you stop.'

end

end

private

# "private" znači da metode koje su ovdje definisane jesu

# metode interne za objekat (Možete nahraniti svoj zmajčeka

# ali ga ne možete upitati da li je gladan.)

# Metode se mogu završavati i sa "?".

# Obično tako završavamo samo one metode koje

# daju povratnu vrijednost true ili false kao:

@stuffInBelly <= 2

end

def poopy?

@stuffInIntestine >= 8

end

def passageOfTime

if @stuffInBelly > 0

# Pomjeri hranu iz stomaka u crijevo.

@stuffInBelly = @stuffInBelly - 1

@stuffInIntestine = @stuffInIntestine + 1

else # Umrije nam zmajić od gladi!

if @asleep

@asleep = false

puts 'He wakes up suddenly!'

end

puts @name + ' is starving! In desperation, he ate YOU!'

exit # Ovdje se program završava.

end

if @stuffInIntestine >= 10

@stuffInIntestine = 0

puts 'Whoops! ' + @name + ' had an accident...'

end

if hungry?

if @asleep

@asleep = false

puts 'He wakes up suddenly!'

end

puts @name + '\'s stomach grumbles...'

end

if poopy?

if @asleep

@asleep = false

puts 'He wakes up suddenly!'

end

puts @name + ' does the potty dance...'

end

end

end

pet = Dragon.new 'Norbert'

pet.feed

pet.toss

pet.walk

pet.putToBed

pet.rock

pet.putToBed

pet.putToBed

pet.putToBed

pet.putToBed

Ispis:

Norbert is born.

You feed Norbert.

You toss Norbert up into the air.

He giggles, which singes your eyebrows.

You walk Norbert.

You put Norbert to bed.

Norbert snores, filling the room with smoke.

Norbert snores, filling the room with smoke.

Norbert snores, filling the room with smoke.

Norbert wakes up slowly.

You rock Norbert gently.

He briefly dozes off...

...but wakes when you stop.

You put Norbert to bed.

He wakes up suddenly!

Norbert's stomach grumbles...

You put Norbert to bed.

He wakes up suddenly!

Norbert's stomach grumbles...

You put Norbert to bed.

He wakes up suddenly!

Norbert's stomach grumbles...

Norbert does the potty dance...

You put Norbert to bed.

He wakes up suddenly!

Norbert is starving! In desperation, he ate YOU!

Opa! Naravno, bilo bi puno bolje da u ovom programu ima malo više interakcije, ali taj dio možete doraditi poslije. U ovom primjeru sam pokušao pokazati samo dijelovi koji se odnose na kreiranje nove klase – dragon -.

U ovom smo primjeru vidjeli nekoliko primjera. Prvi primjer je: -exit- će okončati program tačno na onom mjestu na kojem se nalazi. Drugi: -private- rezervisana riječ koju smo stavili u sred definicije klase. Mogao sam je izostaviti, ali sam htio da napravim razliku među metodama koje su stvari koje možemo „učiniti“ našem zmaju, a druge se jednostavno samo događaju s njim. Mislite o ovim metodama kao o onom što je „pod haubom“: ukoliko niste automehaničari, sve što trebate znati o automobilima da biste ih vozili su pedale gasa i kočnice i volan. Programer bi mogao ove akcije nazvati public interface / vidljivo sučelje. Međutim, kada treba aktivirati okidač zračnog jastuka, za to se brinu interni sistemi u automobilu, sistemi za koje obični vozač i ne zna (niti mora da zna).

Hajde da navedemo konkretan primjer kako predstaviti automobil u video igri (što je moj stvarni posao). Prvo bi trebalo razlučiti kako trebaju izgledati javna sučelja, odnosno, drugim riječima, koje bi metode igrači bili u mogućnosti pozvati nad objektima koje sačinjavaju automobil. Trebalo bi da su u mogućnosti tisnuti pedalu gasa, pedalu kočnice, ali i da budu u mogućnosti odrediti kojom jačinom će stisnuti pedalu (velika je razlika samo je dotaći i stisnuti do poda). Trebalo bi i da budu u mogućnosti okretati volan i opet, odrediti kojom brzinom i za koji ugao. Pretpostavljate da bismo mogli ići sve dalje i dalje,...na kvačilo, žmigavce, bacač granata, nitro pogon, itd ...zavisno od vrste igre koju pravimo.

Međutim, u unutrašnjosti automobila trebalo bi se dašavati daleko više; ostale stvari, koje auto treba su brzina, smjer, pozicija (u najjednostavnijem). Ovi atributi bi se mijenjali pritiskom na papučicu gasa ili kočnice i okretanje volana, naravno, ali njih korisnik ne bi mogao mijenjati direktno (što bi bila dobra osnova za warp). Mogli biste takođe dodati pređeni put, ili oštećenje automobila prilikom utrkivanja...sve nevedeno bi bilo „unutrašnje“ u objektima prisutnim u automobilu.

Par stvarčica za oprobati

    • Napravite klasu OrangeTree. Trebala bi imati metodu height koja bi kao povratnu vrijednost davala visinu i metodu onYearPasses koja, kada se pozove, utvrdi starenje. Svake godine, drvo postaje sve visočije (koliko vi mislite da bi drvo narandže trebalo porasti godišnje) i nakon određenog vremena, kad prođe mnogo godina, drvo bi trebalo istruhnuti. Prvih nekoliko godina, ne bi trebalo proizvoditi voće, ali nakon određenog vremena, počinje rađati; valjda, što je starije drvo, biće više plodova svake godine,...šta god vama bude smisleno. Naravno, trebalo bi biti u stanju countTheOranges (probrojati narandže – povratna vrijednost je količina na drvetu) i pickAnOrange (ubrati narandžu,...što smanjuje @orangeCount za jednu narandžu i vraća string koji govori kako je ukusna narandža bila, ili vraća informaciju da su sve narandže ubrane). Pobrinite se i da sve narandže koje ne uberete opadnu prije nego dođe nova godina/sezona.
    • Napravite program koji će dati malo više interakcije sa bebom zmaja. Trebali biste u mogućnosti dati komande kao što su nahrani i hodaj, tako da bi se ove metode mogle izvršiti nad zmajem. Naravno, kako unosite samo tekt (strings) program će morati da provjeri unos i pozove na osnovu unosa odgovarajuću metodu, što bi trebalo izvesti preko neke vrste dispečera metoda.

Time smo došli do kraja ovog poglavlja! Ali,...čekajte malo...ne rekoh vam ništa o onim klasama koje šalju e-mail, pohranjuju ili učitavaju dokumente, o kreiranju prozora i dugmića, ili 3D svijetu ili,...bilo čemu!

Ah, toliko različitih klasa postoji, koje su vam na raspolaganju, da nije moguće pokazati sve njih; pa, ni ja ne znam većinu njih! Ono što vam mogu pokazati je, gdje da pronađete njih i nešto više o njima, tako da biste mogli naučiti one koje želite koristiti u svojim programima. Prije nego vas otpremim, ima još jedna stvar u Ruby koju trebate znati, nešto što većina drugih programskih jezika nema, a bez čega se jednostavno ne može živjeti: blokovi i procedure,...

----------------------------------------------

© 2003-2009 Chris Pine

http://pine.fm/LearnToProgram/