13 minute read

4.3 Bruksområder for klasser og metoder

Med kullsyre

Varmt vann Full

½

Tom Tapp 0,5 l

1 glass

Tanken er tømt

Tanken er fylt 5 liter

I starten konsentrerer programmererne seg om funksjonaliteten bak knappene med teksten «Tapp 0,5 l», «Tanken er tømt» og «Tanken er fylt».

I dette delkapittelet ser vi på noen bruksområder for klasser og metoder. Vi tar ikke for oss nye teknikker, men viser hvordan du kan sette sammen teknikkene du har lært hittil. Du vil se at vi kan gjøre mye med et lite verktøyskrin. Du må selvsagt ha litt erfaring med verktøyene før du klarer å komme frem til slike løsninger selv, så ta dette delkapittelet som inspirasjon heller enn som en målestokk på hva du skal kunne nå.

BRUKE METODER FOR Å IVARETA REGLER FRA DEN VIRKELIGE VERDEN

Selv om det vanligvis går bra når vi endrer innholdet i et felt direkte, er det ikke alltid lurt. Vi kan ende opp med å gi feltet verdier som objektet ikke kan ha i virkeligheten. Et typisk eksempel er at vi setter et tall til en negativ verdi som det ikke kan ha i virkeligheten. En sirkel kan for eksempel ikke ha en negativ radius, og en kopp kan ikke inneholde en negativ mengde kaffe. Metoder kan forhindre at felter får verdier de ikke skal ha.

Noen programmerere i et vannautomatfirma jobber med å utvikle en kode til en ny elektronisk vannautomat med vanntank og en enkel LCD-skjerm. De er i startfasen og ønsker å lage en enkel variant til å begynne med. I koden nedenfor har de definert en klasse som styrer vannautomaten. Automaten skal slippe ut 0,5 liter vann fra en kran når brukeren trykker på en knapp. Automaten skal også følge med på hvor mye vann det er igjen på tanken. Maskinen har ingen sensor som måler dette, så for å vite hvor mye som er igjen, må maskinen regne ut innholdet basert på hvor mye kranen har sluppet ut siden sist noen fylte på tanken. Det viktigste i dette eksemplet er at metodene i klassen passer på at verdien i innhold aldri går under 0.

1 // _vannautomat.js 2 export class Vannautomat { 3 constructor(kapasitetILiter) { 4 this.kapasitet = kapasitetILiter 5 this.innhold = kapasitetILiter

6 } 7 8 tappVann(liter) { 9 let innholdFoer = this.innhold 10 this.innhold = this.innhold - liter 11 if (this.innhold < 0) {

12 this.innhold = 0

13 } 14 return innholdFoer - this.innhold

15 } 16 17 beskrivVanninnhold() { 18 return `Det er ${this.innhold} av ${this.kapasitet} liter på tanken`

19 } 20 21 lagBeskjedTilSkjerm() { 22 if (this.innhold <= 0) { 23 return "Tanken er tom. Be drift om påfyll." 24 } else {

25 26 } 27 } 28 } return this.beskrivVanninnhold()

Nedenfor ser vi et testgrensesnitt som programmererne har laget for å teste en lignende vannautomat uten å måtte laste programvaren over på den fysiske automaten. Testgrensesnittet simulerer skjermen og knappene på automaten. Vanlige brukere har én knapp de kan trykke på. De er ennå tidlig i utviklingen av produktet, så automaten kan bare slippe ut vann i en forhåndsbestemt dose på 0,5 liter. Teknikere kan låse opp et kontrollpanel og trykke på to knapper. Den ene trykker de på når de har tømt tanken, den andre når de har fylt den helt opp.

1 <!-- vannautomat-testgrensesnitt.svelte --> 2 <script> 3 import { Vannautomat } from "./_vannautomat.js"

4

5 let minVannautomat = new Vannautomat(5) 6 let hvorMyeVannSomKomUtSist = 0 7 let standardVannmengde = 0.5 8 </script>

9 10 <h1>Vannautomat</h1>

11 12 <h2>Synlig for alle</h2> 13 <p>{minVannautomat.lagBeskjedTilSkjerm()}</p>

14 15 <p>Det kom ut {hvorMyeVannSomKomUtSist} liter sist</p>

16 17 <button 18 on:click={() => {

19 20 21 hvorMyeVannSomKomUtSist = minVannautomat.tappVann(standardVannmengde) // Sender signal om å oppdatere visningen. minVannautomat = minVannautomat

22 }} 23 > 24 Tapp {standardVannmengde} liter 25 </button>

26 27 <h2>Synlig for teknikere</h2> 28 <button on:click={() => (minVannautomat.innhold = 0)}>Tanken er tømt</button> 29 <button on:click={() => (minVannautomat.innhold = minVannautomat.kapasitet)}> 30 Tanken er fylt 31 </button>

Legg merke til at vi bruker minVannautomat.tappVann for å tappe vann og oppdatere innholdet. Derfor måtte vi skrive minVannautomat = minVannautomat i den påfølgende linjen for å sørge for at Svelte oppdaterer visningen, slik vi lærte i delkapittel 4.2. Vi trenger derimot ikke å si fra om at hvorMyeVannSomKomUtSist har fått ny verdi, for den sto på venstre side av likhetstegnet i den foregående linjen. I metodene som er bundet til knappene for å tømme og fylle tanken, har vi derimot oppdatert feltet minVannautomat.innhold direkte for å demonstrere forskjellen mellom å oppdatere med metoder og å skrive direkte til feltet. Vi kunne også ha laget metoder for å tømme og fylle, men da måtte vi også ha skrevet minVannautomat = minVannautomat etter å ha kalt på disse metodene.

V = G · h h

G

METODER KAN HUSKE FORHOLDSREGLER OG TILBY EKSTRAFUNKSJONER

En viktig fordel med å overlate jobben med å oppdatere til metoder er at de kan huske på forholdsregler for programmereren: Uten metoden tappVann ville det ha vært fort gjort for programmereren å skrive minVannautomat.innhold = minVannautomat.innhold - 0.5 et sted i koden uten å tenke på at innholdet kan gå under null. Etter at tanken hadde vært tømt og deretter fylt på igjen, kunne brukerne da ha fått se tekst som «–0,5 av 5 liter på tanken» på skjermen.

Metoder kan også forebygge feil og legge til rette for forbedringer som programmereren kanskje ikke hadde tenkt på i utgangspunktet. Metoden tappVann kunne for eksempel ha inneholdt noen kodelinjer som sendte en tekstmelding til driftsavdelingen dersom vanninnholdet gikk under et visst nivå, slik at de visste at det var på tide å fylle på tanken.

Hva er fordelene med å oppdatere med likhetstegnet? Hva er fordelen med å oppdatere med metoder fulgt av variabelnavn = variabelnavn? Kan det være en fordel å oppdatere med metoder hver gang, selv om det krever en ekstra kodelinje?

Sett at du skulle legge til støtte som gjorde at det ble sendt en tekstmelding til et visst telefonnummer når maskinen var tom. Hvor synes du det hadde vært best å oppgi telefonnummeret meldingen skulle sendes til? Som en parameter

DISKUTER

DISKUTER

til tappVann, for eksempel tappVann(0.5, "12345678"), som en parameter til konstruktøren, for eksempel new Vannautomat(5, "12345678"), eller et helt annet sted?

FELTER SOM INNEHOLDER ANDRE OBJEKTER

Det er ofte nyttig å la et objekt inneholde felter med et objekt av en annen klasse. Hvis vi for eksempel allerede har laget en klasse som gjør deler av jobben en annen klasse skal gjøre, kan vi la denne andre klassen gjøre deler av den eksisterende klassens jobb.

En sylinder er en romfigur. Den har en sirkel som grunnflate og en høyde. I følgende fil definerer vi klassen Sylinder i tillegg til å lime inn klassen Sirkel som vi brukte i delkapittel 4.2. Den første parameteren i konstruktøren til Sylinder er grunnflaten, altså et Sirkel-objekt, som lagres i feltet grunnflate. Høyden er bare et vanlig tall.

1 // _klasser.js 2 export class Sirkel { 3 constructor(radius) { 4 this.radius = radius

5 } 6 7 regnUtAreal() { 8 return Math.PI * this.radius * this.radius

9 } 10 11 regnUtOmkrets() { 12 return 2 * Math.PI * this.radius

13 } 14 } 15 16 export class Sylinder { 17 constructor(grunnflate, hoeyde) { 18 this.grunnflate = grunnflate 19 this.hoeyde = hoeyde

20 } 21

22 regnUtVolum() { 23 return this.grunnflate.regnUtAreal() * this.hoeyde

24 } 25 26 regnUtAreal() { 27 let grunnflateareal = this.grunnflate.regnUtAreal() 28 /**

29 * Det er en grunnflate både oppe og nede på sylinderen,

30 * derfor må vi ta med begge i arealet.

31 */ 32 let antallGrunnflater = 2 33 // Dette er arealet som går langs siden av sylinderen. 34 let arealUtenomGrunnflate = this.grunnflate.regnUtOmkrets() * this.hoeyde 35 return arealUtenomGrunnflate + grunnflateareal * antallGrunnflater

36 } 37 }

Som vi kan se av koden, er Sylinder-klassen ganske lat: Den lar Sirkel regne ut sitt eget areal og sin egen omkrets og bidrar selv bare med høyden i ny og ne.

Nedenfor ser du et grensesnitt der vi kan leke oss litt med ulik høyde og radius for sylinderen.

1 <!-- sylinder-test.svelte --> 2 <script> 3 import { Sirkel, Sylinder } from "./_klasser.js"

4

5 let minSylinder =

6 /*

7 8 9 10 11 12 </script>

13 * Her oppretter vi sirkelen og angir den som argument. * Vi lagrer den ikke til noen variabel. * Det går greit ettersom vi ikke trenger å vise til den senere. */ new Sylinder(new Sirkel(2), 3)

14 <div>

15 <label>

16 Radius:

17 <input type="number" bind:value={minSylinder.grunnflate.radius}

/> 18 </label> 19 <label>

20 Høyde:

21 <input type="number" bind:value={minSylinder.hoeyde} />

22 </label> 23 </div>

24 25 <p> 26 Overflatearealet til sylinderen er {minSylinder.regnUtAreal()}. <br /> 27 Volumet er {minSylinder.regnUtVolum()}. 28 </p>

Legg merke til at vi henter ut grunnflatens radius med dot-notasjon, slik:

minSylinder.grunnflate.radius.

EKSTRASTOFF

Polymorfisme

Vi kunne ha brukt et objekt av en hvilken som helst klasse som grunnflate i Sylinder, så lenge klassen vi brukte, inneholdt metodene regnUtAreal og regnUtVolum. Metodene regnUtVolum og regnUtAreal i Sylinder ville ha kjørt og gitt riktige resultater fordi formlene for volum og overflateareal ikke er avhengige av formen på grunnflaten, bare omkrets og areal. Brukergrensesnittet i sylinder-test.svelte er riktignok tett knyttet til akkurat sirkler, så vi hadde nok måttet lage et nytt brukergrensesnitt om vi ønsket å eksperimentere med andre grunnflater. Muligheten til å bruke objekter av ulike klasser så lenge de definerer de forventede metodene, kalles for polymorfisme (som betyr at noe kan opptre i forskjellige former). Vi går ikke videre inn på polymorfisme i denne boka, men dersom du fortsetter med objektorientert programmering senere, vil du sannsynligvis komme borti ordet igjen.

HOLDE OVERSIKT OVER ET BIBLIOTEK MED ET OBJEKTORIENTERT PROGRAM

Mange dataprogrammer har som jobb å holde styr på samlinger, deriblant boksamlinger. I denne seksjonen skal vi lage et enkelt program for å holde styr på et bibliotek.

I følgende kodesnutt lager vi de to klassene Forfatter og Bok. Forfatter-objektene skal lagre fornavnet og etternavnet til en forfatter i to separate felter. Et Bok-objekt skal lagre tittelen til en bok i feltet tittel, og i feltet forfattere skal det ligge et array med objekter av klassen Forfatter som representerer de forfatterne som skrev boka.

1 // _klasser.js 2 export class Bok { 3 constructor(tittel, forfattere) { 4 this.tittel = tittel 5 this.forfattere = forfattere

6 } 7 } 8 9 export class Forfatter { 10 constructor(fornavn, etternavn) { 11 this.fornavn = fornavn 12 this.etternavn = etternavn

13 } 14 }

Vi vil lage et brukergrensesnitt som viser frem bøkene vi har lagret, som en kulepunktliste, med bøkenes forfattere som underpunkter. I biblioteket vårt har vi de to bøkene Mamma Mø av Jukka Wieslander, Tomas Wieslander og Sven Nordquist og Gubben og katten av Sven Nordquist. Punktlisten på nettsiden skal inneholde tittelen på de to bøkene mellom to apostrofer etterfulgt av ordet «av». Forfatternes fulle navn skal presenteres i underpunkter.

1 <!-- bibliotek-grensesnitt.svelte --> 2 <script> 3 import { Bok, Forfatter } from "./_klasser.js"

4 5 // Oppretter et objekt for hver forfatter 6 let jukka = new Forfatter("Jukka", "Wieslander") 7 let tomas = new Forfatter("Tomas", "Wieslander") 8 let sven = new Forfatter("Sven", "Nordquist")

9 10 // Oppretter et array som holder på bøkene 11 let boksamling = [

12 new Bok("Mamma Mø", [jukka, tomas, sven]),

13 new Bok("Gubben og katten", [sven]),

14 ] 15 </script>

16 17 <h1>Biblioteket mitt</h1> 18 <ul> 19 {#each boksamling as bok} 20 <li>

21 22 "{bok.tittel}" av <ul>

23 24 25

{#each bok.forfattere as forfatter} <li>{forfatter.fornavn} {forfatter.etternavn}</li> {/each} 26 </ul> 27 </li> 28 {/each} 29 </ul>

FORBEDRE BIBLIOTEKET VED Å LAGE EN KOBLING KOBLINGER MELLOM FORFATTERE OG BØKER

I biblioteket vi nettopp laget, var det bare mulig å finne ut hvilke forfattere som hadde skrevet en bok. Hvis vi likte en av forfatterne godt, var det ikke mulig å finne ut hvilke bøker vedkommende hadde skrevet. Vi kan bruke metoder som kan hjelpe oss med å løse dette.

Vi legger til et felt i Forfatter som heter verker. Dette er et array med Bok-objekter, altså de bøkene som forfatteren har skrevet. Vi ønsker å oppnå følgende: Hvis et gitt Forfatter-objekt er lagret i feltet forfattere i et Bok-objekt, skal Bok-objektet også være å finne lagret verker i Forfatter-objektet (og omvendt). Dette krever at vi legger til metoder som legger til forfatteren. Nedenfor ser du hvordan vi kan endre klassene for å løse oppgaven.

1 // _klasser.js 2 export class Bok { 3 constructor(tittel) { 4 this.tittel = tittel 5 this.forfattere = []

6 } 7 8 leggTilForfatter(forfatter) { 9 let finnesAlleredeIForfattere = this.forfattere.includes(forfatter) 10 // Ikke legg til forfatter hvis vedkommende allerede er i listen. 11 if (!finnesAlleredeIForfattere) {

12 13 this.forfattere.push(forfatter) /*

14 15 * Legg til boka i forfatterens verker. *

16 17 18 19 20 21 } 22 } 23 } 24 * Forfatter-klassens leggTilVerk-metode * kommer til å forhindre at vi legger til boka dobbelt, * så vi trenger ikke å sjekke om den allerede finnes. */ forfatter.leggTilVerk(this)

25 export class Forfatter { 26 constructor(fornavn, etternavn) { 27 this.fornavn = fornavn 28 this.etternavn = etternavn 29 this.verker = []

30 } 31 32 leggTilVerk(verk) { 33 let erIVerker = this.verker.includes(verk)

34 35 // Ikke legg til verk hvis det allerede er i listen. 36 if (!erIVerker) {

37 38 this.verker.push(verk) /*

39 40 * Legg til forfatteren i bokas forfatterliste. *

41 42 43 44 45 46 } 47 } 48 } * Bok-klassens leggTilForfatter-metode * kommer til å forhindre at vi legger til forfatteren dobbelt, * så vi trenger ikke å sjekke om forfatteren allerede finnes. */ verk.leggTilForfatter(this)

Her har det dukket opp to nye metoder: • • leggTilForfatter, som legger til en forfatter i en boks forfatterliste leggTilVerk, som legger til en bok i forfatterens verksliste

Begge metodene sjekker først at det de skal legge til, ikke allerede ligger i listen. Hvis det allerede ligger i listen, avsluttes metoden. Hvis ikke, legger de til det de skal, samtidig som de kaller på den tilsvarende metoden til motparten, slik at en tilbakekobling opprettes hvis den ikke allerede finnes. Konstruktøren til Bok tar ikke lenger forfattere som parameter, og derfor må vi legge til alle verker og forfattere ved hjelp av disse to metodene.

I disse metodene bruker vi this på en måte vi ikke har sett hittil. Vi bruker ordet for å sende objektet i sin helhet som et argument til en metode i den andre klassen. Det ser litt snålt ut, men når vi tenker over det, har ikke objektet noe annet alternativ hvis det skal vise til hele seg: Det vet jo ikke hvilket variabelnavn det skal få når det brukes. Nedenfor ser du et oppdatert grensesnitt for biblioteket. Der tester vi både leggTilForfatter og leggTilVerk. I slutten av visningen har vi lagt til en liste over forfattere og bøkene de har skrevet, for å sjekke at koblingene går begge veier. Vi kan se for oss at det som skjer i script-delen, simulerer bibliotekarer som fyller inn litt og litt informasjon etter hvert som de får den.

1 <!-- bibliotek-grensesnitt.svelte --> 2 <script> 3 import { Bok, Forfatter } from "./_klasser.js"

4 5 // Bibliotekarene legger inn Mamma mø. 6 let mammaMoe = new Bok("Mamma Mø")

7 8 let jukka = new Forfatter("Jukka", "Wieslander") 9 let tomas = new Forfatter("Tomas", "Wieslander") 10 mammaMoe.leggTilForfatter(jukka) 11 mammaMoe.leggTilForfatter(tomas)

12 13 // Så legger de inn Gubben og katten. 14 let sven = new Forfatter("Sven", "Nordquist") 15 let gubbenOgKatten = new Bok("Gubben og katten") 16 gubbenOgKatten.leggTilForfatter(sven)

17 18 // Så oppdager de at Sven også var med på Mamma Mø. 19 sven.leggTilVerk(mammaMoe)

20 21 let boksamling = [mammaMoe, gubbenOgKatten] 22 let forfattere = [jukka, tomas, sven] 23 </script>

24 25 <h1>Biblioteket mitt</h1> 26 <h2>Bøker</h2> 27 <ul> 28 {#each boksamling as bok} 29 <li>

30 31 "{bok.tittel}" av <ul>

32 33 {#each bok.forfattere as forfatter} <li>{forfatter.fornavn} {forfatter.etternavn}</li>

34

{/each} 35 </ul> 36 </li> 37 {/each} 38 </ul>

39 40 <h2>Forfattere</h2>

This article is from: