Muutettu viimeksi 30.3.2016 / Sivu luotu 14.4.2010 / [oppikirjan esimerkit] / [Scala]
Sivun sisältöä:
Perusteluja nimelle: Pelkkä "piirre" on merkitykseltään kovin laaja. Ei esimerkiksi olisi kovin selkeää puhua tyyliin: "Millaisia piirteitä tuolla uudella piirteelläsi on? Se taitaa olla kovin suurpiirteisesti laadittu. Voisitko luonnehtia lyhyesti piirteesi piirteet?"
Aluksi käytin termiä "piirreluokka", mutta koska piirretyypistä ei voi luoda ilmentymiä ja koska sitä nimenomaan käytetään muuttujien, parametrien yms. tyyppinä, "piirretyyppi" on mielestäni perustellumpi nimi. Ihan vastaavin perustein Javan interface-otusta voisi kutsua nimellä "rajapintatyyppi" eikä "rajapintaluokka" tai pelkkä "rajapinta".
On syytä muistaa myös, että kun olio on rajapintaluokan tyyppisessä muuttujassa, oliolle ei voi soveltaa muita metodeita kuin rajapintaluokan luettelemia! Toinen muistamisen arvoinen juttu on, että Javan luokalla voi olla vain yksi yliluokka, mutta se voi toteuttaa useita rajapintaluokkia.
Scalan piirretyyppi on Javan rajapintaluokan kaltainen, mutta se voi abstraktien jäsenten lisäksi sisältää myös metodien toteutuksia, kenttiä ja yleensäkin (melkein) mitä vain, mitä tavallinenkin luokka. Konstruktoria piirretyyppi ei voi sisältää, koska piirretyypistä ei voi luoda ilmentymiä. Mutta oliokumppani sillä voi olla ja siten se halutessaan voi myös luoda piirretyypin tyyppisiä olioita, jos käytettävissä on sopivia toteutusluokkia... Hyviä esimerkkejä tästä ovat piirretyypit Map ja Set. Muistele kuvia luvussa 3:
Jos jonkin olion luokkaan on liitetty piirretyyppi, tuo olio on (mm.) kyseisen piirretyypin tyyppiä! Kuten Javassa myös Scalassa luokalla voi olla vain yksi yliluokka, mutta luokkaan voidaan liittää useita piirretyyppejä. Ja kuten Javan rapintaluokan ollessa muuttujan tyyppinä, myös Scalan piirretyyppi määrää muuttujalle käytettävissä olevat operaatiot.
Aloitetaan johdattelevalla esimerkillä:
trait Philosophical { def philosophize() { println("I consume memory, therefore I am!") } }Tässä Philosophical määrittelee vain yhden ei-abstraktin metodin. Piirretyypistä ei voi luoda ilmentymää, mutta se voidaan liittää (mix in) vaikkapa sammakkoon:
class Frog extends Philosophical { override def toString = "green" def aantele {println("kurr")} } val kermit = new Frog kermit.philosophize() // I consume memory, therefore I am! val saku: Philosophical = kermit // huom: tyyppi println(kermit==saku) // huom: true saku.philosophize() // I consume memory, therefore I am! kermit.aantele // kurr //saku.aantele on KIELLETTY koska sakun tyyppi on Philosophical! // value aantele is not a member of this.PhilosophicalLuokalla voi olla korkeintaan yksi yliluokka, mutta siihen voidaan lisäksi liittää haluttu määrä piirretyyppejä. Seuraavalla luokalla on nimetty yliluokka ja siihen liitetään yksi piirretyyppi:
class Animal {/*...*/} class Frog extends Animal with Philosophical { override def toString = "green" }Tai useampia:
class Animal {/*...*/} trait HasLegs {/*...*/} class Frog extends Animal with Philosophical with HasLegs { override def toString = "green" }Piirretyypistä perittyjä luokan jäseniä voi korvata samaan tapaan kuin yliluokasta perittyjä:
class Animal {/*...*/} trait Philosophical { def philosophize() { println("I consume memory, therefore I am!") } } class Frog extends Animal with Philosophical { override def toString = "green" // ylijuokasta peritty override def philosophize() { // piirretyypistä peritty println("It ain't easy being "+ toString +"!") } } val kermit = new Frog kermit.philosophize() // It ain't easy being green! val saku: Philosophical = kermit // Silti tämäkin käy! saku.philosophize() // It ain't easy being green! //saku.aantele on KIELLETTY koska sakun tyyppi on Philosophical, // VAIKKA sakun arvona olevalla kermitillä on aantele-metodi! // Muuttujan tyyppi siis määrää operaatiot!
Piirretyypin avulla on mahdollista laatia yleiskäyttöisiä välineitä, joihin on ohjelmoitu rikas rajapinta, jonka saa käyttöönsä, kunhan vain toteuttaa muutamia perittyjä, abstrakteja yksinkertaisia operaatioita. Kyse on itse asiassa ns. "koukkumetodien" toteuttamisesta. Tekniikka on käytössä myös mm. Javan joidenkin kokoelmaluokkien abstrakteissa "adapteriluokissa".
Esimerkki Javan ohuesta rajapinnasta "skalannettuna":
trait CharSequence { def charAt(index: Int): Char def length: Int def subSequence(start: Int, end: Int): CharSequence def toString(): String }Vain muutama metodi tarjolla, vaikka melkein kaikki, mikä on String-olioille mahdollista, voisi olla mahdollista myös CharSequence-tyyppiselle oliolle. Vaan ei ole...
Mutta rajapintaan saadaankin Scalan piirretyypissä ohjelmoitua myös ei-abstrakteja metodeita: Tehdään ohut abstrakteista metodeista koostuva rajapinta ja halutun rikas kokoelma ei-abstrakteja metodeita, jotka kutsuvat – käyttävät hyväkseen! – niitä muutamaa abstraktia metodia.
Ja kunhan vain luokka, johon tuo piirretyyppi liitetään, toteuttaa muutamat harvat abstraktit metodit, se saa "ilmaiseksi" käyttöönsä ne kaikki muut! Seuraava esimerkki valaissee asiaa.
class Point(val x: Int, val y: Int)Nelikulmio voitaisiin ohjelmoida vaikka seuraavaan tapaan kirjoittamalla suuri/tarpeellinen määrä konkreettisia metodeja:
class Rectangle(val topLeft: Point, val bottomRight: Point) { def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods... }Samaan tapaan ohjelmoitaisiin jokin abstrakti vitkutin, jossa on kenttiä ja myös ei-abstrakteja metodeita, joista ainakin osa on ihan samanlaisia kuin edellä:
abstract class Component { def topLeft: Point def bottomRight: Point def left = topLeft.x def right = bottomRight.x def width = right - left // and many more geometric methods... }Kun tähän tyyliin joutuisi toistelemaan samoja koodipätkiä ties mihin nelikulmaisia graafisia hahmoja esittäviin luokkiin, alkaisi mietityttää, eikö jotenkin voisi päästä helpommalla.
No voi toki! (Osta hyvä Scala! ;-)
Laaditaan "rikastuspiirretyyppi" (enrichment trait). Siinä on vain kaksi abstraktia metodia, joiden avulla sitten toteutaan ei-abstrakteina metodeina rikas rajapinta:
trait Rectangular { def topLeft: Point // Vain nurkat ovat abstrakteja! def bottomRight: Point // Nähin "koukkuihin" ripustetaan sitten // ne konkreettiset toteutukset. def left = topLeft.x def right = bottomRight.x // Kaikki muu ohjelmoidaan valmiiksi kutsumalla def width = right - left // tarvittaessa noita abstrakteja metodeita! // and many more geometric methods... }Jos nyt halutaan tuo edellä nähty abstrakti luokka se saadaan käytännössä ihan ilmaiseksi:
abstract class Component extends Rectangular { // other methods... }Eikä Rectanglen toteuttamisessa paljon vaivaa tarvitse nähdä. Riittää toteuttaa nuo kaksi Point-nurkkaa:
class Rectangle(val topLeft: Point, val bottomRight: Point) extends Rectangular { // (Tämä on esimerkki myös siitä, miten peritty funktio korvataan kentällä!) // other methods... } val x = new Rectangle(new Point(1,2),new Point(5,6)) println(x.left) // 1 println(x.right) // 5 println(x.width) // 4
Vaikkapa Rational-olioita olisi luontevaa voida vertailla operaatioin "<", ">", "<="ja ">=". Toki noiden ohjelmointi onnistuu. Ja kun yhden saa tehtyä, sen avulla muut on vaivatonta toteuttaa:
class Rational(n: Int, d: Int) { // ... def < (that: Rational) = this.numer * that.denom > that.numer * this.denom def > (that: Rational) = that < this def <= (that: Rational) = (this < that) || (this == that) def >= (that: Rational) = (this > that) || (this == that) }Samaan tyyliin voisi toteuttaa ties mille luokille nuo operaatiot. Yhä uudelleen ja uudelleen vaikka logiikka pysyy samana.
Siksi Scalaan onkin laadittu valmis piirretyyppi Ordered[OmaTyyppi] erisuuruusvertailujen liittämiseen omiin luokkiin. Tyypin ainoa abstrakti metodi on compare(that: OmaTyyppi): Int. Kunhan vain toteuttaa sen omalle luokalle, saa ilmaiseksi nuo muut vertailuoperaatiot. Metodin pitää palauttaa negatiivinen arvo, jos this-olio on pienempi kuin that-olio, nolla, jos ne ovat samat ja positiivinen, jos this-olio on suurempi kuin that-olio.
Huom: Vertaa tätä Javan Comparable-rajapintaluokkaan, joka ei toteuta yhtään mitään, antaa vain Comparable-tyypin oman luokan ilmentymille. Kovin on köyhää... ;-)
Huom: Ordered-piirretyyppi vaatii siis vertailtavan olion tyypin tyyppiparametrina.
Ordered-piirretyyppiä käyttäen on helppo liittää vertailtavuus omaan luokkaan. Toteutetaan vain ja ainoastaan se yksi vaadittu metodi:
class Rational(n: Int, d: Int) extends Ordered[Rational] { // ... def compare(that: Rational) = (this.numer * that.denom) - (that.numer * this.denom) }[ Valmiin Ordered-piirretyypinn toteutus ei ole kovinkaan monimutkainen:
trait Ordered[T] { def compare(that: T): Int // ainoa abstrakti metodi! def <(that: T): Boolean = (this compare that) < 0 def >(that: T): Boolean = (this compare that) > 0 def <=(that: T): Boolean = (this compare that) <= 0 def >=(that: T): Boolean = (this compare that) >= 0 }Huomaa tässä myös tyyppiparametrin käyttö piirretyypin määrittelyssä! Asiaa esitellään lyhyesti materiaalin luvussa 19, laajasti kirjan vastaavassa luvussa. ]
Huom: Ordered ei määrittele equals-metodia ja siten ei myöskään ==- ja !=-operaatioita! Ne saa käyttöönsä korvaamalla omassa luokassa Any-luokasta peritty equals-metodi. (Syyksi mainitaan Java-toteutuksen "type erasure"; geneerisiä tyyppejä ei tiedetä ajoaikana.)
Menettely perustuu siihen, että piirretyypissäkin saa käyttää super-viittausta, joka kuitenkin sidotaan milloin mihinkin yliluokkaan tai piirretyyppiin "dynaamisesti", siis toisin kuin tavallisen luokan tapauksessa, jossa yliluokka on yksikäsitteinen. Piirretyypin super saa merkityksensä, kun piirretyyppi ohjelman käännösaikana liitetään johonkin luokkaan tai piirretyyppiin. (Varoitus: yleensä ohjelmointikielten yhteydessä "dynaaminen" tarkoittaa suoritusaikaista!)
Ohjelmoidaan kokonaislukujono ja sille joitakin muunnoksia. Ensin abstrakti jono:
abstract class IntQueue { def get(): Int def put(x: Int) }Jonoon siis viedään lukuja ja sieltä saadaan ulos yksitellen aina kaikkein vanhin luku, jonossa pisimpään jonottanut. Jono voidaan toteuttaa esimerkiksi ArrayBuffer-oliona:
import scala.collection.mutable.ArrayBuffer class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { buf += x } } val jono = new BasicIntQueue jono.put(10) jono.put(20) jono.put(30) println(jono.get()) // 10 println(jono.get()) // 20 println(jono.get()) // 30Ohjelmoidaan sitten jonoa muuntava piirretyyppi Doubling, joka nimensä mukaisesti tuplaa jonoon vietävän luvun:
trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) } }Abstrakti IntQueue on siis Doubling-piirretyypin yliluokka eli samalla sen ylityyppi! Tämä johtaa siihen, että Doubling voidaan liittää vain johonkin IntQuen aliluokkaan!
Viittaus super on myös kiinnostava! Se tulee luokkaan liittämisen jälkeen tarkoittamaan juuri nyt kyseessä olevan luokan put-metodia! Ja tällainenhan varmasti on olemassa, koska se on abstraktina vaatimuksena luokassa IntQue! Tällaisesta käännösaikaisesta "dynaamisuudesta" yllä puhuttiin.
Kun piirretyypissä (sallittu vain siellä!) määritellään korvaaminen abstraktiksi, tyyppi on luvallista liittää vain sellaiseen luokkaan, jonka ilmentymä antaa konkreettisen toteutuksen tuolle metodille. Muista: konkreettisen aliluokan ilmentymä on myös abstraktin yliluokan ilmentymä!
Kokeillaan:
// IntQueuen ja BasicIntQueuen määrittelyt trait Doubling extends IntQueue { abstract override def put(x: Int) { super.put(2 * x) } } class MyQueue extends BasicIntQueue with Doubling // !! val jono = new MyQueue jono.put(10) jono.put(20) jono.put(30) println(jono.get()) // 20 println(jono.get()) // 40 println(jono.get()) // 60Ohjelmoidaan sitten pari muuta muunnosta:
// IntQueuen ja BasicIntQueuen määrittelyt trait Incrementing extends IntQueue { abstract override def put(x: Int) { super.put(x + 1) } } trait Filtering extends IntQueue { abstract override def put(x: Int) { if (x >= 0) super.put(x) } } val jono = new BasicIntQueue with Incrementing with Filtering // !! jono.put(10) jono.put(20) jono.put(-50) jono.put(30) println(jono.get()) // 11 println(jono.get()) // 21 println(jono.get()) // 31Muuntavien piirretyyppien liittämisjärjestys määrää muunnosten suoritusjärjestyksen. Yksinkertaistettuna ne suoritetaan "oikealta vasemmalle". Asia on todellisuudessa monimutkaisempi, koska luokkien ja piirretyyppien välillä voi olla verkkomaisia yli-ali-suhteita. Ns. linearisointia esitellään alempana.
Tehdään versio "aputulostuksin":
abstract class IntQueue { def get(): Int def put(x: Int) } import scala.collection.mutable.ArrayBuffer class BasicIntQueue extends IntQueue { private val buf = new ArrayBuffer[Int] def get() = buf.remove(0) def put(x: Int) { print("vien ") buf += x } } trait Doubling extends IntQueue { abstract override def put(x: Int) { print("tuplaan ") super.put(2 * x) } } trait Incrementing extends IntQueue { abstract override def put(x: Int) { print("kasvatan ") super.put(x + 1) } } trait Filtering extends IntQueue { abstract override def put(x: Int) { print("suodatan ") if (x >= 0) super.put(x) } } val jono1 = new BasicIntQueue with Doubling with Incrementing with Filtering jono1.put(10) println(jono1.get()) // suodatan kasvatan tuplaan vien 22 val jono2 = new BasicIntQueue with Filtering with Incrementing with Doubling jono2.put(10) println(jono2.get()) // tuplaan kasvatan suodatan vien 21
Jos kielessä olisi "tavallinen moniperintä", seuraava koodipätkä toimisi toisin:
val q = new BasicIntQueue with Incrementing with Doubling q.put(42)Tässä jouduttaisiin valitsemaan jompi kumpi tulkinta: joko kasvattaminen tai tuplaaminen. Kielessä olisi varmaan jokin julkilausuttu sääntö valintaan. Ja jos oletuksesta halutaan poiketa, se olisi ilmaistava vaikkapa tyyliin:
trait MyQueue extends BasicIntQueue with Incrementing with Doubling { def put(x: Int) { Incrementing.super.put(x) // Tämä ei ole Scalaa! Doubling.super.put(x) } }Pitäisi siis itse määrätä mitä ei-yksikäsitteisiä metodeita kutsutaan ja missä järjestyksessä. Vaan Scalassapa on taas automatisoitu asioita....
Klassinen moniperinnän "timanttiongelma" esitetään klassisesti luokkien Henkilö, Opettaja, Opiskelija ja OpettavaOpiskelija suhteina seuraavaan tapaan:
Tehdään aluksi versio, jossa timantti syntyy luokasta ja traitista:
// Timantti: aliluokka ja "traitti" class Henkilo { val nimi="Henkilö" def syo {println("Henkilö syö")} } class Opettaja extends Henkilo { override def syo {println("Opettaja syö")} } trait Opiskelija extends Henkilo { // vaatii extends Henkilö, ks. selitys alla override def syo {println("Opiskelija syö")} } class OpettavaOpiskelija extends Opettaja with Opiskelija val x = new OpettavaOpiskelija println(x.nimi) // Henkilö x.syo // Opiskelija syöNimi periytyy vain kerran; ei siis ole mahdollista, että opettajana henkilön nimi olisi "Matti Lahnanen" ja opiskelijana "Masa". Ja kuten näkyy viimeisenä valittu syömisen tapa valitaan.
Ehkä olisi luontevampaa ohjelmoida symmetrisesti sekä Opettaja että Opiskelija piirretyyppinä:
// Timantti: molemmat traitteja class Henkilo { val nimi="Henkilö" def syo {println("Henkilö syö")} } trait Opettaja extends Henkilo { override def syo {println("Opettaja syö")} } trait Opiskelija extends Henkilo { override def syo {println("Opiskelija syö")} } class OpettavaOpiskelija extends Opettaja with Opiskelija val x = new OpettavaOpiskelija println(x.nimi) // Henkilö x.syo // Opiskelija syö class OpiskelevaOpettaja extends Opiskelija with Opettaja val y = new OpiskelevaOpettaja println(y.nimi) // Henkilö y.syo // Opettaja syö
Myös kenttiä voidaan periä useampaa kautta:
// Timantti: korvataan kenttiä class Henkilö { val nimi="Henkilö" def syo {println("Henkilö syö")} } trait Opettaja extends Henkilö { override val nimi="Opettaja" override def syo {println("Opettaja syö")} } trait Opiskelija extends Henkilö { override val nimi="Opiskelija" override def syo {println("Opiskelija syö")} } class OpettavaOpiskelija extends Opettaja with Opiskelija val x = new OpettavaOpiskelija println(x.nimi) // Opiskelija x.syo // Opiskelija syö class OpiskelevaOpettaja extends Opiskelija with Opettaja val y = new OpiskelevaOpettaja println(y.nimi) // Opettaja y.syo // Opettaja syöSiistiä: viimeisin miksaus siis voittaa! Voittajan valinta on itse asiassa hieman hieman mutkikkaanpaa, kun luokkaan liitetään useita piirretyyppejä, joilla on itsellään yliluokkia tai "ylipiirretyyppejä"!
Liitettyjen piirretyyppien kenttien ja metodien valintajärjestys on
täsmällisesti (ja luontevasti??) määritelty ns. periytymishierarkian
linearisoinnilla. Se määrää siis myös ja aivan erityisesti sen, mihin
super-viittaukset viittaavat.
(Linearisointi syvällisemmin kuin yllä nähtiin
ei kuulu koealueeseen!
On lähinnä Ohjelmointikielten periaatteet -kurssin asiaa.
Siellä timantista ja linearisoinnista puhutaan
hieman enemmän.)
class Animal
trait Furry extends Animal
trait HasLegs extends Animal
trait FourLegged extends HasLegs
class Cat extends Animal with Furry with FourLegged
Type Linearization:
Animal Animal, AnyRef, Any
Furry Furry, Animal, AnyRef, Any
FourLegged FourLegged, HasLegs, Animal, AnyRef, Any
HasLegs HasLegs, Animal, AnyRef, Any
Cat Cat, FourLegged, HasLegs, Furry, Animal, AnyRef, Any
(Koealueen ulkopuolinen osuus päättyy)
trait Nopeus { private var nopeus = 0 def setNopeus(n: Int) {nopeus = n} def getNopeus = nopeus } trait Moottori { private var hevosia = 0 def setHevosia(h: Int) {hevosia = h} def getHevosia = hevosia } trait Merkki { private var merkki = "hyrysysy" def setMerkki(m: String) {merkki = m} def getMerkki = merkki } trait Automaisuus extends Nopeus with Moottori with Merkki { override def toString = "("+ getMerkki +": " + getHevosia + " hp, " + getNopeus + " km/h)" } class Auto extends Automaisuus // Saisi toki sanoa myös class Auto extends AnyRef with Automaisuus val biili = new Auto biili.setNopeus(120) biili.setHevosia(200) biili.setMerkki("Maserati") println(biili.getNopeus) // 120 println(biili.getHevosia) // 200 println(biili.getMerkki) // Maserati println(biili) // (Maserati: 200 hp, 120 km/h)Helppoa ja hauskaa?
// --- Eläinten yhteinen yliluokka: abstract class Elain (nimi:String) { def aantele: Unit // abstrakti metodi override def toString = nimi // korvattaessa "override" pakollinen! } // --- Aliluokan määrittely, huomaa parametrivälitys: class Kissa(nimi:String, naukumistiheys:Int) extends Elain(nimi) { override def aantele {println("Miau")} override def toString = super.toString + "-" + naukumistiheys } val kissa = new Kissa("Missu", 7) println(kissa) // Missu-7 kissa.aantele // Miau // --- Pari imettäväisiin kuuluvaa eläinlajia: class Hevonen(nimi:String) extends Elain(nimi) { override def aantele {println("Ihahaa")} } val hevonen = new Hevonen("Polle") println(hevonen) // Polle hevonen.aantele // Ihahaa class Nauta(nimi:String) extends Elain(nimi) { override def aantele {println("Ammuu")} } val nauta = new Nauta("Julle") println(nauta) // Julle nauta.aantele // Ammuu // --- Kokeillaan polymorfismia: var x:Elain = new Kissa("Töpö", 2) // Huom: muuttujan tyyppi on Elain println(x) // Töpö-2 x.aantele // Miau x = new Hevonen("Valma") println(x) // Valma x.aantele // Ihahaa x = new Nauta("Muurikki") println(x) // Muurikki x.aantele // Ammuu // -- Ja sitten tehdään piirretyyppi lypsäville eläimille: trait Lypsava { def lypsa = {0.0} // tässä oletustoteutus; myös abstrakti metodi "def lypsa:Double" olisi mahdollinen } // Ja pari lypsävää, joihin tuo "traitti mixataan": class Lehma(nimi:String) extends Nauta(nimi) with Lypsava { override def lypsa = 3.14 * nimi.length // maidon määrä riippuu nimen pituudesta... } val mansikki = new Lehma("Mansikki") println(mansikki) // Mansikki mansikki.aantele // Ammuu println(mansikki.lypsa) // 25.12 class Tamma(nimi:String) extends Hevonen(nimi) with Lypsava { override def lypsa = 1.23 * nimi.length // vähemmän maitoa kuin lehmältä } val tammukka = new Tamma("Tammukka") println(tammukka) // Tammukka tammukka.aantele // Ihahaa println(tammukka.lypsa) // 9.84 // Lopuksi tehdään juustoa mistä tahansa "traitin saaneesta": def juustoa(tuotantoelain:Lypsava):Double = 42 * tuotantoelain.lypsa println(juustoa(mansikki)) // 1055.04 println(juustoa(tammukka)) // 413.28
Piirretyyppi voi sisältää sekä toteutettuja metodeita (jotka peritään) sekä abstrakteja metodeita (jotka on pakko toteuttaa, ellei ole ohjelmoimassa abstraktia luokkaa). Peritty toteutettu metodikin toki voidaan korvata:
// --- Eläinten yhteinen yliluokka: abstract class Elain (nimi:String) { def aantele: Unit // abstrakti metodi override def toString = nimi // korvattaessa "override" pakollinen } // --- Pari imettäväisiin kuuluvaa eläinlajia: class Hevonen(nimi:String) extends Elain(nimi) { override def aantele {println("Ihahaa")} } class Nauta(nimi:String) extends Elain(nimi) { override def aantele {println("Ammuu")} } // -- Ja sitten tehdään hienompi "traitti" lypsäville eläimille: trait Lypsava { def lypsa(n:String) = 3.14 * n.length // ei-abstrakti metodi def potkaise:Unit // abstrakti metodi eli Javan interfacen kaltainen "vaatimus" } // Ja pari Lypsävää, joihin tuo "traitti" "mixataan in": class Lehma(nimi:String) extends Nauta(nimi) with Lypsava { // lypsa(n:String) peritään traitista, so. oletustoteutus def potkaise {println("Lehmä potkaisee")} } val mansikki = new Lehma("Mansikki") println(mansikki) // Mansikki mansikki.aantele // Ammuu println(mansikki.lypsa(mansikki.toString)) // 25.12 mansikki.potkaise // Lehmä potkaisee class Tamma(nimi:String) extends Hevonen(nimi) with Lypsava { override def lypsa(n:String) = 1.23 * n.length // korvataan peritty omalla def potkaise {println("Tamma potkaisee")} } val tammukka = new Tamma("Tammukka") println(tammukka) // Tammukka tammukka.aantele // Ihahaa println(tammukka.lypsa(tammukka.toString)) // 9.84 tammukka.potkaise // Tamma potkaisee // Lopuksi tehdään juustoa: def juustoa(tuotantoelain: Lypsava):Double = 42 * tuotantoelain.lypsa(tuotantoelain.toString) println(juustoa(mansikki)) // 1055.04 println(juustoa(tammukka)) // 413.28