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: konstruktoriparametrien 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 konstruktoriparametrien 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 konstruktoriparametreja. 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ä konstruktoriparametreja 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
Konstruktoriparametrit vs. ilmentymämuuttujat
Metodin parametrimuuttujat ovat olemassa vain metodin suorituksen ajan. Vastaavasti konstruktoriparametrit 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 konstruktoriparametreilla ei tehty mitään muuta kuin sijoitettiin niiden arvoja suoraan ilmentymämuuttujiin. Voikin herätä kysymys, miksi oliota luotaessa edes tarvitaan erikseen muuttujia konstruktoriparametreille, jos niiden arvot vain sijoitetaan ilmentymämuuttujiin.
On järkevää, että konstruktoriparametrit voi määritellä erikseen, koska konstruktoriparametreilla 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 konstruktoriparametreiksi, 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 konstruktoriparametrien
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 erittäin 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 konstruktoriparametriksi
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.
Mielenkiintoisempi 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. Konstruktoriparametrit 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:
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ä. Konstruktoriparametriksi 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 palautusarvo on merkkijono, jossa konstruktoriparametreiksi 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 palautusarvossa). 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 palautusarvo 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 konstruktoriparametri 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 konstruktoriparametreista (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 konstruktoriparametrien 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.
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
iltä luodessa on annettava konstruktoriparametri, joka
määrää nimen. Tämän yksittäisen erikois-Henkilo
n määrittelyssä
kirjaamme konstruktoriparametrin 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ä luokalle yleisesti kirjoitetun metodimäärittelyn muokkaaminen 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ä konstruktoriparametreja on annettava, kun uusi ilmentymä luodaan. Usein konstruktoriparametrien 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, konstruktoriparametri;
this
; korvata.
Tärkeää asiaa!
Ilmentymämuuttujat ja niiden suhde konstruktoriparametreihin 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.
Konstruktori-termi
Tässä luvussa puhuttiin useasti konstruktoriparametreista. Tämä termi, jota olemme käyttäneet olion luomisen yhteydessä annettavista parametreista, 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ä kirjoitetaan konstruktori erilliseksi määrittelyksi luokan sisään muiden metodien tapaan.
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, Joonatan Honkamaa, Antti Immonen, Jaakko Kantojärvi, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, 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.