Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 2.4: Luokan koodi ja ilmentymämuuttujat
Tästä sivusta:
Pääkysymyksiä: Miten luokkia määritellään ohjelmakoodissa? Miten ohjelmakoodi määrää, mitä tapahtuu, kun luokasta luodaan uusi ilmentymä? Miten luokan ohjelmakoodia sovelletaan yksittäiselle ilmentymälle? Voiko ilmentymän toiminta poiketa luokan määrittelystä?
Mitä käsitellään? Luokan ohjelmakoodi, ja erityisesti: luontiparametrien määrittelyt ja ilmentymämuuttujat. Luokan metodien räätälöinti yksittäiselle oliolle. Teräsmies.
Mitä tehdään? Sekä luetaan että ohjelmoidaan.
Suuntaa antava työläysarvio:? Puolitoista tuntia? Yli? Alle? Vaarin vanhat villahousut? Luku edellyttää kaikkien aiempien asioiden osaamista. Ilmentymämuuttujien ja luontiparametrien toiminta voi joka tapauksessa aiheuttaa aluksi päänvaivaa. Älä epäröi kysyä assarilta neuvoa!
Pistearvo: A65.
Kertausta
Tässä luvussa tutustutaan ohjelmakoodiin, jossa määritellään luokkia. Näin yhdistyy se, mitä tiedät yksittäisolioiden ohjelmakoodin laatimisesta (luku 2.2) siihen, mitä opit luokista ja niiden suhteesta olioihin (luku 2.3). Aloitetaan pikakertauksella niistä asioista, jotka ovat kaikkein tärkeimpiä tämän luvun ymmärtämiseksi.
Luokista ja olioista tiedetään jo:
Luokat ovat määritelmiä niistä tietotyypeistä, joita ohjelma käsittelee. Luokka määrittelee yleiskäsitteen, josta voidaan luoda ilmentymiä; luokka voi kuvata esimerkiksi opiskelijan käsitettä.
Luokan voi instantioida. Toisin sanoen: luokasta voi luoda ilmentymän eli olion, jolla on luokan kuvaama tietotyyppi. Tällainen olio on yksittäistapaus yleiskäsitteestä, esimerkiksi yksittäinen opiskelija.
Kun luomme ilmentymän luokasta, on usein tarpeen kirjata luontiparametreja. Näitä parametreja voidaan käyttää ilmentymäkohtaisten tietojen (esim. luotavan opiskelijaolion nimen ja opiskelijanumeron) alustamiseen.
Saman luokan eri olioilla on siis keskenään eri tiedot (esim. opiskelijaolioilla voi olla eri nimet). Näillä tiedoilla on kuitenkin samat tietotyypit (esim. kunkin opiskelijaolion nimi on merkkijono).
Samantyyppisillä olioilla on myös keskenään samat metodit. Esimerkiksi kaikilla opiskelijaolioilla on ilmoittautumismetodi, jos sellainen on niitä kuvaavaan luokkaan määritelty.
Yksittäisolioista tiedetään jo:
Scalalla voi määritellä yksittäisolioita, joille ei erikseen laadita luokkaa.
Yksittäisolion ohjelmakoodiin kirjoitetaan muuttujat, joiden arvoiksi kirjataan olion tietoja. Voimme myös toteuttaa oliolle metodeita.
Metodien toteutuksessa sana
this
viittaa olioon itseensä.this.jokuMuuttuja
-tyyppisellä ilmaisulla voimme näin viitata olion metodeista olion omiin muuttujiin.
Luokka vs. yksittäisolio
Luvussa 2.2 toteutimme yksittäisolion, joka kuvasi yhtä työntekijää, ja käytimme sitä.
Toisaalta äskeisessä luvussa 2.3 käytimme luokkaa Tyontekija
, joka kuvasi työntekijöitä
yleisenä tietotyyppinä ja oli siksi yksittäisoliota hyödyllisempi.
Alla on vertailun vuoksi sekä luvun 2.2 yksittäisolion että (nyt ensimmäistä kertaa)
Tyontekija
-luokan ohjelmakoodi. Ne ovat monilta osin aivan samanlaiset, mutta myös
tärkeitä eroja löytyy.
Seuraavien kahden koodilaatikon väliset vihreätaustaiset kommentit korostavat eroavat kohdat sekä niiden yllä että alla olevasta koodista, kun viet kursorin kommentin päälle.
object tyontekija:
var nimi = "Matti Mikälienen"
val syntynyt = 1965
var kkpalkka = 5000.0
var tyoaika = 1.0
def ikaVuonna(vuosi: Int) = vuosi - this.syntynyt
def kuukausikulut(kulukerroin: Double) = this.kkpalkka * this.tyoaika * kulukerroin
def korotaPalkkaa(kerroin: Double) =
this.kkpalkka = this.kkpalkka * kerroin
def kuvaus =
this.nimi + " (s. " + syntynyt + "), palkka " + this.tyoaika + " * " + this.kkpalkka + " euroa"
end tyontekija
Luokan nimen perässä ilmoitetaan, mitä luontiparametreja luokan ilmentymää luodessa tulee antaa. Yksittäisoliolla vastaavassa kohdassa ei ole mitään.
Parametrimuuttujia voi hyödyntää, kun olion tietoja asetetaan.
Luotavalle Tyontekija
-ilmentymälle asetetaan tiedoiksi
parametreina vastaanotettuja arvoja. (Tästä lisää alempana.)
Metodien määrittelyissä ei ole mitään eroa yksittäisolion ja luokan välillä!
class Tyontekija(annettuNimi: String, annettuSyntymavuosi: Int, annettuPalkka: Double):
var nimi = annettuNimi
val syntynyt = annettuSyntymavuosi
var kkpalkka = annettuPalkka
var tyoaika = 1.0
def ikaVuonna(vuosi: Int) = vuosi - this.syntynyt
def kuukausikulut(kulukerroin: Double) = this.kkpalkka * this.tyoaika * kulukerroin
def korotaPalkkaa(kerroin: Double) =
this.kkpalkka = this.kkpalkka * kerroin
def kuvaus =
this.nimi + " (s. " + syntynyt + "), palkka " + this.tyoaika + " * " + this.kkpalkka + " euroa"
end Tyontekija
Katsotaan nyt tarkemmin Tyontekija
-luokan toimintaa.
Ilmentymämuuttujat
Miten luokan ohjelmakoodi toimii, kun luomme uuden ilmentymän?
Tarkastellaan asiaa animaation avulla. Animaatio esittelee tärkeän ilmentymämuuttujan (instance variable) käsitteen.
Voit ajatella olion luomisprosessia myös aiemmin käytetyn lomakevertauksen kautta. Alla on päivitetty versio luvun 2.3 esityksestä; se on höystetty ilmentymämuuttujan käsitteellä.
Tarkennus ilmentymän luomisesta ja luokan ohjelmakoodista
Yllä mainittiin, että luokan sisään kirjoitetut käskyt suoritetaan, kun uusi ilmentymä luodaan. Esimerkiksi uuden työntekijäluokasta instantioidun olion ilmentymämuuttujille sijoitetaan tällöin (alku)arvot.
Huomaa kuitenkin, että tällä tarkoitettiin suoraan luokkamäärittelyn sisään kirjoitettuja käskyjä, ei metodien määrittelyjen sisältämiä. Metodien koodi suoritetaan vain silloin, kun erikseen kyseistä metodia kutsumalla käsketään.
Esimerkiksi työntekijäluokan ilmentymää luotaessa ei suinkaan tule kutsutuksi mikään sen
metodeista ikaVuonna
, kuukausikulut
, korotaPalkkaa
tai kuvaus
, vaikka nämä metodit
on luokan ohjelmakoodin sisällä määritelty. Mainittuja metodeita voi luokan käyttäjä
määrätä suoritettaviksi olion ensin luotuaan, kuten aiemmissa esimerkeissä on tehty.
Pikkutehtäviä ilmentymämuuttujista
Luontiparametrit vs. ilmentymämuuttujat
Metodin parametrimuuttujat ovat olemassa vain metodin suorituksen ajan. Vastaavasti luontiparametrit ovat olemassa ja niiden arvot talletettuina tietokoneen muistiin vain olion luomisprosessin ajan. Ne sijaitsevat kutsupinon kehyksessä. Tämä muistialue vapautetaan muuhun käyttöön heti, kun olio on saatu luotua.
Ilmentymämuuttujat puolestaan kuuluvat olion yhteyteen pysyvämmin. Niiden arvot säilyvät olion luomisen jälkeen ja myös metodikutsujen välillä.
Käskyt kuten val nimi = annettuNimi
siis pistävät parametreina saatuja arvoja talteen
myöhempää käyttöä varten.
Työntekijä- ja lemmikkiluokkien tapauksessa luontiparametreilla ei tehty mitään muuta kuin sijoitettiin niiden arvoja suoraan ilmentymämuuttujiin. Voikin herätä kysymys, miksi oliota luotaessa edes tarvitaan erikseen muuttujia luontiparametreille, jos niiden arvot vain sijoitetaan ilmentymämuuttujiin.
On järkevää, että luontiparametrit voi määritellä erikseen, koska luontiparametreilla voi tehdä myös muuta kuin kopioida ne ilmentymämuuttujiin; näet tästä esimerkkejä myöhemmin. Toisaalta "kopiointisijoitukset", joita noissa esimerkkiluokissamme esiintyy, ovat yleisiä, ja ne tosiaan voi ilmaista yksinkertaisemminkin. Scala tarjoaa mahdollisuuden tiivistää koodia:
Kätevämpi tapa kirjoittaa luokkia
Seuraavat kaksi versiota Tyontekija
-luokasta tekevät käytännössä täsmälleen saman
asian. Ensimmäinen on jo nähty alkuperäinen ja toinen "tiivistetty" versio.
class Tyontekija(annettuNimi: String, annettuSyntymavuosi: Int, annettuPalkka: Double):
var nimi = annettuNimi
val syntynyt = annettuSyntymavuosi
var kkpalkka = annettuPalkka
var tyoaika = 1.0
// metodien koodi tähän
end Tyontekija
var
- ja val
-sanat voi kirjoittaa myös heti luokkamäärittelyn
alkuun kuten alla. Tämä tarkoittaa, että sekä edellytetään
näiden kolmen tiedon antamista luontiparametreiksi, kun
Tyontekija
-tyyppistä oliota luodaan, että tallennetaan
annetut tiedot olion ilmentymämuuttujiin.
Yllä olevassa pidemmässä versiossa tehtiin annettu
-alkuisia
parametrimuuttujia ilmentymämuuttujien lisäksi. Niitä ei tiiviissä
versiossa ole lainkaan. Tiiviissä versiossa luontiparametrien
nimet ovat samat kuin ilmentymämuuttujien ja niiden arvot
kopioituvat ilmentymämuuttujiin ilman erillistä käskyä.
Neljännen ilmentymämuuttujan määrittelyssä ei tässä esimerkissä käytetä parametriarvoja. Se on määritelty molemmissa versioissa samalla tavalla.
class Tyontekija(var nimi: String, val syntynyt: Int, var kkpalkka: Double):
var tyoaika = 1.0
// metodien koodi tähän
end Tyontekija
Voit halutessasi tarkastella jälkimmäisen version toimintaa myös tästä animaatiosta.
Tässä käytetty tiivimpi tapa tekee koodista lyhyemmän ja kenties helpomman lukea. Joka tapauksessa tämä tyyli on Scala-ohjelmoinnissa yleinen, ja siihen on hyvä totutella heti. Tiiviimpää tyyliä tulemme käyttämään jatkuvasti.
Ilmentymän metodien toiminta
Metodikutsut toimivat luokasta instantioiduille olioille aivan samalla tavalla kuin
yksittäisolioille. Kuten luvussa 2.2 nähtiin, sanaa this
voi käyttää metodien
ohjelmakoodissa viittaamaan siihen olioon, jolle metodia on kutsuttu. Luokan ilmentymäksi
luotu olio pääsee sekin käsiksi esimerkiksi omiin ilmentymämuuttujiinsa ilmaisulla, joka
on muotoa this.muuttuja
.
Jos tämä asia tuntuu ihan selvältä, ohita surutta seuraava animaatio; siinä ei ole muuta uutta opittavaa. Muutoin katso se.
Pikkutehtävä: suorakaidetiiviste
Oletetaan, että meillä on käytössä luokka Suorakaide
, jota voi käyttää näin:
val testi = Suorakaide(10.0, 15.5)testi: o1.Suorakaide = o1.Suorakaide@152a308 testi.sivu1res0: Double = 10.0 testi.sivu2res1: Double = 15.5 testi.alares2: Double = 155.0
Suorakaideoliolta voi siis kysyä ainakin sen sivujen pituudet ja pinta-alan.
Tässä yksi tapa määritellä noin käytettävä luokka:
class Suorakaide(annettuSivunPituus: Double, annettuToinenSivunPituus: Double):
val sivu1 = annettuSivunPituus
val sivu2 = annettuToinenSivunPituus
def ala = this.sivu1 * this.sivu2
// jne. (Täällä voisi olla muita metodeita.)
end Suorakaide
Tämä tapa on tarpeettoman monisanainen. Määrittele luokka lyhyemmin määrittelemällä ilmentymämuuttujat jo luokan otsikkorivillä kuten edellä opetettiin.
Kirjoita ratkaisusi Oliointro-moduulin tiedostoon Suorakaide.scala
, josta löytyy
annettuna tuo pidempi ohjelmakoodi. Muokkaa annettu koodi pyydetynlaiseksi.
Varmista REPLissä kokeilemalla, että uusittu luokkasi toimii esimerkin mukaisesti.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Pikkutehtävä: suorakaiteen kuvaus
Kokoava pikkuharjoitus: tililuokka
Lisätreeniä: suorakaiteita grafiikaksi
Kuten sanottu, yllä laadittu suorakaideluokka kuvaa eräitä suorakaiteitten matemaattisia ominaisuuksia. On toki mahdollista lisätä luokkaan myös toimintoja, joissa suorakaidetta käsitellään graafisena elementtinä.
Lisää Suorakaide
-luokkaan metodi kuvaksi
, joka
ottaa yhden
Color
-tyyppisen parametrin, japalauttaa suorakaiteen kuvan (tyyppiä
Pic
), jossa leveytenä onsivu1
, korkeutenasivu2
ja värinä metodin saama parametriarvo.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Äskeisessä tehtävässä väri miellettiin lisätiedoksi, joka välitetään suorakaideoliolle parametriksi aina, kun halutaan suorakaideolion tuottavan itsestään uuden kuvan.
Vaihtoehtoisesti voimme mallintaa väriä kunkin suorakaiteen ominaisuutena.
Laadi samaan Suorakaide.scala
-tiedostoon toinen luokka nimeltä
VarillinenSuorakaide
. Kopioi Suorakaide
-luokan koodi
pohjaksi siihen alle ja vaihda kopion nimi. Muokkaa sitten
VarillinenSuorakaide
-luokkaa:
Lisää kolmanneksi luontiparametriksi
Color
-tyyppinen väriarvo.Huolehdi siitä, että tuohon väriin pääsee käsiksi myös
vari
-nimisen ilmentymämuuttujan kautta.Muokkaa metodia
kuvaksi
niin, että se on parametriton ja käyttää kyseisen suorakaiteen omaa väriä palauttamassaan kuvassa.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Kumpi laadituista luokista on parempi? Ero ei ole suuri, ja vastaus riippuu joka tapauksessa siitä, millaisen tai millaisten laajempien ohjelmakokonaisuuksien osaksi luokka olisi tarkoitettu.
Kiinnostavampi ja yleisluontoisempi kysymys on: kannattaako yleensäkään kirjoittaa mallinnettavaa käsitettä (kuten suorakaide) kuvaavaan luokkaan sellaista koodia, joka koskee ohjelman ilmiasuun liittyviä asioita (kuten värejä ja kuvia)? Palaamme tähän vielä kurssin aikana.
Monsteritehtävä
Laaditaan luokka, jolla voisi kuvata hirviöitä jossakin yksinkertaisessa, muutoin kuvitteellisessa pelissä.
Hirviöillä on hirviötyyppi ("örkki", "vampyyri", tms.) sekä kuntopisteet. Luokkaa tulisi voida käyttää seuraavan esimerkin kaltaisesti.
val otus = Hirvio("louhikäärme", 200)otus: Hirvio = Hirvio@597f1753 otus.tyyppires3: String = louhikäärme otus.taysiKuntores4: Int = 200 otus.nykykuntores5: Int = 200 otus.kuvausres6: String = louhikäärme (200/200)
Luodaan uusi ilmentymä luokasta, yksi hirviöolio. Luontiparametrit määräävät hirviön tyypin sekä sen kuntopisteet täysissä voimissa.
Miltä vain hirviöoliolta voi tiedustella sen tyyppiä sekä täysiä ja nykyisiä kuntopisteitä. Kuntopisteet ovat aluksi täysillä.
Oliolta voi myös pyytää kuvauksen, joka muodostuu hirviötyypistä sekä nykyisistä ja täysistä kuntopisteistä.
Hirviön tila muuttuu, kun se vahingoittuu vaikkapa sankarin hyökkäyksestä:
otus.vahingoitu(30)otus.nykykuntores7: Int = 170 otus.vahingoitu(40)otus.nykykuntores8: Int = 130 otus.kuvausres9: String = louhikäärme (130/200)
vahingoitu
-metodille ilmoitetaan, paljonko kuntopisteitä hirviö
menettää. Tämä vaikutuksellinen metodi vain muuttaa hirviön tilaa
eikä palauta mitään.
Muutos näkyy kysyttäessä hirviöltä sen nykykuntoa tai kuvausta.
Tutkitaan luokan toteutusta:
Konstruktori-termi
Tässä luvussa on puhuttu paljon useasti luontiparametreista; toinen nimi samalle asialle on "konstruktoriparametri". Jälkimmäinen termi juontuu konstruktorin (constructor; suomeksi myös rakentaja) käsitteestä: konstruktori on aliohjelma, joka suoritetaan oliota luotaessa ja jolla alustetaan olion tila. Käskyt, joilla sijoitetaan ilmentymämuuttujille arvoja, ovat konstruktoreissa tyypillisiä.
Scala-ohjelman tapauksessa konstruktorina toimivat luokkamäärittelyyn metodien ulkopuolelle kirjatut käskyt. Monissa muissa ohjelmointikielissä konstruktori on kirjoitettava erilliseksi määrittelyksi luokan sisään muiden metodien tapaan.
Odds-tehtävä (osa 1/9)
Todennäköisyyksiä (engl. odds) voidaan kuvata eri tavoin. Esimerkiksi kuusisivuista noppaa heitettäessä voimme sanoa, että kuutosen heittämiseen on "yksi kuudesta" -mahdollisuus. Eri ilmaisu samalle on "5/1", mikä kertoo, että on viisi tapaa olla heittämättä kuutosta ja yksi tapa heittää se. Toisin sanoen: on viisi kertaa niin todennäköistä, että kuutosta ei heitetä kuin että se heitetään. Vielä yksi ilmaisutapa on 16,67 %, joka voidaan myös kirjoittaa muotoon 0.1667.
Laaditaan luokka, jolla voidaan kuvata tapahtumien toteutumismahdollisuuksia ja esittää niitä eräissä muodoissa ja lopulta myös tehdä tiettyjä todennäköisyyslaskuja. Tässä tehtävässä aloitamme luokan toteuttamisen; luokkaa jatkokehitetään useassa pienessä tehtävässä myöhemmissä luvuissa.
Pohjustus: Odds
-luokan haluttu toiminnallisuus
Tarkoitus olisi, että yhtä Odds
-luokasta luotua oliota voitaisiin käyttää esimerkiksi
kuvaamaan todennäköisyyttä heittää kuutonen tai vedonlyöntitoimiston tarjoamaa arviota
Norjan todennäköisyydestä voittaa Euroviisut. Olion voisi luoda näin:
val rollingSix = Odds(5, 1)rollingSix: Odds = o1.odds.Odds@1c60524
Nyt rollingSix
-muuttuja sisältää viittauksen Odds
-tyyppiseen olioon, joka kuvaa
kuutosen heittämisen todennäköisyyttä. Luontiparametriksi annettiin "epäonnistuvien
tapausten määrä" (5) ja "onnistuvien tapausten määrä" (1), jotka määräävät todennäköisyyden.
Voimme nyt pyytää oliolta todennäköisyystietoja erilaisissa muodoissa. Metodilla
probability
saadaan kuutosen heiton todennäköisyys eli laskutoimituksen 1.0 ∕ (5 + 1)
tulos desimaalilukuna:
rollingSix.probabilityres10: Double = 0.16666666666666666
Metodilla fractional
todennäköisyys saadaan suhteellisessa muodossa:
rollingSix.fractionalres11: String = 5/1
Kuten näet, tämän metodin paluuarvo on merkkijono, jossa luontiparametreiksi annettujen lukujen välissä on kauttamerkki.
decimal
-metodilla saadaan probability
-metodin palauttaman luvun käänteisluku,
joka kertoo, "yksi monestako" on tapahtuman todennäköisyys. Tässä tapauksessa yksi kuudesta:
rollingSix.decimalres12: Double = 6.0
Otetaan toinen esimerkki. Monet kansainväliset vedonlyöntitoimistot ilmoittavat kohteensa
murtolukuina (kuten fractional
-metodin paluuarvossa). Odds
-olio voi toimia
vedonlyöntikohteen kuvaamisessa. Oletetaan nyt vaikkapa, että vedonlyöntitoimisto tarjoaa
Norjan euroviisuvoitolle luvut 5/2, ja mallinnetaan tämä Odds
-oliona:
val norwayWin = Odds(5, 2)norwayWin: Odds = o1.odds.Odds@1e75d66 norwayWin.probabilityres13: Double = 0.2857142857142857 norwayWin.fractionalres14: String = 5/2 norwayWin.decimalres15: Double = 3.5
Yllä näkyvä decimal
-metodin paluuarvo ilmoittaa kyseisen kohteen voittokertoimen.
Esimerkiksi 5/2-kohteesta onnekas vedonlyöjä saa panoksensa 3,5-kertaisena: rahansa
takaisin ja 2,5 kertaa panoksen verran lisää.
Suunnitellaan Odds
-luokkaamme vielä voittopotin suuruuden laskemiseenkin sopiva metodi
winnings
. Sillä voi laskea vaikkapa, paljonko voittaa, jos sijoittaa 20 rahaa Norjan
voitolle ja osuu oikeaan:
norwayWin.winnings(20.0)res16: Double = 70.0
(Tulos on siis panos 20 € kertaa decimal
-metodinkin palauttama kerroin 3.5.)
Viimeinen esimerkkimme alla osoittaa, että ensimmäinen luontiparametri voi toki mainiosti olla toista pienempikin, jos tapahtuma on todennäköinen:
val threeOutOfFive = Odds(2, 3)threeOutOfFive: Odds = o1.odds.Odds@dfdd0c threeOutOfFive.probabilityres17: Double = 0.6 threeOutOfFive.fractionalres18: String = 2/3 threeOutOfFive.decimalres19: Double = 1.6666666666666667 threeOutOfFive.winnings(123.45)res20: Double = 205.75
Tehtävänanto
Nouda moduuli Odds ja tutustu siellä olevaan Odds
-luokan määrittelyyn. (Projektin
muuhun sisältöön ei vielä tarvitse.)
Huomaat, että luokka on vajavainen: vähän alkua löytyy, ja metodi probability
on
tehty, mutta yllä esitellyt metodit fractional
, decimal
ja winnings
puuttuvat.
Toteuta ne.
Ohjeita ja vinkkejä
Sinulle on valmiiksi määritelty pari ilmentymämuuttujaa:
wont
jawill
. Ne saavat arvonsa suoraan luontiparametreista (tavalla, joka esiteltiin ylempänä kohdassa Kätevämpi tapa kirjoittaa luokkia).Voit hyödyntää näitä ilmentymämuuttujia metodeita laatiessasi.
Et tarvitse tässä muita ilmentymämuuttujia.
probability
-metodia ei siis tarvitse muuttaa; se toimii. Voit silti katsoa, miten se on tehty.Kunhan varmistat, että ymmärrät mitä kunkin metodin pitäisi saada aikaan, niin niiden toteuttaminen yksinkertaisia laskutoimituksia yhdistelemällä ei liene suuri homma.
Varmista REPLissä kokeilemalla, että metodisi toimivat.
Huomaa valita Odds-moduuli REPLiin.
Muista REPLin uudelleenkäynnistys muutosten jälkeen.
Palauta ratkaisusi vasta, kun olet toimintaan tyytyväinen.
fractional
-metodissa ei kuulu tehdä mitään sievennyksiä, vaan esimerkiksi luontiparametrien ollessa 6 ja 2 palautetaan6/2
eikä3/1
.Jos merkkijonon muodostamisessa tulee vaikeuksia, muista suorakaidetehtävän
kuvaus
-metodi.
Osaatko toteuttaa
decimal
jawinnings
-metodit niin, että kutsut näiden metodien sisältä jotakin toista saman luokan metodia? (Ei pakollista, mutta kätevää.)
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Metodi vai muuttuja?
Männävuosien opiskelijat pohtivat:
Ei auennut että millon kannattaa tehdä def
ja milloin val
?
Jäin miettimään, miksi halusimme Odds-tehtävässä, että probability
,
fractional
ja decimal
olivat metodeja. Kun nämä metodit eivät
kerran tarvitse mitään parametreja, eikö ainakin tämä esimerkki
toimisi yhtä hyvin, jos kirjoittaisimme ne ilmentymämuuttujiksi?
Tai siis toimii se, kokeilin. Miksi siis teimme näin?
Oikein hyvää pohdintaa.
Esimerkiksi seuraavasta koodista voisi kolme ensimmäistä def
-sanaa
vaihtaa val
-sanoiksi, ja koodi toimisi edelleen yhtä hyvin:
class Odds(…):
def probability = …
def fractional = …
def decimal = …
def winnings(bet: Double) = …
end Odds
Kun probability
, fractional
ja decimal
on määritelty metodeiksi def
-sanalla,
kunkin metodin sisältämä koodi suoritetaan vasta kun — tai siis vain jos —
kyseistä metodia todella kutsutaan ohjelmassa kyseiselle oliolle. Toisaalta metodin
sisältämät laskutoimitukset tulevat suoritetuiksi joka kerta, kun metodia kutsutaan,
joten toistuva saman metodin kutsuminen saa tietokoneen laskemaan saman asian moneen
kertaan.
Jos nämä metodit olisi määritelty val
-sanalla ilmentymämuuttujiksi, niiden alustaminen
olisi osa olion alustusprosessia ja kyseiset laskutoimitukset suoritettaisiin heti oliota
luotaessa. Laskujen tulokset jäisivät muuttujiin talteen osaksi olion tietoja. Näitä
tuloksia voisi "kysyä" oliolta helposti muuttujien kautta, eikä tietokoneen tarvitsisi
laskea niitä kullekin Odds
-oliolle kuin kertaalleen. Toisaalta tietokone laskisi
jokaiselle luodulle Odds
-oliolle kunkin näistä arvoista kerran (vaikka jokaista niistä
ei ehkä joka oliolle tarvittaisi kertaakaan). Koneen täytyisi myös käyttää hieman lisää
muistia näiden tietojen varastoimiseksi ilmentymämuuttujiin.
Huomaamme siis vaikutuksia muistinkäyttöön ja suoritusnopeuteen. Niiden painoarvo riippuu
siitä, miten usein mitäkin näistä metodeista/muuttujista kyseisessä sovelluksessa tarvitaan.
Pikkuesimerkissämme valinnalla ei ole mitään käytännön merkitystä, koska Odds
-olioita
luodaan muistiin vain muutama, niiden metodeita kutsutaan vain muutama kerta ja kukin
olioista vie joka tapauksessa muistia hyvin vähän.
Valintaan metodin ja muuttujan välillä vaikuttavat myös muut seikat; joskus on aivan
ratkaisevasti väliä sillä, tallennetaanko tietty tieto muistiin (val
tai var
) vai
luodaanko tieto uudelleen kutsuttaessa (def
). Muista esimerkiksi tämän luvun Hirvio
-luokka, jonka saimme kuntoon vaihtamalla yhden muuttujan tilalle metodin, joka muodostaa
hirviön kuvauksen senhetkisten tietojen perusteella. (Vinkki: vielä tällä kierroksella
kohtaat tehtävän, jossa ongelma on päinvastainen eli tarvitaan nimenomaan muuttuja eikä
metodia.)
Pannaanpa tähän vielä pieni mutta merkityksellinen pohdintatehtävä
Tallensit varmaankin äskeisessä tehtävässä Odds
-olion kaksi
luontiparametria val
-muuttujiin? (Toivottavasti!)
Miten koko äskeinen "voiko def
it muuttaa val
eiksi?" -pohdinta
muuttuisi, jos luontiparametrit menisivätkin talteen var
-muuttujiin
eikä val
eihin?
Pohdi sitä. Paljastan vastauksen viikkokoosteessa 3.0, joka julkaistaan kierroksen sulkeuduttua.
Yksittäisolion räätälöinti luokasta
Pöhköillään lopuksi vähän yhdellä esimerkkiluokalla. Pöhköillessä opitut temput tulevat käyttöön seuraavissa luvuissa.
class Henkilo(val nimi: String):
def lausu(lause: String) = this.nimi + ": " + lause
def reagoiSrirachaan = this.lausu("Onpa hyvä kastike.")
def reagoiKryptoniittiin = this.lausu("Onpa kumma mineraali.")
end Henkilo
Kullakin henkilöllä on metodi, jolla hänet voi komentaa sanomaan jotakin. Metodi palauttaa merkkijonon, joka sisältää henkilön nimen ja pyydetyn lauseen.
Lisäksi henkilöä voi komentaa reagoimaan erilaisiin asioihin. Henkilö reagoi lausumalla asioita, ja siksipä...
... onkin näppärää toteuttaa nämä metodit kutsumalla henkilön
itsensä lausu
-metodia. Henkilöolio ikään kuin lähettää
itselleen viestin: "Kun reagointimetodiani kutsutaan, komenna
this
-olio eli minut itseni suorittamaan lausu
-metodi tietyllä
parametriarvolla."
Käyttöesimerkkejä:
val eka = Henkilo("Jimmy")eka: Henkilo = Henkilo@5cf6635a eka.lausu("Super-Duper!")res21: String = Jimmy: Super-Duper! eka.reagoiSrirachaanres22: String = Jimmy: Onpa hyvä kastike. eka.reagoiKryptoniittiinres23: String = Jimmy: Onpa kumma mineraali. val toka = Henkilo("Lois")toka: Henkilo = Henkilo@645b797d toka.reagoiKryptoniittiinres24: String = Lois: Onpa kumma mineraali.
Kaikki Henkilo
t siis pitävät srirachasta ja kummastelevat kryptoniittia, koska heidän
luokkansa niin määrää. Juuri mitään muuta he eivät osaa tehdä, esimerkiksi lentää. Mutta
samat lait eivät päde kaikkiin:
Metodin lisääminen ilmentymäkohtaisesti
object terasmies extends Henkilo("Clark"): def lenna = "WOOSH!" end terasmies// defined object terasmies
Määritellään yksittäisolio, joka on erikoistapaus henkilöluokasta.
Käytämme tähän extends
-avainsanaa: tässä "laajennetaan"
henkilöluokan määrittelyä yksittäisen terasmies
-olion osalta.
Henkilo
itä luodessa on annettava luontiparametri, joka määrää
nimen. Tämän yksittäisen erikois-Henkilo
n määrittelyssä
kirjaamme luontiparametrin osaksi extends
-rimpsua.
Määritellään tälle henkilöoliolle uusi metodi. Tätä metodia ei siis ole muilla henkilöillä, mutta tällä oliollapa on.
Vapaaehtoisen loppumerkin voisi näin yksinkertaisesta oliosta hyvin jättää poiskin, mutta tähän se on esimerkin vuoksi kirjoitettu.
Teräsmies osaa lentää mutta myös kertoa nimensä ja reagoida asioihin kuten muutkin henkilöt. Tämäkin olio on henkilöolio.
terasmies.lennares25: String = WOOSH! terasmies.nimires26: String = Clark terasmies.reagoiKryptoniittiinres27: String = Clark: Onpa kumma mineraali.
Saimme räätälöityä tietylle oliolle uuden metodin. Entä voimmeko muokata luokalle yleisesti kirjoitettua metodimäärittelyä oliokohtaisesti?
Metodin korvaaminen
Vihreä kryptoniitti on Teräsmiehen heikkous. Jos haluamme kuvata hänet Henkilo
-luokkaa
käyttäen, voimmeko huomioida tämän seikan? Meidän pitäisi siis saada teräsmiestä kuvaava
henkilöolio poikkeamaan tältä osin siitä, miten muut henkilöoliot käyttäytyvät.
object realistinenTerasmies extends Henkilo("Clark"): def lenna = "WOOSH!" override def reagoiKryptoniittiin = "GARRRRGH!"// defined object realistinenTerasmies realistinenTerasmies.reagoiKryptoniittiinres28: String = GARRRRGH!
Määritellään Henkilo
-luokan jo määrittelemä metodi uusiksi
tälle yhdelle erikoisilmentymälle.
Tämä määritelmä korvaa (override) luokassa tehdyn yleisen
määrittelyn. Jotta tällaista ei tule tehtyä vahingossa, on
perusteltua, että Scala vaatii meidän merkitsevän korvaamisen
override
-avainsanalla. (Ilman tulisi virheilmoitus.)
Yhteenvetoa
Luokkien ohjelmakoodiin kirjataan ne ilmentymämuuttujat ja metodit, joita luokan ilmentymillä (olioilla) halutaan olevan.
Oliota luotaessa se saa omat kopionsa ilmentymämuuttujista, joille tallennetaan oliokohtaiset arvot.
Koodissa määritellään myös se, mitä luontiparametreja on annettava, kun uusi ilmentymä luodaan. Usein luontiparametrien arvoja tallennetaan sellaisenaan uuden olion ilmentymämuuttujiin.
On mahdollista ja silloin tällöin perusteltuakin laatia luokan tietylle ilmentymälle lisämetodeita. Voidaan myös korvata luokan määräämä toteutus ilmentymäkohtaiseksi räätälöidyllä toteutuksella.
Lukuun liittyviä termejä sanastosivulla: luokka, ilmentymä, ilmentymämuuttuja, instantioida, luontiparametri;
this
; korvata.
Tärkeää asiaa!
Ilmentymämuuttujat ja niiden suhde luontiparametreihin ovat monesti hankalanpuoleisia asioita olio-ohjelmoinnin oppimisessa. Aihe on kuitenkin luokkiin perustuvan olio-ohjelmoinnin ja tämän kurssin jatkon kannalta aivan keskeinen, ja siksi sitä on tässä luvussa selitetty eri tavoin. Jos suinkin mahdollista, jatka eteenpäin vasta sitten, kun sinusta tuntuu, että olet ymmärtänyt mainitut asiat. Kertaa esimerkkejä ja animaatioita tarpeen mukaan ja juttele assarien ja toisten opiskelijoiden kanssa.
Palaute
Huomaathan, että tämä on henkilökohtainen osio! Vaikka olisit tehnyt lukuun liittyvät tehtävät parin kanssa, täytä palautelomake itse.
Tekijät
Tämän oppimateriaalin kehitystyössä on käytetty apuna tuhansilta opiskelijoilta kerättyä palautetta. Kiitos!
Materiaalin luvut tehtävineen ja viikkokoosteineen on laatinut Juha Sorva.
Liitesivut (sanasto, Scala-kooste, usein kysytyt kysymykset jne.) on kirjoittanut Juha Sorva sikäli kuin sivulla ei ole toisin mainittu.
Tehtävien automaattisen arvioinnin ovat toteuttaneet: (aakkosjärjestyksessä) Riku Autio, Nikolas Drosdek, Kaisa Ek, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Anna Valldeoriola Cardó ja Aleksi Vartiainen.
Lukujen alkuja koristavat kuvat ja muut vastaavat kuvituskuvat on piirtänyt Christina Lassheikki.
Yksityiskohtaiset animaatiot Scala-ohjelmien suorituksen vaiheista suunnittelivat Juha Sorva ja Teemu Sirkiä. Teemu Sirkiä ja Riku Autio toteuttivat ne apunaan Teemun aiemmin rakentamat työkalut Jsvee ja Kelmu.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset laati Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Tapa, jolla käytämme O1Libraryn työkaluja (kuten Pic
) yksinkertaiseen graafiseen
ohjelmointiin, on saanut vaikutteita tekijöiden Flatt, Felleisen, Findler ja Krishnamurthi
oppikirjasta How to Design Programs sekä Stephen Blochin oppikirjasta Picturing Programs.
Oppimisalusta A+ luotiin alun perin Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Nykyään tätä avoimen lähdekoodin projektia kehittää Tietotekniikan laitoksen opetusteknologiatiimi ja tarjoaa palveluna laitoksen IT-tuki. Pääkehittäjänä on nyt Markku Riekkinen, jonka lisäksi A+:aa ovat kehittäneet kymmenet Aallon opiskelijat ja muut.
A+ Courses -lisäosa, joka tukee A+:aa ja O1-kurssia IntelliJ-ohjelmointiympäristössä, on toinen avoin projekti. Sen suunnitteluun ja toteutukseen on osallistunut useita opiskelijoita yhteistyössä O1-kurssin opettajien kanssa.
Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
Yksittäisolion määrittely alkaa sanalla
object
, luokan sanallaclass
. Molemmille voi vapaaehtoisesti määritelläend
-loppumerkin.