Tämä kurssi on jo päättynyt.

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. Metodien räätälöinti ilmentymäkohtaisesti. 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.

Oheismoduulit: Oliointro, Odds (uusi).

../_images/person06.png

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 (Scalassa new-käskyllä), 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"
}
Yksittäisolion määrittely alkaa sanalla object, luokan sanalla class.
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"
}

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 välittömästi luokkamäärittelyn rajaavien aaltosulkeiden 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 new-käskyn annettuaan, kuten aiemmissa esimerkeissä on tehty.

Pikkutehtäviä ilmentymämuuttujista

class Lemmikki(annettuNimi: String, annettuLaji: String) {

  val laji = annettuLaji
  var nimi = annettuNimi

  // tänne metodeita

}

Tarkastele tuota pikkuluokkaa ja erityisesti sen riviä val laji = annettuLaji. Pohdi myös aiemmin näkemääsi animaatiota työntekijäolioiden luomisesta. Mitkä seuraavista väitteistä pitävät paikkansa?

Oletetaan suoritetuiksi seuraavat käskyt:

import o1.luokkia.Tyontekija
val a = new Tyontekija("Eugenia Enkeli", 1965, 5000)
val b = new Tyontekija("Teija Tonkeli", a.syntynyt, a.kkpalkka - 1000)

Mitkä seuraavista väitteistä pitävät paikkansa?

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 sellaiset "kopiointisijoitukset", joita esimerkkiluokissamme esiintyy, ovat yleisiä, ja on totta, että ne olisivat ilmaistavissa yksinkertaisemminkin. Niinpä 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

}
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

}

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 = new 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.)

}

Tämä tapa on kuitenkin 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

Täydennetään suorakaideluokkaa kuvauksen muodostavalla metodilla mitat. Kuvaus on muotoa "X kertaa Y". Alla on neljä yritystä. Mitkä niistä toimivat?

Voit halutessasi kokeilla mitä tahansa näistä toteutuksista lisäämällä sen suorakaideluokkaasi.

def mitat = this.sivu1 + " kertaa " + this.sivu2
def mitat = s"$this.sivu1 kertaa $this.sivu2"
def mitat = s"$sivu1 kertaa $sivu2"
def mitat = s"${this.sivu1} kertaa ${this.sivu2}"

Seuraavista kuudesta väitteestä neljä pitää paikkansa. Mitkä?

Kokoava pikkuharjoitus: tililuokka

Ota esiin luvussa 2.2 laatimasi tiliolion määrittely. Pohdi, miten tämän koodin voisi muokata tililuokaksi, josta voi instantioida erilaisia tiliolioita. Kukin näistä olioista toimisi samaan tapaan kuin se yksittäisolio, jonka aiemmin toteutit.

Mitkä seuraavista ideoista ovat mielekkäitä? Voit myös toteuttaa tarvittavat muutokset olioon, jos haluat.

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, ja
  • palauttaa suorakaiteen kuvan (tyyppiä Pic), jossa leveytenä on sivu1, korkeutena sivu2 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 = new 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:

class Hirvio(val tyyppi: String, val taysiKunto: Int) {

  var nykykunto = taysiKunto

  val kuvaus = this.tyyppi + " (" + this.nykykunto + "/" + this.taysiKunto + ")"

  def vahingoitu(paljonko: Int) = {
    this.nykykunto = this.nykykunto - paljonko
  }

}

Tämäkin koodi löytyy moduulista Oliointro; voit kokeilla luokan käyttöä REPLissä.

Perehdy koodiin huolellisesti. Mitkä seuraavista väitteistä pitävät paikkansa?

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 = new 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 = new 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 = new 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 ja will. 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 kuitenkin katsoa, miten se on tehty.
    • Huomaa 1.0:lla kertominen, jotta jakolaskun tulos saadaan desimaalilukuna; ilman tulisi käytännössä aina nolla (vrt. luku 1.3). Jos tämä tuntuu ihmeelliseltä kikkailulta, niin lohduttaudu sillä, että luvussa 5.2 vastaan tulee nätimpikin tapa tehdä sama asia.
  • 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 palautetaan 6/2 eikä 3/1.
    • Jos merkkijonon muodostamisessa tulee vaikeuksia, muista suorakaidetehtävän kuvaus-metodi.
  • Osaatko toteuttaa decimal ja winnings-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.

Ilmentymän metodien räätälöintiä

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.")

}
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 = new 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 = new Henkilo("Lois")toka: Henkilo = Henkilo@645b797d
toka.reagoiKryptoniittiinres24: String = Lois: Onpa kumma mineraali.

Kaikki Henkilot 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

val terasmies = new Henkilo("Clark") {
  def lenna = "WOOSH!"
}terasmies: Henkilo{def lenna: String} = $anon$1@25ba32e0
Luodaan henkilö tavalliseen tapaan paitsi, että...
... määritellään tälle henkilöoliolle lennosta uusi metodi.

Teräsmies osaa lentää mutta myös kertoa nimensä ja reagoida asioihin kuten muutkin henkilöt.

terasmies.lennares25: String = WOOSH!
terasmies.nimires26: String = Clark
terasmies.reagoiKryptoniittiinres27: String = Clark: Onpa kumma mineraali.

Saimme räätälöityä tietylle ilmentymälle uuden metodin. Entä luokalle yleisesti kirjoitetun metodimäärittelyn muokkaaminen ilmentymäkohtaisesti?

Metodin korvaaminen

Vihreä kryptoniitti on Teräsmiehen heikkous. Jos haluamme kuvata hänet Henkilo-luokkaa käyttäen, voimmeko kuitenkin huomioida tämän seikan? Meidän pitäisi siis saada teräsmiestä kuvaava Henkilo-olio poikkeamaan tältä osin siitä, miten muut saman luokan ilmentymät käyttäytyvät.

val realistinenTerasmies = new Henkilo("Clark") {
  def lenna = "WOOSH!"
  override def reagoiKryptoniittiin = "GARRRRGH!"
}realistinenTerasmies: Henkilo{def lenna: String} = $anon$1@47bd09ef
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, Jaakko Kantojärvi, Niklas Kröger, Teemu Lehtinen, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä 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 tällä hetkellä 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 ovat luoneet Nikolai Denissov, Olli Kiljunen, Nikolas Drosdek, Styliani Tsovou, Jaakko Närhi ja Paweł Stróżański yhteistyössä Juha Sorvan, Otto Seppälän, Arto Hellaksen ja muiden kanssa.

Kurssin tämänhetkinen henkilökunta löytyy luvusta 1.1.

Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.

a drop of ink
Palautusta lähetetään...