Luku 2.2: Olion sisällä

../_images/person06.png

Johdanto

Tässä luvussa määritellään yksittäisiä olioita Scala-koodissa. Osoittautuu, että se vaatii taitoa ja tekniikkaa, jota meillä on ennestään. Olion määrittelyyn sisältyy nimittäin kahdenlaisia asioita:

  • Olion toiminnot kuvataan metodeina eli olioon liitettyinä funktioina. Näiden funktioiden määritteleminen onnistuu aiemmista luvuista tuttuun tapaan.

  • Olio tarvitsee tavan, jolla se pitää kirjaa tiedoistaan. Tähän sopii toinen vanhastaan tuttu tekniikka eli muuttujat.

Uutta seuraavassa on lähinnä se, miten funktioita ja muuttujia liitetään olioon.

Tavoite: työntekijäolio

Laaditaan yksittäisolio, joka kuvaa erästä yksittäistä työntekijää kuvitteellisessa kirjanpitojärjestelmässä.

Katsotaan ensin REPL-esimerkki siitä, miten haluaisimme tämän olion toimivan.

Työntekijällä on ominaisuuksia kuten nimi ja kuukausipalkka. Olion ohjelmakoodissa on määritelty näille ominaisuuksille arvot. Arvoja voi tiedustella oliolta tässä esitettyyn tapaan:

tyontekija.nimires0: String = Matti Mikälienen
tyontekija.kkpalkkares1: Double = 5000.0

Vaikutukselliselle korotaPalkkaa-metodille annetaan parametriksi luku, jolla vanha palkka kerrotaan. Tässä siis annetaan 10 %:n palkankorotus:

tyontekija.korotaPalkkaa(1.1)

Todennetaan, että edellinen käsky todella muutti olion tilaa:

tyontekija.kkpalkkares2: Double = 5500.0

Vaikutukseton ikaVuonna-metodi kertoo paluuarvollaan, kuinka monta vuotta työntekijä täyttää annettuna vuonna. Työntekijäolio tietää oman syntymävuotensa ja osaa sen perusteella laskea ja ilmoittaa ikänsä:

tyontekija.ikaVuonna(2024)res3: Int = 59

Työntekijäoliolle on määritelty työaika desimaalilukuna. Arvo 1.0 tarkoittaa täyspäiväisyyttä (100 %:n työaika):

tyontekija.tyoaikares4: Double = 1.0

Työaikaa voi muuttaa yksinkertaisesti sijoituskäskyllä (vrt. radioesimerkki luvusta 2.1), joka lähettää oliolle viestin: "Aseta työajaksesi 0.6." Arvo 0.6 tarkoittaa osa-aikaisuutta; Matti Mikälienen asetetaan tekemään 60-prosenttista työkuukautta:

tyontekija.tyoaika = 0.6tyontekija.tyoaika: Double = 0.6

Vaikutukseton kuukausikulut-metodi laskee ja palauttaa työntekijän kuukausittaisen hinnan työnantajalleen. Hinta lasketaan tässä esimerkissä kuukausipalkan (nyt 5500 euroa), työajan (nyt 60 %) ja sivukulukertoimen (parametriarvo 1.3) tulona:

tyontekija.kuukausikulut(1.3)res5: Double = 4290.0

Vaikutukseton kuvaus-metodi palauttaa merkkijonokuvauksen eräistä työntekijäolion keskeisistä tiedoista. Metodi on parametriton, eikä kutsussa ole sulkeita nimen kuvaus perässä (kuten ei yllä nimeä ja kuukausipalkkaa kysyessäkään ollut). Tässä kuvaus ensin pyydetään oliolta ja sitten tulostetaan:

println(tyontekija.kuvaus)Matti Mikälienen (s. 1965), palkka 0.6 * 5500.0 euroa

Olion ohjelmakoodi ja metodien sisäinen toiminta

Työntekijäolion toteutus: yleiskuva

Alla on työntekijäolion määrittelevä ohjelmakoodi, joka löytyy myös Oliointro-moduulin tiedostosta tyontekija.scala. Muodostetaan siitä ensin kokonaiskuva ja tutkitaan vasta sitten metodien yksityiskohtia.

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. " + this.syntynyt + "), palkka " + this.tyoaika + " * " + this.kkpalkka + " euroa"

end tyontekija

Yksittäisen olion määrittely alkaa sanalla object. Sen perään kirjoitetaan ohjelmoijan valitsema nimi, jolla olioon voi viitata. Ja sen perään kaksoispiste.

Koko olion loppuun voi vapaaehtoisesti kirjoittaa end-alkuisen loppumerkin, mikä onkin tapana, ellei olion koodi ole erityisen yksinkertainen ja lyhyt. Kirjoitamme siis sen.

Alku- ja loppumerkintöjen välissä on määritelty ensin olion muuttujat. Määrittelyt ovat tutun näköisiä. Olion muuttujille sijoitetaan tässä (alku)arvot. Oliolle määritellyn val-muuttujan arvon voi vain "katsoa" ilmaisulla kuten tyontekija.syntynyt, mutta var-muuttujalle voi myös sijoittaa uuden arvon kuten yllä tehtiin REPLissä työajalle.

Alempana on määritelty metodit def-sanalla alkaen. Scalassa ei ole sääntöä, joka määräisi kirjoittamaan ensin muuttujat ja sitten metodit, mutta tämä on melko yleinen tapa. Metodeista lisää alla.

Sisennykset ovat tärkeät myös olion määrittelyssä. Kukin olion sisään määrittellyistä muuttujista ja metodeista on sisennetty alku- ja loppurivejä syvemmälle. Monirivisten metodien runkoja on sisennetty vielä pykälän verran lisää tästä.

Työntekijäolion sisäinen toimintatapa

Työntekijäolion toteutus: metodien koodi selitettynä

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. " + this.syntynyt + "), palkka " + this.tyoaika + " * " + this.kkpalkka + " euroa"

Scalan sana this tarkoittaa: "se olio, jonka metodia ollaan suorittamassa". Tai olion näkökulmasta: "minä itse". Sanaa voi käyttää pisteen kanssa viittaamaan olion osiin. Kuten äskeisestä animaatiosta näkyy, käytännössä this on parametrimuuttuja, joka toimii kuin muutkin parametrimuuttujat. Se saa arvonsa "metodikutsusta pisteen edestä".

Tässä siis määritellään oliolle: "Kun ikaVuonna-metodiasi kutsutaan, vastaa luvulla, jonka saat vähentämällä parametriksi annetusta vuodesta oman syntynyt-muuttujasi arvon."

"Kerro keskenään oma kuukausipalkkasi, oma työaikasi ja (parametrina paikallisessa muuttujassa oleva) kulukerroin."

"Kerro oma kuukausipalkkasi annetulla kertoimella ja sijoita tulos uudeksi omaksi kuukausipalkaksesi."

Metodeissakin noudatetaan luvussa 1.7 esitettyjä funktioiden muotoilusääntöjä. Muista yhtäsuuruusmerkit.

kuvaus-metodi ei ota parametreja, eikä sille ole edes määritelty parametriluetteloa.

this-sanan käytöstä tarkemmin

this-sanaa käytetään tällä kurssilla jotakuinkin aina, kun viitataan metodia suorittavan olion omiin jäseniin. Sana ei kuitenkaan usein ole teknisessä mielessä pakollinen.

Joskus this on pakollinen. Se käy ilmi tästä työntekijäolion versiosta, jossa on aiempaan verrattuna erilaista vain yhden muuttujan nimi:

object tyontekija:

  var nimi = "Matti Mikälienen"
  val vuosi = 1965
  var kkpalkka = 5000.0
  var tyoaika = 1.0

  def ikaVuonna(vuosi: Int) = vuosi - this.vuosi
  // jne.

Tässä versiossa syntymävuosi on tallennettu muuttujaan, jonka nimi on vuosi eikä syntynyt, kuten alkuperäisessä.

Olion muuttuja on siis samanniminen kuin ikaVuonna-metodin parametri.

Ilman this-sanaa kirjoitettu vuosi tarkoittaa parametrina annettua vuotta.

this kertoo, että tässä viitataan nimenomaan olion muuttujaan eikä parametriin. Muutoin tämä metodi vähentäisi parametrimuuttujansa arvon siitä itsestään ja palauttaisi siis aina nolla.

Alkuperäisessä esimerkissämme syntymävuosi oli syntynyt-muuttujassa. Siinä metodeista olisi voinut jättää this-sanan pisteineen joka kohdasta poiskin, koska mikään paikallisista muuttujista ei ollut olion muuttujien kanssa saman niminen.

Silloinkin, kun se ei ole pakollista, this-sanan käyttö voi olla perusteltua. Sana korostaa lukijalle sitä, missä kohdissa käytetään olion muuttujia ja missä paikallisia. Suosittelemme kaikille kurssilaisille this-sanan käyttöä aina olion muuttujiin viitatessa, sillä se selkeyttää ohjelmia.

Kyseessä on osin makuasia. Jos haluat — ja jos tiedät mitä teet — niin saat kyllä kurssillakin jättää ei-välttämättömät this-sanat pois. Kurssimme ulkopuolella on (valitettavasti?) varsin yleistä jättää this-sana pois kun sallittua.

Tarkkana sisennysten ja välimerkkien kanssa

Oliota määritellessäkin on välimerkit saatava kuntoon, ja sisennysten kanssa on oltava huolellinen, kun koodin rakenteet ovat sisäkkäiset. Kohta pääset kirjoittamaan itse oman olion, mutta pohjustetaan sitä vielä pienellä tehtävällä, jossa arvioit, toimivatko annetut oliomäärittelyt vai onko niissä virhe.

Kussakin kohdassa on annettu testiolio-nimisen olion määrittely — tai ainakin yritelmä sellaisesta. Tämä olio ei tee mitään kiinnostavaa; keskitymme tässä muotoseikkoihin.

Testioliolla tulisi olla laskeTulos-niminen metodi, joka ottaa luvun ja palauttaa sen perusteella laskemansa toisen luvun näin:

testiolio.laskeTulos(100)res6: Int = 204

Sillä pitäisi olla myös monista-niminen metodi, joka ottaa merkkijonon ja palauttaa sen moninkertaisena:

testiolio.monista("Jee")res7: String = JeeJeeJeeJee

Vastaa kussakin kohdassa annettua koodia parhaiten kuvaava vaihtoehto. (Yhdessä kohdassa oikeita vastauksia on kaksikin. Valitse kumpi vain niistä.)

object testiolio:

  val perusluku = 4

  def laskeTulos(luku: Int) = this.perusluku + (luku * 2)

  def monista(jono: String) = jono * this.perusluku

end testiolio

Syntyykö näin olio, jonka metodit toimivat REPL-esimerkin mukaisesti?

object testiolio:

  val perusluku = 4

  def laskeTulos(luku: Int) =
    val tupla = luku * 2
    this.perusluku + tupla

  def monista(jono: String) =
    jono * this.perusluku

end testiolio

Syntyykö näin olio, jonka metodit toimivat REPL-esimerkin mukaisesti?

object testiolio:

  val perusluku = 4

  def laskeTulos(luku: Int) =
  val tupla = luku * 2
  this.perusluku + tupla

  def monista(jono: String) = jono * this.perusluku

end testiolio

Syntyykö näin olio, jonka metodit toimivat REPL-esimerkin mukaisesti?

object testiolio:

  val perusluku = 4

  def laskeTulos(luku: Int) =
    val tupla = luku * 2
    this.perusluku + tupla

    def monista(jono: String) = jono * this.perusluku

end testiolio

Syntyykö näin olio, jonka metodit toimivat REPL-esimerkin mukaisesti?

object testiolio

  val perusluku = 4

  def laskeTulos(luku: Int) = this.perusluku + (luku * 2)

  def monista(jono: String) = jono * this.perusluku

end testiolio

Syntyykö näin olio, jonka metodit toimivat REPL-esimerkin mukaisesti?

Laadi itse olio

Laadi nyt itse olio, joka kuvaa yhtä pankkitiliä. Olion on toimittava seuraavan esimerkin mukaisesti.

Tiliolio: käyttöesimerkki

Tilillä on saldo (sentteinä) ja tilinumero, jotka voi kysyä siltä:

tili.saldores8: Int = 0
tili.numerores9: String = 15903000000776FI00

Tilille voi tallettaa rahaa. Tässä kuvaamme rahasummia kokonaislukuina, jotka vastaavat eurosenttien määriä. Lisätään tilille 200 euroa ja katsotaan taas saldo:

tili.talleta(20000)tili.saldores10: Int = 20000

Negatiivisen summan tallentaminen ei muuta saldoa:

tili.talleta(-1000)tili.saldores11: Int = 20000

nosta-metodi vähentää tililtä rahaa ja palauttaa onnistuneen vähennyksen määrän:

tili.nosta(5000)res12: Int = 5000
tili.saldores13: Int = 15000

Alle nollan ei voi vähentää. Tässä saadaan vain 150,00 euroa ja tyhjennetään tili:

tili.nosta(50000)res14: Int = 15000
tili.saldores15: Int = 0

Tehtävänanto

Määrittele siis Scalalla olio tili, joka toimii niin kuin yllä on kuvattu ja jolla on seuraavat piirteet:

  • Sillä on muuttumaton numero "15903000000776FI00".

  • Sillä on saldo, joka on aluksi nolla mutta joka voi muuttua.

  • Sillä on vaikutuksellinen metodi talleta, joka lisää parametriarvon verran rahaa tilille (kunhan parametriarvo on positiivinen) eikä palauta mitään.

  • Sillä on vaikutuksellinen metodi nosta, joka vähentää tililtä parametriarvon verran, jos mahdollista, tai tyhjentää tilin, jos parametriarvo on saldoa suurempi.

  • nosta-metodi paitsi vähentää tililtä rahaa myös palauttaa onnistuneesti nostetetun määrän kuten REPL-esimerkissä yllä.

  • (Tässä tehtävässä sinun ei ole pakko huomioida mahdollisuutta, että nosta-metodin parametriarvo olisi negatiivinen. Kuitenkin jos haluat, voit laatia metodin sellaiseksi, että se negatiivisella parametriarvolla se jättää saldon koskemattomaksi.)

Kirjoita koodi Oliointro-moduulin tiedostoon o1/yksittaisia/tili.scala merkittyyn kohtaan.

Toimintaohje

Suositellut työvaiheet:

  1. Katso käyttöesimerkki yltä huolella. Huomaa muuttujien nimet ja tietotyypit! Ei tilinumero vaan numero ja saldon tyypiksi Int.

  2. Etsi tili.scala-tiedostosta tiliolion määrittelyn alku ja kohta, johon olion tiedot kirjoitetaan.

  3. Kirjoita muuttujien määrittelyt ja muuttujille alkuarvot. Sisennä parilla lisävälilyönnillä.

  4. Testaa, toimiiko. Käynnistä REPL-sessio Oliointro-moduuliin ja kokeile lausekkeita tili.saldo ja tili.numero.

  5. Kirjoita talleta- ja nosta-metodit.

    • Käytä apuna aiemmista luvuista tuttuja min- ja max-funktioita.

    • nosta-metodissa tarvittavaan algoritmiin tutustuit jo luvun 1.8 sakko-tehtävässä.

  6. Nollaa taas REPL ja testaa metodeitasi erilaisilla parametriarvoilla. (Muistutus: voit myös käyttää REPLin vasemman yläkulman Rerun-toimintoa rerun.)

  7. Palauta, kun olet vakuuttunut oliosi toimivan spesifikaation mukaisesti.

A+ esittää tässä kohdassa tehtävän palautuslomakkeen.

Mitä vikaa?

Kun olet tehnyt tehtävän, voit miettiä seuraavaa.

Teitkö pankkitilioliollesi saldo-nimisen var-muuttujan? Jos teit, niin hyvä niin. Kuitenkin saatoit jo tulla ajatelleeksi pientä kummallisuutta. Tiliolion saldoahan olisi näemmä tarkoitus muuttaa talleta- ja nosta-metodeilla, jotka tekevät halutut tarkastukset: ei lisätä negatiivista määrää eikä sallita saldon menevän alle nollan. Kuitenkaan mikään ei estä meitä komentamasta oliota asettamaan saldonsa negatiiviseksi näin: tili.saldo = -100000

Tietenkään tämä ei haittaa mitään, jos tuollaista sijoitusta ei koskaan tehdä. Vääränlaisen sijoituksen voi tulkita oliota käyttävän ohjelmoijan virheeksi. Kuitenkin monien ohjelmoijien mielestä ohjelmat tulisi laatia niin, että tällaisten virheiden riski olisi mahdollisimman pieni. Opit pian (luku 3.2), miten ei-toivotun sijoituskäskyn voi estää, mutta ihan vielä siitä ei tarvitse välittää.

On olemassa ohjelmointityylejä, joissa ei luettaisi ongelmaksi sitä, että olio tarjoaa käyttäjälleen sopimattomiakin käskyjä. Scala-ohjelmoijat tapaavat kuitenkin kuulua siihen joukkoon, jonka mielestä tällaisten virheiden mahdollisuuden tulee minimoida, jotta ohjelmoijan työ tehostuu ja saadaan luotettavampia, ammattimaisemmin laadittuja ohjelmistoja.

Yhteenvetoa

  • Osa Scala-ohjelman olioista on ns. yksittäisolioita eli muista olioista irrallisina määriteltyjä olioita.

  • Yksittäisolion määrittely koostuu muuttujien määrittelyistä ja metodien määrittelyistä.

    • Olion tietoihin voi määritellä muuttujan ja sille arvon aivan aiempien lukujen mukaisesti.

    • Samoin metodien määrittelyt ovat aivan vastaavia kuin jo tutuksi tulleiden funktioiden.

  • On erittäin yleistä, että metodin toteutuksessa on tarve viitata johonkin samaisen olion (siis metodia suorittavan olion) muuttujaan tai toiseen metodiin. Tähän voi Scalassa käyttää this-sanaa.

  • Lukuun liittyviä termejä sanastosivulla: olio, yksittäisolio; this.

Päivitetty käsitekaavio:

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, Onni Komulainen, Niklas Kröger, Kalle Laitinen, Teemu Lehtinen, Mikael Lenander, Ilona Ma, Jaakko Nakaza, Strasdosky Otewa, Timi Seppälä, Teemu Sirkiä, Joel Toppinen, 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, Juha Sorva ja Jaakko Nakaza. 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; sitä 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.

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