Kurssin viimeisimmän version löydät täältä: O1: 2024
Luku 2.3: Luokat olioiden tyyppeinä
Tästä sivusta:
Pääkysymyksiä: Mikä on järkevä tapa kuvata useita keskenään
samankaltaisia olioita? Miten luon uuden olion, joka edustaa
tiettyä oliotyyppiä? Mitä kaikkea Pic
-tyyppisillä kuvilla
voi tehdä?
Mitä käsitellään? Luokat: oliot luokkien ilmentyminä, luokat
tietotyyppeinä, instantiointi, new
-operaattori. Pic
-olioiden
metodeita. Kaadamme taas päällesi röykkiöllisen uusia käsitteitä
ja termejä, mutta lupaamme, että niistä on hyötyä.
Mitä tehdään? Luetaan ja tehdään pieniä ohjelmointiharjoituksia.
Suuntaa antava työläysarvio:? Puolitoista tuntia?
Pistearvo: A50.
Oheisprojektit: Oliointro. Lopussa palataan lyhyesti Aliohjelmia-projektiin.
Luokat
Kun ihminen näkee tuolin, hän kokee sen sekä kirjaimellisesti "juuri tuona tiettynä asiana" että abstraktisti "tuona tuolimaisena asiana". Abstraktio syntyy ihmismielen ihmeellisestä kyvystä sulauttaa samankaltaisia kokemuksia toisiinsa. Se ilmenee mielessä platonilaisena tuolin ideana eli tuoliutena.
—Dan Ingalls (käännetty englanninkielisestä lähteestä)
Kuvittele tilanne, jossa täytät paperille vaikkapa opintotukihakemusta. Et tietenkään aloita kirjoittamaan tyhjälle arkille, vaan käytät valmiiksi suunniteltua lomaketta, joka kertoo, mitä asioita hakemukseen kuuluu kirjata. Olisi hyvin epäkäytännöllistä, että jokainen hakija kirjoittaisi lomakkeenkin itse, kun tarvittavat tiedot ovat kuitenkin kaikille samat. On parempi käyttää määrättyä lomaketyyppiä, josta voi luoda mielivaltaisen määrän kopioita täytettäviksi.
Palauta seuraavaksi mieleen luvussa 2.1 näkemäsi esitykset GoodStuff-ohjelman ja kuvitteellisen kurssi-ilmoittautumissovelluksen toiminnasta. Niissähän esiintyi lukuisia keskenään samankaltaisia olioita: monta kurssia, monta opiskelijaa ja monta kokemusoliota. Keskenään samankaltaisilla olioilla oli samantyyppiset tiedot ja samantyyppinen toimintatapa; esimerkiksi jokainen kurssiolio piti kirjaa kurssikoodista, opetuspaikasta ja ilmoittautuneista opiskelijoista.
Aivan kuin opintotukilomakkeita täyttäessä, on myös olioiden tapauksessa aivan tarpeetonta ja epäkäytännöllistä, että jokainen olio määriteltäisiin aina uutena yksittäistapauksena, kuten olemme tähän mennessä tehneet.
Pelkkien yksittäisolioiden käytössä on muitakin haasteita. Ohjelmoija kirjaa kunkin yksittäisolion tiedot ohjelmakoodiin erikseen, mutta tietääkö hän ohjelmaa kirjoittaessaan täsmälleen, kuinka paljon ja millaisia olioita ohjelman eri suorituskertojen aikana tullaan tarvitsemaan? Yleensä ei. Tarvittavien olioiden lukumäärä ja niiden muuttujien arvot määräytyvät usein vasta dynaamisesti, ohjelmaa ajettaessa, ja ne voivat riippua vaikkapa käyttäjän antamasta syötteestä. Esimerkiksi GoodStuff-sovelluksessa käyttäjä syöttää kirjattavien kokemusten tiedot.
Ohjelmoija voi sen sijaan kyllä määritellä etukäteen, minkälaisia olioita ohjelman ajon aikana tullaan tarvitsemaan eli minkä tyyppisistä tiedoista ohjelman on pidettävä kirjaa ja mitä toimintoja näihin tietoihin liittyy.
Tässä luvussa tutustumme olioiden tyyppeihin eli luokkiin (class) ja siihen, miten olioita luodaan luokkien kautta. Tällä tavoin määritellyt oliot ovat paljon yksittäisolioita yleisempiä.
Edetään tutun kaavan mukaan: Tässä luvussa koekäytät valmiiksi määriteltyjä luokkia. Sitten tulevissa luvuissa perehdyt siihen, miten luokkien ohjelmakoodi kirjoitetaan.
Oliot luokkien ilmentyminä
Seuraava esitys kuvaa luokkien ja olioiden välistä suhdetta lomakevertauskuvalla:
Perusajatus on siis se, että ohjelmoijina määrittelemme:
- luokkia ("lomaketyyppejä"),
- sen, missä yhteyksissä luokkien kuvaaman kaltaisia olioita luodaan ("milloin otetaan uusi kopio lomakepohjasta täytettäväksi"; esim. kun käyttäjä syöttää uuden kokemuksen tiedot), ja
- sen, mistä olioiden muuttujille tällöin saadaan arvot ("lomakkeen sisältö").
Jäljempänä käytämme tästä aiheesta usein seuraavia teknisempiä termejä. Paina ne mieleen.
- Olion suhdetta luokkaan kuvataan sanomalla, että olio on luokan ilmentymä eli instanssi (instance) eli tietty yksittäistapaus luokasta.
- Luokka on olion tietotyyppi (tai vain tyyppi; (data) type).
- Uuden ilmentymän — siis olion — luomista luokkamäärittelyn perusteella sanotaan instantioinniksi (instantiation).
Luokan käyttö Scalalla: new
Tarkastellaan luokkaa Tyontekija
, joka kuvaa täsmälleen sen kaltaisia työntekijäolioita,
jollaisen loimme yksittäisoliona edellisessä luvussa. Ainoa mutta merkittävä ero
silloiseen on, että käytämme nyt yksittäisen olion sijaan tietotyyppiä, joka kuvaa
työntekijän käsitettä yleisesti. Sen avulla voimme luoda erinimisiä ja -palkkaisia
työntekijäolioita.
Kun käytössä on valmiiksi määritelty luokka, siitä voi luoda ilmentymän helposti. Alla on esimerkki työntekijäluokan käyttämisestä. Kokeile myös itse; käytettävä koodi on projektissa Oliointro.
Luodaan ensin yksi uusi työntekijä:
import o1.luokkia._import o1.luokkia._ new Tyontekija("Eugenia Enkeli", 1963, 5500)res0: o1.luokkia.Tyontekija = o1.luokkia.Tyontekija@1145e21
Tyontekija
-luokassa on määritelty, että työntekijäoliota
alustettaessa on annettava nimi, syntymävuosi ja palkka.
(Uusi työntekijä on oletusarvoisesti täyspäiväinen, eli työaika
on 1.0.) Se, millainen olio tulee luoduksi, riippuu luokan
ohjelmakoodista ja näistä parametriarvoista.new
-sanalla muodostettu instantiointikäsky on lauseke.
Tietokone evaluoi sen varaamalla olion tiedoille muistista tilaa
ja alustamalla olion luokassa määritellyllä tavalla.Tyontekija
.Erittäin usein on kätevää sijoittaa johonkin muuttujaan viittaus juuri luotuun olioon, kuten alla. Tässä luodaan saman luokan toinen ilmentymä ja sijoitetaan muuttujaan viittaus uuteen ilmentymään:
val juuriPalkattu = new Tyontekija("Teija Tonkeli", 1985, 3000)juuriPalkattu: o1.luokkia.Tyontekija = o1.luokkia.Tyontekija@704234
Nyt muuttujan nimeä voi hyödyntää vaikkapa olion metodeita kutsuessa:
println(juuriPalkattu.kuvaus)Teija Tonkeli (s. 1985), palkka 1.0 * 3000.0 euroa
Huomasithan, että tässä luotiin kaksi kokonaan erillistä työntekijäoliota? Niillä on eri tiedot, mutta niille on kuitenkin yhteistä muun muassa se, että niillä on jotkin nimet, syntymävuodet ja kuukausipalkat.
Ilmentymistä, muuttujista ja viittauksista
Katsotaan pari lyhyttä animaatiota.
Jo edellinen animaatio näytti, että olioita käsitellään viittausten kautta kuten puskureitakin luvussa 1.5 käsiteltiin. Seuraava animoitu esimerkki korostaa viittausten merkitystä.
Tee animaation alussa pyydetty ennustus ja katso animaatio huolellisesti.
Viittaukset olioihin, viittaukset puskureihin
Ei ole sattumaa, että olioihin viitataan samalla tavalla kuin luvussa 1.5 käsiteltyihin puskureihin. Puskuritkin nimittäin ovat olioita. Tästä lisää luvussa 4.1.
Pikkutehtäviä olioista, luokista ja viittauksista
Vaikka vähän toiston puolelle jo meneekin, niin kerrataan vielä pienillä kysymyksillä keskeisiä käsitteitä, joiden hahmottaminen on jatkon kannalta aivan ratkaisevaa.
Luokkien merkityksestä... tai merkityksistä
Luokat tietotyyppeinä
"Tietotyyppi"-termi on putkahtanut esiin paitsi tässä luvussa myös ennen kuin mainitsimme
luokat, ja kyse on tosiaan aivan samasta asiasta. Esimerkiksi Tyontekija
-luokka on
työntekijäolioiden tietotyyppi ja GoodStuff-ohjelman luokka Experience
kokemusolioiden
tietotyyppi juuri samassa mielessä kuin Int
-tietotyyppi edustaa kokonaislukuja.
Ohjelmissa käytettäviä tietotyyppejä on monenlaisia:
- ohjelmoijan itsensä tai toisten saman hankkeen parissa
työskentelevien ohjelmoijien määrittelemät tietotyypit, jotka
liittyvät kiinteästi sovelluksen aihepiiriin (esim.
Tyontekija
taiExperience
), - kieleen kiinteästi kuuluvat tietotyypit (esim.
Int
,String
), ja - muut enemmän tai vähemmän yleishyödylliset tietotyypit, jotka on
otettu käyttöön jostakin ohjelmakirjastosta. Ne voivat olla jonkin
kolmannen tahon laatimia. (Esim. puskurityyppi
Buffer
Scalan oheiskirjastossa taiPic
kurssimme kirjastossa.)
Yksittäisolioiden tietotyypit
Yksittäisolioillakin on tietotyyppinsä, kullakin ihan omansa. Kokeillaan vaikkapa luvun 2.1 papukaijalla:
papukaijares1: o1.olioita.papukaija.type = o1.olioita$papukaija$@4e0405c9
papukaija.type
.Kun määrittelet yksittäisolion, tulet samalla määritelleeksi sille oman tietotyypin. Yksittäisolioiden tietotyyppejä ei ole yleensä tarpeen nimenomaisesti koodissa käsitellä.
Itse asiassa
Int
-tietotyyppikin on (hieman erikoinen) luokka, mutta palataan
siihen asiaan luvussa 4.5.
Luokat käsitteellisen mallin osina
Luvussa 2.1 todettiin olio-ohjelmoinnin erääksi tavoitteeksi käsitteellisten mallien laatiminen. Luokat antavat hienoja uusia mahdollisuuksia käsitteelliseen mallintamiseen! Siinä missä oliot kuvaavat malliin kuuluvia yksittäisiä asioita (esim. tietyt työntekijät), luokat kuvaavat yleiskäsitteitä, joiden ilmentymiä nuo asiat ovat (esim. työntekijä käsitteenä). Olio-ohjelmoijan huomio usein kiinnittyykin luokkiin ja siihen miten luokat yhdessä kuvaavat yleisellä tasolla ne mahdolliset olioiden maailmat, joita syntyy ohjelma-ajojen aikana.
Yksittäisoliot vs. luokat
On itse asiassa melko harvinaista, että ohjelmointikielessä edes voi määritellä yksittäisolioita ohjelmakoodiin siihen tapaan kuin Scalassa. Monissa yleisesti käytetyissä olio-ohjelmointia tukevissa kielissä (esim. Java, C++, Python) oliot määritellään aina luokista instantioimalla. Niin Scalassakin useimmiten.
Eivät luokat tosin olio-ohjelmoinnin ehdoton edellytys ole. Joissakin kielissä (joista suosituimpana JavaScriptissä) olioita kloonataan toisista olioista, ja yksittäinen olio voi toimia toisten samankaltaisten prototyyppinä. Siitä lisää esimerkiksi Wikipediassa.
Luokat abstraktioina
Useassa aiemmassa luvussa on puhuttu abstraktioista, ja olemme jälleen kohdanneet uudenlaisen abstrahoinnin muodon. Luokat ovat abstraktioita — yleistyksiä — toisaalta olioista ja toisaalta mallinnettavan aihepiirin käsitteistä.
Luokat ohjelmakoodin osina
Scalalla kirjoitetun olio-ohjelman koodi koostuu luokkien ja yksittäisolioiden määrittelyistä.
On mahdollista kirjoittaa usean luokan määrittelyt samaan kooditiedostoon. Tätä on kuitenkin Scala-ohjelmissa tapana välttää, elleivät luokat liity toisiinsa poikkeuksellisen kiinteästi. Tälläkin kurssilla pääsääntöisesti määrittelet kunkin luokan omaan Scala-kooditiedostoonsa.
On yleinen käytäntö nimetä kooditiedosto ja sen sisältämä luokka tai yksittäisolio
samoin. Esimerkiksi luokka Tyontekija
määritellään tiedostossa nimeltä
Tyontekija.scala
, jonka sisältöön tutustumme seuraavassa luvussa.
Luokista ja metodeista
Huomasithan aiempaa työntekijäanimaatiota katsoessasi, että olioiden tiedot on tallennettu erilleen luokan tiedoista? Ja että metodit löytyvät nimenomaan luokan yhteydestä eivätkä olioiden?
Luokan tiedot ovat kaikille sentyyppisille olioille yhteiset, eikä niitä tarvitse kopioida jokaiseen olioon erikseen. Kussakin oliossa on tallessa tieto siitä, minkä tyyppinen se on. Näin päästään käsiksi olion metodeihin tarvittaessa.
Luokka määrää sen ilmentymillä olevat metodit ja siis kyseisentyyppisten olioiden
käyttäytymismallin. Esimerkiksi kaikilla työntekijäolioilla on kuukausikulut
- ja
kuvaus
-metodit. Kukin olioista vastaa samaan tapaan, kun noita metodeita kutsutaan.
Jokainen olio kuitenkin nojaa omiin oliokohtaisiin tilatietoihinsa.
Olioiden rajoituksista
Luokan ilmentymää voi komentaa tekemään vain niitä tiettyjä asioita, joita se osaa, eli
joita sen edustamaan luokkaan on määritelty. Oikeannimisen metodin täytyy olla olemassa,
ja parametrien täytyy olla täsmälleen halutunlaiset. Kaikki seuraavat yritykset komentaa
Tyontekija
-tyyppistä oliota epäonnistuvat:
var esimerkki = new Tyontekija("Matti Mikälienen", 1965, 5000)res2: o1.luokkia.Tyontekija = o1.luokkia.Tyontekija@177e207 esimerkki.syoKaalikeittoaJoutuin("nam nam")<console>:12: error: value syoKaalikeittoaJoutuin is not a member of o1.luokkia.Tyontekija esimerkki.syoKaalikeittoaJoutuin("nam nam") ^ esimerkki.ikaVuonna(2014, 2018)<console>:12: error: too many arguments for method ikaVuonna: (vuosi: Int)Int esimerkki.ikaVuonna(2014, 2018) ^ esimerkki.ikaVuonna("2018")<console>:12: error: type mismatch; found : String("2018") required: Int esimerkki.ikaVuonna("2018") ^ esimerkki.ikaVuonna<console>:12: error: missing arguments for method ikaVuonna in class Tyontekija; ... esimerkki.ikaVuonna ^
Luokankäyttöharjoitus
Oliointro-projektin o1.luokkia
-pakkauksessa on Tyontekija
-luokan lisäksi
muutakin. Luokka Puhelinlasku
kuvaa (toki taas yksinkertaistetusti) telefirman asiakkaiden
puhelinlaskuja; yksi sen ilmentymä on tietyn asiakkaan lasku. Yhteen puhelinlaskuun
liittyy jokin määrä puheluita (nolla tai yli), joista kutakin kuvataan yhtenä
Puhelu
-oliona eli Puhelu
-luokan ilmentymänä.
Harjoittele näiden luokkien käyttöä REPLissä alla olevien ohjeiden mukaisesti. Kumpikin luokka on annettu valmiina; sinun ei tässä tehtävässä ole tarkoitus laatia niitä tai mitään muitakaan luokkia itse.
Jos tämä tehtävä tuntuu vaikealta, saattaa olla hyödyksi poiketa ensin hieman alempana olevassa Sudenkuoppia-osiossa ja palata sitten tehtävän pariin.
Puhelu
-luokka
Eräitä luokan Puhelu
piirteitä tarkemmin:
Puhelu
-oliota luotaessa on annettava konstruktoriparametreiksi: puhelun alkuhinta, puhelun minuuttihinta sekä kesto minuutteina. Kaikki nämä ovat desimaalilukuja, hinnat ovat euroissa.Puhelu
-luokka määrittelee, että kultakin puheluoliolta voi tiedustella puhelun kokonaishintaa parametrittomallakokonaishinta
-metodilla. Puheluolio osaa laskea kokonaishintansa konstruktoriparametrien perusteella ja samalla lisätä siihen paikallisverkkomaksun.- Vastaavasti
Puhelu
-olioilta voi pyytää sanallisen selityksen puhelutiedoista parametrittomallakuvaus
-metodilla.
Voit kokeilla luokan käyttöä REPLissä myös vapaasti, mutta tee ainakin seuraavat asiat:
- Luo uusi puheluolio ja määrittele muuttuja, johon tallennat
viittauksen luomaasi olioon.
- Valitse muuttujan nimi itse.
- Laita puhelun alkuhinnaksi 0,99 euroa, minuuttihinnaksi 0,47 euroa ja kestoksi 7,5 minuuttia (käytä desimaalierottimena kuitenkin pistettä kuten Scalassa on tapana).
- Kutsu puheluolion
kokonaishinta
-metodia. Huomaa, että olio on lisännyt palautusarvoon myös paikallisverkkomaksun, joka on 0,99 senttiä plus 1,99 senttiä per minuutti. - Kutsu puheluolion
kuvaus
-metodia.
Puhelinlasku
-luokka
Eräitä luokan Puhelinlasku
piirteitä tarkemmin:
Puhelinlasku
-oliota luotaessa annetaan yksi konstruktoriparametri: asiakkaan nimi merkkijonona.- Laskuolioiden
lisaaPuhelu
-metodille annetaan parametriksi viittaus puheluolioon. Kun tätä vaikutuksellista metodia kutsutaan tietylle laskuoliolle, niin olio lisää kyseisen puhelun "itseensä" eli kyseiseen puhelinlaskuun. - Laskuolioiden
kokonaishinta
-metodi on parametriton ja palauttaa kaikkien laskuun lisättyjen puheluiden hintojen summan. - Myös
erittely
-metodi on parametriton. Se palauttaa monirivisen merkkijonon, jossa on kaikkien laskuun lisättyjen puheluiden hintatiedot.
Tee ensin seuraavat järjestyksessä ja kokeile sitten halutessasi muutakin:
- Luo yksi puhelinlaskuolio ja muuttuja, joka viittaa siihen. Valitse itse sekä asiakkaan nimi että muuttujan nimi.
- Lisää aiemman ohjeen mukaan luomasi puheluolio juuri luomaasi laskuun. Käytä puheluolioon viittaavan muuttujan nimeä parametrilausekkeena.
- Lisää vielä toinenkin puheluolio samaan laskuun:
- Laita puhelun alkuhinnaksi 1,2 euroa, minuuttihinnaksi 0,4 euroa ja kestoksi 30 minuuttia.
- Voit hoitaa puheluolion luomisen ja laskuun
lisäämisen näpsäkästi yhdellä rivillä: laita
new
-sanalla alkava olionluomiskäskylisaaPuhelu
-metodin parametrilausekkeeksi. Näin viittaus juuri luotuun olioon välittyy saman tien metodin parametriksi.
- Kutsu laskun
kokonaishinta
-metodia. - Kutsu laskun
erittely
-metodia. - Kirjoita kahdessa edellisessä kohdassa saamasi kokonaishintametodin ja erittelymetodin palautusarvot alla olevaan lomakkeeseen saadaksesi tehtäväpisteitä.
Päivitetty käsitekaavio
Sijoitetaan tässä välissä uudet käsitteemme kaavioon, joka on jatkoa luvun 2.2 vastaavalle.
Sudenkuoppia
Olio-ohjelmoinnin peruskäsitteisiin liittyy eräitä sudenkuoppia, joihin erittäin moni ohjelmoinnin vasta-alkaja on haksahtanut. Vältä sinä ne. Tässä luvussa asia on yritetty esittää siten, etteivät seuraavat virhekäsitykset pääsisi syntymään, mutta alleviivataan hieman vielä.
Luokat eivät ole oliosäiliöitä
On melko yleinen aloittelijan virhe ajatella, että luokat ovat jonkinlaisia olioiden ryhmiä tai varastoja, ja että oliot vastaavasti jollain tapaa sijoittuvat luokan sisään. Mutta: luokka on kuvaus tietynlaisten olioiden yhteispiirteistä eikä tällaisten olioiden säilömiseen tarkoitettu paikka. Tai aiempaa vertausta jatkaen: luokka on "lomaketyyppi" eikä "kansiollinen täytettyjä lomakkeita".
Käytännön merkitystä tällä on esimerkiksi sikäli, että luokan nimen kautta ei pääse käsiksi vaikkapa luetteloon sen "sisältämistä" olioista. Olioilla ei myöskään ole "luokan sisällä" (jossa ne eivät siis ole) mitään keskinäistä järjestystä, johon nojaten niitä voisi käsitellä. Olioihin viittaamiseen käytetään muuttujia.
On toki yleistä, että haluamme käsitellä useita toisiinsa liittyviä olioita ryhmänä. Silloin voimme panna oliot usean arvon kokoelmaan, esimerkiksi puskuriin. Myöhemmissä luvuissa teemme tätä usein.
Luokan ilmentymillä ei ole "nimiä"
Koodiesimerkkejä lukiessa saattaa syntyä se virheellinen vaikutelma, että ainakin joidenkin luokkien ilmentymiksi luoduilla olioilla olisi sisäänrakennettu nimi tai vastaava tunniste, jolla noihin olioihin voisi ohjelmakoodissa viitata. Esimerkiksi työntekijäluokasta luomillamme ilmentymillä oli eräänä ominaisuutena nimi, pankkitililuokan ilmentymillä voisi olla kullakin oma tilinumero, kurssiolioilla kurssikoodi ja niin edelleen.
Kuitenkaan mitään mainituista ei voi käyttää ohjelmakoodissa olioon viittaamiseen. Se,
että kirjoittaa työntekijän nimen pisteen eteen — "Matti Mikälienen".ikaVuonna(2018)
— toimii täsmälleen yhtä kehnosti kuin se, että kirjoittaisit nimen sijaan olion palkan
tai työajan. Nimi, palkka ja työaika ovat kaikki olion sisältämiä tietoja, mutta mistään
niistä ei ole apua olioon viittaamisessa sen ulkopuolelta. Tähän käytetään muuttujia ja
niihin tallennettuja viittauksia kuten yllä olevissa esimerkeissä.
Tältä osin luokkien ilmentymät eroavat yksittäisolioista, joille on ohjelmakoodissa
määritelty nimi, joka toimii juuri kyseisen olion tunnisteena. Olemme esimerkiksi
viitanneet yksittäisolioihin nimeltä tili
ja papukaija
määrittelemättä muuttujia,
joihin tallentaisimme viittauksen näihin olioihin.
Toki voimme haluta laatia ohjelman, joka etsii olioiden joukosta sen, jolla on tietty piirre, esimerkiksi tietty nimi tai kurssikoodi. Tämä on erillinen ongelma, johon löytyy erilaisia ratkaisuja. Niistä lisää muun muassa luvuissa 5.3 ja 8.4.
Olioon viittaava muuttuja ei ole olion nimi
var esimerkki = new Tyontekija("Matti Mikälienen", 1965, 5000)
Jos tätä Scala-koodiriviä ajattelee yhtenä kokonaisuutena, voi syntyä ajatus, että
rivillä luodaan Tyontekija
-tyyppinen olio, jonka nimeksi laitetaan esimerkki
.
Tulkinta ei pidä paikkaansa, vaan rivillä tehdään useita asioita: määritellään muuttuja,
luodaan olio luokan ilmentymäksi ja lopuksi sijoitetaan muuttujaan viittaus, joka
osoittaa luotuun olioon.
Aiemmista esimerkeistä olet jo nähnyt, että usea eri muuttuja voi samanaikaisesti
sisältää viittauksen samaan paikkaan muistissa (samaan olioon) ja että yksikin
var
-muuttuja voi viitata suorituksen eri vaiheissa eri olioihin. Niinpä on tärkeää
muistaa, ettei esimerkki
ole olion vaan muuttujan nimi. Asia käy konkreettisesti ilmi
kurssimateriaalin animaatioista.
Miksi ilmentymillä ei ole nimiä?
Mikään ilmentymän ominaisuuksista ei siis ole ilmentymään viittaamiseen kelpaava tunniste, eikä ilmentymään viittaavan muuttujan nimikään ole ilmentymän itsensä ominaisuus. Miksi muuttujat nimetään eikä luokkien ilmentymät?
Syihin lukeutuvat muun muassa seuraavat kaksi. Nämä syyt konkretisoituvat vähitellen, kun olio-ohjelmointikokemuksesi karttuu.
- Yhteen olioon voi viitata ohjelmassa useasta eri kohdasta ja sillä voi olla erilaisia "rooleja" eri kohdissa. On perusteltua viitata siihen eri nimillä eri kohdista. Vertaa reaalimaailman roolit: "presidentti", "kokouksen puheenjohtaja", "aviovaimoni", "äitini", "joku vastaantulija", "seuraava asiakas", "nettitilauksen tekijä" ja "lentomatkustaja" voivat kaikki viitata yhteen ja samaan kohteeseen, kun eri tahot tarkastelevat henkilöä eri näkökulmista.
- Usein halutaan, että tietyssä "roolissa" oleva olio voi vaihtua toiseen ohjelman suorituksen aikana. Silti halutaan yhdellä tietyllä nimellä päästä käsiksi siihen olioon, joka sillä hetkellä toimii kyseisessä roolissa. Yksi esimerkki on GoodStuff-ohjelman suosikkikokemus, joka voi vaihtua ohjelma-ajonaikana. (Ja vertaa taas reaalimaailmaan: esim. presidenttinä, seuraavana asiakkaana tai aviopuolisona oleva henkilö voi vaihtua.)
Pikkutehtävä: muuttujat ja olion tilaan vaikuttaminen
Lisää harjoitusta: kuvat olioina
Edellisellä kierroksella loimme kuvia Pic
-tietotyypin avulla. Pic
on itse asiassa
luokan nimi, ja kukin Pic
-tyyppinen arvo on olio, joka pitää sisällään tiedon siitä,
mitä kuvassa on.
Tehtaista
Aliohjelmia, joiden nimenomaisena tehtävänä on luoda uusi olio ja palauttaa viittaus siihen, voi sanoa “tehtaiksi” (factory). Lisää tehtaista luvussa 5.1.
Pic
-tyyppi sopii monenlaisten kuvien esittämiseen. Jotta erisorttisten kuvien luominen
olisi kätevää, o1
-pakkaukseen on määritelty useita eri funktioita, jotka luovat
Pic
-olioita. Esimerkkejä näistä ovat circle
- ja rectangle
-funktiot: ne luovat
uuden kuvaolion ja palauttavat viittauksen luomaansa olioon. Emme ole kirjoittaneet
new Pic
, vaan olemme käyttäneet noita näppäriä apufunktioita.
Toistaiseksi olemme käyttäneet Pic
-tyyppisiä arvoja vain antamalla niitä parametriksi
show
-funktiolle, jolla kuvan saa näkyviin. Mutta kuvaolioilla on myös runsas valikoima
metodeita. Alla on kuvattu niistä muutama.
Kuvan perusominaisuuksia
Kuvaoliolta voi kysyä sen leveyden ja korkeuden.
import o1._import o1._ val pikkuympyra = circle(100, Red)pikkuympyra: Pic = circle-shape pikkuympyra.widthres3: Double = 100.0 pikkuympyra.heightres4: Double = 100.0 val isompi = circle(pikkuympyra.width * 3, Red)isompi: Pic = circle-shape isompi.widthres5: Double = 300.0
Samat toiminnot ovat käytettävissä myös toisin muodostetuille kuvaolioille:
val ladattu = Pic("ladybug.png")ladattu: Pic = ladybug.png println("Kuvassa on yhteensä " + ladattu.width * ladattu.height + " pikseliä.")Kuvassa on yhteensä 900.0 pikseliä.
Kuvien "muokkausta"
Kuvaa voi pyörittää. Kokeile niin näet!
val suorakaide = rectangle(100, 200, Orange)suorakaide: Pic = rectangle-shape val kierretty = suorakaide.clockwise(45)kierretty: Pic = rectangle-shape (transformed) show(kierretty)show(suorakaide)
clockwise
-metodille annetaan parametriksi kierron suuruus
asteina. (Vastaavasti on olemassa metodi counterclockwise
.)clockwise
-metodi siis kuitenkaan ei millään tavoin vaikuta
jo olemassa olevaan kuvaolioon (kuten ei mikään muukaan
Pic
-luokan metodeista). Alkuperäinen kuva on edelleen se,
mikä se olikin.history
-metodi palauttaa tiedon siitä, miten kuva on luotu:
kierretty.historyres6: List[String] = List(clockwise, rectangle)
Kuvia yhteen
Muodostetaan kuva, jossa on kaksi suorakaidetta vierekkäin:
val eka = rectangle(50, 100, Red)eka: Pic = rectangle-shape val toka = rectangle(150, 100, Blue)toka: Pic = rectangle-shape val yhdistelma = eka.leftOf(toka)yhdistelma: Pic = combined pic show(yhdistelma)
leftOf
-metodia vastaavasti voit käyttää myös metodeita rightOf
, above
ja below
.
Kokeile! Esimerkiksi Pong-pelin (luku 1.2) pelikenttä on muodostettu tähän tyyliin
kuvioita yhdistelemällä. Alla olevassa esimerkissä puolestaan rakennetaan yhdistelmä,
jossa sama kuva toistuu kaksi-kertaa-kaksi muodostelmassa:
val testikuva = Pic("https://en.wikipedia.org/static/images/project-logos/enwiki.png")testikuva: Pic = https://en.wikipedia.org/static/images/project-logos/enwiki.png val vierekkain = testikuva.leftOf(testikuva)val vierekkain: Pic = combined pic show(vierekkain.above(vierekkain))
Viime kierroksella laadit mm. funktion pystypalkki
, joka palautti Pic
-tyyppisen arvon.
Viittauksia kuvaolioihin palauttavat myös seuraavissa tehtävissä laadittavat funktiot,
joista ensimmäinen, helpoin, on vapaaehtoinen.
Pikkutreeni
Kirjoita vaikutukseton funktio nelikko
, joka:
- ottaa parametrikseen kuvan, ja
- palauttaa(!) kuvan, jossa annettu kuva toistuu kaksi kertaa kaksi -muodostelmassa.
Funktio siis tekee saman kuin hieman ylempänä annettu koodi mutta mielivaltaiselle parametrikuvalle.
Kirjoita funktio ensimmäiseltä kierrokselta tuttuun Aliohjelmia-projektiin,
aliohjelmia.scala
-tiedostoon.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lipputehtävä 1: Somalia
Laadi vaikutukseton funktio somalianLippu
, joka:
- ottaa ainoaksi parametrikseen
Double
n, joka on lipun leveys, - palauttaa tuonlevyisen kuvan Somalian lipusta, jonka:
- korkeus on kaksi kolmasosaa leveydestä,
- tähden leveys on neljä kolmastoistaosaa koko lipun leveydestä, ja
- värit ovat
RoyalBlue
jaWhite
.
Kirjoita tämäkin funktio aliohjelmia.scala
an.
Viisisakaraisen tähden voi luoda o1
-pakkauksen metodilla star
, jolle annetaan
parametreiksi leveys ja väri. Tähden saa paikoilleen onto
-metodilla, josta on tässä
toisenlainen käyttöesimerkki:
val tummaYmpyra = circle(300, Black)tummaYmpyra: Pic = circle-shape show(testikuva.onto(tummaYmpyra))
Kun lasket, älä unohda miten kokonaislukujen jakolasku toimii (luku 1.3):
> 2 / 3 res7: Int = 0 2 * 150.0 / 3res8: Double = 100.0
Huomaa myös, että funktiosi tulee vain palauttaa lipun kuva, ei laittaa sitä näkyviin. Sitä siis tulee voida käyttää esimerkiksi näin:
show(somalianLippu(400))
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Lipputehtävä 2: Suomi
Tee samaan tiedostoon funktio suomenLippu
, joka:
- ottaa
Double
-tyyppisen leveysparametrin kuten edellinenkin funktio, ja - palauttaa tuonlevyisen kuvan Suomen lipusta, jossa on oheisen kuvan (ja siis virallisten säädösten) mukaiset mittasuhteet.
Käytä värejä White
ja Blue
.
Ohjeita ja vinkkejä:
- Laske ensin "perusyksiköksi" tuo kuvassa x:ksi merkitty mitta.
Muodosta sitä käyttäen oikean kokoiset lipun palaset suorakaiteina.
Asemoi ne vierekkäin tai allekkain sopivasti. Älä käytä skaalausmetodia.
- Huomaa, että funktion parametri siis kuitenkin kertoo lipun koko leveyden, ei tuota kuvassa x:llä merkittyä lyhyempää osaa.
- Käytä paikallisia muuttujia.
- Muista ettei tämänkään funktiosi kuulu laittaa lippua näkyviin
show
’lla. - Voit kokeilla, osaatko ratkaista tehtävän sekä ilman
onto
-metodia että sen avulla.
A+ esittää tässä kohdassa tehtävän palautuslomakkeen.
Sen kun piirtelet muutakin.
Yhteenvetoa
- Yksittäisolioiden lisäksi voidaan määritellä myös luokkia, jotka kuvaavat toisiaan muistuttavien olioiden yhteisiä piirteitä (muuttujia ja metodeita).
- Luokkien käyttäminen kätevöittää olio-ohjelmointia. Esimerkiksi Scala-kielisissä olio-ohjelmissa luokkamäärittelyillä on yleensä huomattavasti yksittäisolioita suurempi merkitys.
- Kun käytössä on luokan määrittely, voi luokan instantioida eli siitä voi luoda ilmentymän, olion. Ilmentymällä on luokan yleiset piirteet (muuttujat ja metodit) mutta tietyt ilmentymäkohtaiset muuttujien arvot.
- Näkökulmasta riippuen luokat ovat: 1) ohjelmakoodin osia, 2) tietotyyppejä, 3) yleiskäsitteiden kuvauksia käsitteellisessä mallissa sekä 4) ohjelmoinnissa käytettyjä abstraktioita.
- Scalassa instantiointi käy
new
-käskyllä, jolle annetaan uuden olion alustamiseen sopivat konstruktoriparametrit. - Olioita käsitellään viittausten kautta.
- Viittauksia voi esimerkiksi sijoittaa muuttujiin.
- Samaan olioon voi osoittaa viittaus useasta paikasta.
- Toisaalta samassa (
var
-)muuttujassa voi olla ensin viittaus yhteen olioon ja myöhemmin toiseen.
- Lukuun liittyviä termejä sanastosivulla: luokka, tietotyyppi; ilmentymä eli instanssi, instantioida, konstruktoriparametri; viittaus; abstraktio.
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!
Kierrokset 1–13 ja niihin liittyvät tehtävät ja viikkokoosteet on laatinut Juha Sorva.
Kierrokset 14–20 on laatinut Otto Seppälä. Ne eivät ole julki syksyllä, mutta julkaistaan ennen kuin määräajat lähestyvät.
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 Riku Autio, Jaakko Kantojärvi, Teemu Lehtinen, 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 ovat suunnitelleet Juha Sorva ja Teemu Sirkiä. Niiden teknisen toteutuksen ovat tehneet Teemu Sirkiä ja Riku Autio käyttäen Teemun toteuttamia Jsvee- ja Kelmu-työkaluja.
Muut diagrammit ja materiaaliin upotetut vuorovaikutteiset esitykset on laatinut Juha Sorva.
O1Library-ohjelmakirjaston ovat kehittäneet Aleksi Lukkarinen ja Juha Sorva. Useat sen keskeisistä osista tukeutuvat Aleksin SMCL-kirjastoon.
Opetustapa, jossa 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+ on luotu Aallon LeTech-tutkimusryhmässä pitkälti opiskelijavoimin. Pääkehittäjänä toimii tällä hetkellä Jaakko Kantojärvi, jonka lisäksi järjestelmää kehittävät useat tietotekniikan ja informaatioverkostojen opiskelijat.
Kurssin tämänhetkinen henkilökunta on kerrottu luvussa 1.1.
Joidenkin lukujen lopuissa on lukukohtaisia lisäyksiä tähän tekijäluetteloon.
new
-sanalla, jonka perään kirjoitetaan instantioitavan luokan nimi eli luotavan olion tyypin nimi. Luokkien nimet on tapana kirjoittaa Scalassa isolla alkukirjaimella.