Tilbake
6.1

6.1 Fra spesifikasjon til klasser — modelleringsmetoden

Den systematiske veien fra en tekstlig/UML-spesifikasjon til et klasseoppsett: identifisér klassene, deres instansvariabler og metoder, og hvordan de forholder seg til hverandre.

55 min
8 oppgaver
Fra spesifikasjon til klassermodelleringsmetoden
Din fremgang i kapitlet
0 / 8 oppgaver

Forkunnskaper — sist du var her

Kapitlet bygger på hele del 5:
kap. 5.1 — Klasser og objekter,
kap. 5.2 — Innkapsling og
kap. 5.3 — __str__.

De tre malene du skal sette sammen, friskes opp med kjørt kode.

1. Konstruktør-malen — alle instansvariabler settes med self._:

class Bok:
    def __init__(self, tittel, forfatter):
        self._tittel = tittel self._forfatter = forfatter self._utlaant = False

Blokken skriver ingenting ut ennå.

2. Aksessor/mutator-malen, og 3. __str__ — alle tre satt sammen:

class Bok:
    def __init__(self, tittel, forfatter):
        self._tittel = tittel self._forfatter = forfatter self._utlaant = False

    def hent_tittel(self):
        return self._tittel

    def sett_utlaant(self, ny):
        self._utlaant = ny

    def __str__(self):
        return self._tittel + " av " + self._forfatter

b = Bok("Sult", "Hamsun")
b.sett_utlaant(True)
print(b, b.hent_tittel())

Utskrift:

Sult av Hamsun Sult

Fra nå av er ikke spørsmålet hvordan du skriver en klasse, men hvilke
klasser du skal skrive.

Løkke 1 — Fra tekst til klasser (~14 min)

En eksamensoppgave i sjanger F starter alltid med en beskrivelse på noen få avsnitt. Slik:

> Et bibliotek har et navn og en samling bøker. Hver bok har en tittel, en
> forfatter og en opplysning om den er utlånt. En bok kan lånes ut og leveres
> tilbake. Biblioteket kan legge til en bok, finne en bok på tittel, og skrive ut
> hele samlingen.

Det ser ut som løpende tekst. Det er det ikke — det er en oppskrift, og den kan leses nesten mekanisk.

📜Klassemodellering-malen, steg 1 og 2

Bokas femtende kodemønster, og det som avgjør hele Oppgave 4. De to første stegene er ren tekstlesing:

Steg 1 — hvert substantiv som «er en ting», blir en klasse. I teksten over:
bibliotek og bok. Ikke navn, ikke tittel — de er egenskaper ved en ting,
ikke ting i seg selv.

Steg 2 — hver egenskap blir en instansvariabel, satt med self._ i
konstruktøren. Bok får _tittel, _forfatter og _utlaant. Bibliotek får
_navn og en samling bøker.

Steg 3 — hver «kan gjøre»-setning blir en metode. «En bok kan lånes ut» gir
laan_ut(self). «Biblioteket kan finne en bok på tittel» gir
finn(self, tittel).

Steg 4 — skriv aksessor og mutator for de verdiene som trengs utenfra. Ikke
for alle — bare de oppgaven faktisk bruker.

De fire stegene tar tre–fire minutter, og de gir deg et skjelett: klassene med tomme metoder. Fyll aldri inn kroppene før skjelettet står.

Å finne klassene

Framgangsmåten: stryk under hvert substantiv i teksten, og spør om det er en
ting med egenskaper eller bare en egenskap.

Bok har tittel, forfatter og utlånsstatus — det er en ting. Tittel har ingen
egenskaper; det er en egenskap. Altså blir Bok en klasse og _tittel en instansvariabel.

En god tommelfingerregel: kan du tenke deg flere av den, og har hver av dem sine egne opplysninger, er det en klasse.

Eksamensoppgavene har typisk tre til fem klasser. Har du funnet to, har du sannsynligvis slått sammen noe; har du funnet åtte, har du gjort egenskaper om til klasser.

Å finne instansvariablene

Hver egenskap teksten nevner om en ting, blir en instansvariabel satt i konstruktøren.

To slag: de som kommer utenfra og står som parametere
(def __init__(self, tittel, forfatter):), og de som har en startverdi
(self._utlaant = False, self._antall_utlaan = 0, self._boker = {}).

Den andre gruppen er den som oftest glemmes. Kjenn den igjen på formuleringer som
«starter på null», «er ikke utlånt når den registreres» og «har ingen bøker til å begynne med».

Alt objektet skal huske, settes i __init__. Ingen unntak.

Å finne metodene

Hver «kan gjøre»-setning i teksten blir en metode.

«En bok kan lånes ut» gir laan_ut(self). «Biblioteket kan finne en bok på tittel» gir finn(self, tittel). Trenger metoden en verdi utenfra, blir den en parameter.

Metodenavnene skrives med små bokstaver og gjerne som verb: laan_ut,
legg_til, finn, meld_paa. Norske navn er fasitens egen stil — bare uten æ, ø og å.

Ligger metoden på riktig klasse? Spør hvem som eier opplysningene den
trenger. Utlånsstatusen ligger i Bok, så laan_ut hører hjemme der — ikke i
Bibliotek.

✏️Eksempel 1: Fra spesifikasjon til skjelett

Les spesifikasjonen under og sett opp skjelettet: klassene med instansvariabler og metodesignaturer, uten å fylle inn kroppene.

> Et bibliotek har et navn og en samling bøker. Hver bok har en tittel, en
> forfatter og en opplysning om den er utlånt. En bok kan lånes ut og leveres
> tilbake. Biblioteket kan legge til en bok, finne en bok på tittel, og skrive ut
> hele samlingen.

Steg 1 — klassene. Substantivene som er ting: bibliotek og bok. Altså to
klasser, Bibliotek og Bok.

Steg 2 — instansvariablene.

KlasseFra tekstenInstansvariabler
Boktittel, forfatter, utlånt_tittel, _forfatter, _utlaant
Biblioteknavn, samling bøker_navn, _boker

_utlaant og _boker kommer ikke utenfra: en ny bok er ikke utlånt, og et nytt bibliotek har ingen bøker.
Steg 3 — metodene.
Klasse«Kan gjøre»-setningMetode
Bokkan lånes utlaan_ut(self)
Bokkan leveres tilbakelever(self)
Bibliotekkan legge til en boklegg_til(self, bok)
Bibliotekkan finne en bok på tittelfinn(self, tittel)
Bibliotekkan skrive ut hele samlingenskriv_alle(self)

Steg 4 — aksessorer. Biblioteket må kunne slå opp på tittel, så Bok trenger

hent_tittel(). Utlånsstatusen bør kunne leses utenfra, så er_utlaant() blir med. De andre verdiene ber teksten ikke om.

Skjelettet, med kroppene bare antydet:

class Bok:
    def __init__(self, tittel, forfatter):
        self._tittel = tittel self._forfatter = forfatter self._utlaant = False

    def hent_tittel(self):
        return self._tittel

    def er_utlaant(self):
        return self._utlaant

    def laan_ut(self):
        self._utlaant = True

    def lever(self):
        self._utlaant = False

    def __str__(self):
        return self._tittel + " av " + self._forfatter

class Bibliotek:
    def __init__(self, navn):
        self._navn = navn self._boker = {}

    def legg_til(self, bok):
        self._boker[bok.hent_tittel()] = bok

    def finn(self, tittel):
        if tittel in self._boker:
            return self._boker[tittel]
        return None

    def skriv_alle(self):
        for tittel in self._boker:
            print(self._boker[tittel])

bib = Bibliotek("Deichman")
bib.legg_til(Bok("Sult", "Hamsun"))
bib.legg_til(Bok("Vaar", "Undset"))
bib.finn("Sult").laan_ut()
bib.skriv_alle()
print(bib.finn("Sult").er_utlaant(), bib.finn("Naboer"))
Utskrift:
Sult av Hamsun
Vaar av Undset
True None
Slik ville sensor sett på det: klassevalget alene er verdt poeng, før en
eneste metodekropp er skrevet. To klasser med riktige instansvariabler og riktige signaturer gir uttelling selv om kroppene skulle vise seg å ha feil.
Legg merke til at laan_ut ligger på Bok, ikke på Bibliotek. Utlånsstatusen

er bokas egen opplysning. Å legge metoden i Bibliotek ville tvunget den til å røre bok._utlaant — feilkode #11.
Merk _boker = {} og ikke []. Teksten sier «finne en bok på tittel», og

det peker rett på en ordbok. Valget mellom liste og ordbok er tema i

kap. 6.2.

📝Oppgave 1

(Innstegsoppgave — gjengivelse.) Les spesifikasjonen:

> En kantine har et navn og en meny. Hver rett har et navn, en pris og et antall
> solgte porsjoner som starter på null. En rett kan selges. Kantina kan legge til
> en rett og skrive ut hele menyen.

a) Hvilke to klasser skal du lage?

b) Hvilke instansvariabler får hver av dem?

c) Hvilke av instansvariablene kommer ikke utenfra?

Løkke 2 — Skjelett før innhold (~13 min)

Den vanligste tidsfellen i Oppgave 4 er å begynne å skrive ferdig kode på den første klassen, og oppdage tjue minutter senere at modellen ikke henger sammen.

Skriv skjelettet først. Klassene, instansvariablene og metodesignaturene, med
kroppene tomme eller antydet. Da ser du hele modellen på ett ark før du bruker tid på detaljer — og du har allerede sikret deg poengene for strukturen.

Skjelett

Klassene med instansvariabler og metodesignaturer, men uten ferdige metodekropper:

class Rett:
    def __init__(self, navn, pris):
        # setter _navn, _pris, _solgte = 0

    def selg(self, antall):
        # oeker _solgte

    def omsetning(self):
        # returnerer pris ganger solgte

Blokken er et skjelett med kommentarer i stedet for kode, og er derfor merket som ikke-kjørbar — Python krever en kropp i hver metode.

Skjelettet er verdt poeng i seg selv. Rekker du ikke å fylle inn alle kroppene, viser det likevel at du har forstått modellen, og deloppgavene rettes hver for seg.

På kladdepapiret holder det med enda mindre: klassenavn, en strek under, og
metodenavnene i en liste.

Metodesignatur

Navnet på en metode pluss parameterne den tar: selg(self, antall).

Signaturen er det den som kaller, trenger å vite. Er den riktig, kan resten av programmet skrives selv om kroppen er tom — og på eksamen kan du bruke en metode oppgaveteksten beskriver uten selv å ha rukket å skrive den.

Det er en av de viktigste taktiske rådene i hele boka: stå aldri fast. Sier oppgaven at klassen har en metode hent_ledige(), kan du kalle den i deloppgave d) selv om du hoppet over den i deloppgave b).

✏️Eksempel 2: Skjelett først, kropper etterpå

Vis framgangsmåten på spesifikasjonen fra oppgave 1: sett opp skjelettet for
Kantine og Rett, og fyll deretter inn kroppene.

Skjelettet først, slik det ville sett ut på kladdepapiret:

Rett
  __init__(navn, pris)      _navn, _pris, _solgte = 0 hent_navn()
  hent_solgte()
  selg(antall)
  omsetning()
  __str__()

Kantine
  __init__(navn)            _navn, _retter = []
  legg_til(rett)
  skriv_alle()
  samlet_omsetning()

Blokken er en oppstilling, ikke et program, og er derfor merket som ikke-kjørbar.

Så kroppene, én metode om gangen:

class Rett:
    def __init__(self, navn, pris):
        self._navn = navn self._pris = pris self._solgte = 0

    def hent_navn(self):
        return self._navn

    def hent_solgte(self):
        return self._solgte

    def selg(self, antall):
        self._solgte = self._solgte + antall

    def omsetning(self):
        return self._pris * self._solgte

    def __str__(self):
        return self._navn + ": " + str(self._pris) + " kr x" + str(self._solgte)

class Kantine:
    def __init__(self, navn):
        self._navn = navn self._retter = []

    def legg_til(self, rett):
        self._retter.append(rett)

    def skriv_alle(self):
        for r in self._retter:
            print(r)

    def samlet_omsetning(self):
        total = 0 for r in self._retter:
            total = total + r.omsetning()
        return total

k = Kantine("Fredrikke")
suppe = Rett("Fiskesuppe", 89)
k.legg_til(suppe)
k.legg_til(Rett("Vafler", 35))
suppe.selg(14)
k.skriv_alle()
print("Omsetning:", k.samlet_omsetning())

Utskrift:

Fiskesuppe: 89 kr x14
Vafler: 35 kr x0
Omsetning: 1246

Legg merke til at omsetningen regnes to steder på to nivåer. Rett vet hva
én rett har omsatt for; Kantine summerer det rettene svarer. Ytterklassen
regner aldri selv med r._pris * r._solgte — den spør.

Og legg merke til navnene: omsetning() på retten, samlet_omsetning()
kantina. Containerens metode har alltid et annet navn enn elementets, etter regelen fra kap. 5.3.

Slik ville sensor sett på det: skjelettet er verdt poeng før en eneste kropp
er skrevet. Rekker du bare halve modellen, teller den halvparten fullt ut.

📝Oppgave 2
Eksamensnivå, sjanger F

Les spesifikasjonen:

> En sykkelutleie har flere stativer. Hvert stativ har et navn, et antall plasser
> og en samling sykler som står der nå. Hver sykkel har en kode og et antall
> turer som starter på null. En sykkel kan leies ut, som øker antall turer. Et
> stativ kan sette inn en sykkel og si hvor mange plasser som er ledige.

a) Hvilke klasser skal du lage, og hvilke instansvariabler får hver av dem?

b) Sett opp skjelettet med metodesignaturer, uten kropper.

c) Hvilken klasse hører metoden «leies ut» hjemme i, og hvorfor?

📝Oppgave 3
Eksamensnivå, sjanger F

En medstudent har lest denne spesifikasjonen:

> En brettspillklubb har medlemmer. Hvert medlem har et navn og en samling spill.
> Hvert spill har en tittel og et antall spillere det passer for.

og laget én klasse:

class Klubb:
    def __init__(self, navn, spilltittel, maks):
        self._medlemsnavn = navn self._spilltittel = spilltittel self._maks = maks

a) Hva er galt med modellen?

b) Hvilke klasser skulle det vært, og med hvilke instansvariabler?

c) Gi et konkret eksempel på noe modellen over ikke kan uttrykke.

Løkke 3 — Hvor mange av hver? (~14 min)

Når klassene er funnet, gjenstår ett spørsmål per forbindelse mellom dem:
hvor mange?

Har hver bok én forfatter, eller mange? Har hvert bibliotek én bok, eller mange? Svaret bestemmer om instansvariabelen blir ett objekt eller en samling — og det er den siste beslutningen du tar før koden skrives.

Multiplisitet

Hvor mange objekter av den ene klassen hvert objekt av den andre er knyttet til.

To former, og bare to i denne boka:

- Én-til-én: instansvariabelen peker på ett objekt.
self._eier = person.
- Én-til-mange: instansvariabelen er en samling av objektreferanser.
self._boker = [] eller self._boker = {}.

Les teksten sakte. «Hvert stativ har en samling sykler» er én-til-mange. «Hver konto har en eier» er én-til-én.

Ordene som avslører én-til-mange: flere, en samling, alle, hver av, flertallsform.

Liste eller ordbok

Valget innenfor én-til-mange, og det avgjøres av ett spørsmål: skal noe slås opp på et navn eller en kode?

Er svaret ja — «finne en bok på tittel», «hente salen med et gitt navn» — er det en ordbok, med navnet som nøkkel og objektet som verdi.

Er svaret nei — du skal bare gå gjennom alle, telle eller skrive ut — er en
liste enklere.

I tvil: begynn med en liste. Å lete gjennom en liste med en løkke er alltid mulig, og det gir uttelling. Ordboka er bare raskere og ryddigere når oppslaget er hovedjobben.

Objektreferanser, ikke rådata

Samlingen skal holde objektene, ikke opplysninger om dem.

self._boker.append(bok) — ikke self._boker.append(bok.hent_tittel()).

Grunnen er at du mister metodene. Har du bare titlene, kan biblioteket ikke lenger be en bok om å låne seg ut, skrive seg selv eller si hvem forfatteren er — og alle de neste deloppgavene faller sammen.

Det er en av de faste vurderingene i Oppgave 4, og den dyreste enkeltbeslutningen i hele oppgaven.

✏️Eksempel 3: Multiplisitet i praksis

Vis forskjellen mellom én-til-én og én-til-mange i den samme modellen, med en kollektivrute som domene.

Spesifikasjonen:

> Et kollektivselskap har flere ruter. Hver rute har et nummer, én sjåfør, og
> en samling holdeplasser. En sjåfør har et navn. En holdeplass har et navn og et
> sonenummer.

Tre klasser, og to ulike forbindelser. Ruten har én sjåfør (én-til-én) og
mange holdeplasser (én-til-mange):

class Sjafor:
    def __init__(self, navn):
        self._navn = navn

    def hent_navn(self):
        return self._navn

class Holdeplass:
    def __init__(self, navn, sone):
        self._navn = navn self._sone = sone

    def hent_sone(self):
        return self._sone

    def __str__(self):
        return self._navn + " (sone " + str(self._sone) + ")"

class Rute:
    def __init__(self, nummer, sjafor):
        self._nummer = nummer self._sjafor = sjafor self._holdeplasser = []

    def legg_til(self, h):
        self._holdeplasser.append(h)

    def antall_holdeplasser(self):
        return len(self._holdeplasser)

    def skriv_alle(self):
        print("Rute", self._nummer, "med", self._sjafor.hent_navn())
        for h in self._holdeplasser:
            print(" -", h)

r = Rute(20, Sjafor("Ada"))
r.legg_til(Holdeplass("Blindern", 1))
r.legg_til(Holdeplass("Toyen", 1))
r.legg_til(Holdeplass("Kalbakken", 2))
r.skriv_alle()
print("Antall:", r.antall_holdeplasser())

Utskrift:

Rute 20 med Ada
 - Blindern (sone 1)
 - Toyen (sone 1)
 - Kalbakken (sone 2)
Antall: 3

De to formene står side om side i konstruktøren. self._sjafor = sjafor er
ett objekt; self._holdeplasser = [] er en samling. Ordet «én» i
spesifikasjonen er hele forskjellen.

Legg merke til self._sjafor.hent_navn(). Ruten spør sjåføren om navnet i
stedet for å lagre det selv. Hadde konstruktøren skrevet
self._sjafornavn = sjafor.hent_navn(), ville navnet blitt foreldet i det øyeblikket sjåføren skiftet navn — og det er nettopp poenget med å lagre referanser.

Hvorfor liste og ikke ordbok her? Fordi teksten aldri ber om å slå opp en
holdeplass på navn. Skulle en senere deloppgave gjøre det, ville en ordbok vært bedre.

— naturlig pausepunkt —

📝Oppgave 4
Eksamensnivå, sjanger F

For hver forbindelse under:
er den én-til-én eller én-til-mange, og hvordan skrives instansvariabelen?

a) «Hver konto har en eier.»

b) «Et treningssenter har flere saler.»

c) «Hvert medlem har en samling økter det har deltatt på.»

d) «Hver bok har en forfatter, og biblioteket skal kunne finne en bok på
tittel.»

📝Oppgave 5
Eksamensnivå, sjanger F

Les spesifikasjonen:

> En resirkuleringsstasjon har et sted og flere containere. Hver container har et
> materiale, en kapasitet i kilo og en fyllingsgrad i kilo som starter på null.
> En container kan ta imot avfall, men bare hvis det er plass til alt. Stasjonen
> kan sette inn en container, skrive ut alle, og si hvor mange containere som er
> mer enn 80 prosent fulle.

a) Sett opp de to klassene med instansvariabler og metodesignaturer.

b) Skriv klassene ferdig.

c) Test med tre containere, der ett forsøk på å ta imot avfall avvises.

Løkke 4 — Antakelser, filer og hva som gir uttelling (~14 min)

De siste tre tingene er ikke kode, men de er poeng.

Rimelig antakelse dokumentert i kommentar

Eksamensoppgavene er ofte underspesifiserte med vilje, og egne rimelige forutsetninger er uttrykkelig tillatt — så lenge de dokumenteres.

Skriv én kommentarlinje der valget tas:

class Stativ:
    def __init__(self, navn, plasser):
        # forutsetter at et stativ ikke kan ha null plasser
        self._navn = navn self._plasser = plasser self._sykler = []

s = Stativ("Blindern", 12)
print("ok")

Utskrift:

ok

Dette er den ene legitime bruken av kommentarer i en besvarelse. Ellers
skriver boka ingen kommentarer: ingen som gjentar hva koden gjør, og ingen overskrifter.

Typiske ting å dokumentere: hva som skjer ved en manglende nøkkel, om en verdi kan være negativ, om en klasse teksten så vidt nevner, er tatt med eller ikke.

Klasser i egne filer

I et virkelig program legges hver klasse i sin egen fil, og filene hentes inn med en linje øverst i programmet:

from bok import Bok from bibliotek import Bibliotek

Blokken er merket som ikke-kjørbar: den forutsetter to filer som ikke finnes her.

Denne boka skriver alle klassene i samme blokk, slik at hvert eksempel kan
kjøres nøyaktig som det står. På eksamen er det heller ingen som teller filer — det som rettes, er klassene og metodene.

Nevner oppgaveteksten filstrukturen, skriv en kommentar om at hver klasse hører hjemme i sin egen fil. Det er nok.

Delkarakter per deloppgave

Oppgave 4 er delt i seks–sju deloppgaver, og hver av dem rettes for seg.

Konsekvensen er taktisk og viktig: stå aldri fast. Får du ikke til deloppgave c), hopp til d) — den rettes uansett, og den bygger sjelden på at c)
er skrevet.

Og du kan bruke en metode oppgaveteksten beskriver selv om du ikke har rukket å skrive den. Sier teksten at Bok har hent_tittel(), kall den. Sensor vet at den finnes, fordi oppgaven sa det.

Riktig struktur belønnes selv med småfeil. En løkke med en indeksfeil, en
glemt aksessor eller en litt klønete betingelse trekker lite når modellen er riktig.

✏️Eksempel 4: Full modelleringsoppgave på eksamensnivå

> Et treningssenter har et navn og flere saler. Hver sal har et navn, en
> kapasitet og en samling påmeldte medlemmer. Hvert medlem har et navn og et
> medlemsnummer. En sal kan melde på et medlem, men bare hvis det er ledig plass.
> Senteret kan legge til en sal, finne en sal på navn, og skrive ut alle salene
> med antall påmeldte.

Sett opp modellen og skriv den ferdig.

Steg 1 — klassene. Treningssenter, sal og medlem: tre klasser.

Steg 2 — instansvariablene.

KlasseInstansvariablerMerknad
Medlem_navn, _nummerbegge utenfra
Sal_navn, _kapasitet, _paameldtesiste er en tom liste
Senter_navn, _salerordbok, fordi teksten sier «finne en sal på navn»

Steg 3 — metodene. «Kan melde på», «kan legge til en sal», «kan finne en sal
på navn», «kan skrive ut alle salene».
Steg 4 — aksessorer. Sal trenger hent_navn(), fordi senteret bruker det
som nøkkel i ordboka. Medlem trenger ingen i denne omgangen — teksten ber bare om at medlemmet meldes på, ikke om at noe hentes ut av det. Ber en senere deloppgave om å skrive ut de påmeldte, legger du til hent_navn() og __str__ da.

Modellen ferdig. Alle tre klassene og en kjøring står i samme blokk, slik at
den kan kjøres nøyaktig som den står — i en besvarelse ville hver klasse hatt sin egen fil:

class Medlem:
    def __init__(self, navn, nummer):
        self._navn = navn self._nummer = nummer

class Sal:
    def __init__(self, navn, kapasitet):
        self._navn = navn self._kapasitet = kapasitet self._paameldte = []

    def hent_navn(self):
        return self._navn

    def hent_ledige(self):
        return self._kapasitet - len(self._paameldte)

    def meld_paa(self, medlem):
        if self.hent_ledige() < 1:
            print("Fullt i", self._navn)
        else:
            self._paameldte.append(medlem)

    def __str__(self):
        return self._navn + ": " + str(len(self._paameldte)) + " av " + str(self._kapasitet)

class Senter:
    def __init__(self, navn):
        self._navn = navn self._saler = {}

    def legg_til(self, sal):
        self._saler[sal.hent_navn()] = sal

    def finn(self, navn):
        if navn in self._saler:
            return self._saler[navn]
        return None

    def skriv_alle(self):
        print("Senter:", self._navn)
        for navn in self._saler:
            print(" -", self._saler[navn])

s = Senter("Toyen")
s.legg_til(Sal("Speilsal", 2))
s.legg_til(Sal("Spinning", 10))
speil = s.finn("Speilsal")
speil.meld_paa(Medlem("Ada", 104))
speil.meld_paa(Medlem("Nora", 105))
speil.meld_paa(Medlem("Per", 106))
s.skriv_alle()
print(s.finn("Basseng"))
Utskrift:

Fullt i Speilsal
Senter: Toyen
 - Speilsal: 2 av 2
 - Spinning: 0 av 10
None
Slik ville sensor sett på det, deloppgave for deloppgave.

Klassene og konstruktørene (typisk 10–12 p): tre klasser, riktige

instansvariabler, alle satt med self._, og de to samlingene med riktig starttype.
«En-til-mange» som ordbok (6–8 p): self._saler = {}, innsettingen med

salens egen aksessor som nøkkel, og oppslaget med if navn in ...-sjekk.

Kontrollen i meld_paa (5–7 p): sjekken inne i metoden, else-grenen som
faktisk melder på, og at ingen meldes på når det er fullt.

Delegeringen i skriv_alle (4–5 p): løkka over nøklene, og print som lar
salen vise seg selv.

__str__ (3–4 p): returnerer en tekst, med str(...) rundt tallene.
Riktig struktur belønnes selv med småfeil. Hadde hent_ledige brukt <= 1 i

stedet for < 1, ville modellen fortsatt vært riktig, og trekket lite — men salen ville vært «full» ved én ledig plass.

📝Oppgave 6
Eksamensnivå, sjanger F

Les spesifikasjonen:

> En frivilligsentral har et sted og flere oppdrag. Hvert oppdrag har en tittel,
> et antall timer og én ansvarlig frivillig. Hver frivillig har et navn og et
> telefonnummer. Sentralen kan legge til et oppdrag, finne et oppdrag på tittel,
> og si hvor mange timer det er registrert til sammen.

a) Hvilke tre klasser, og hvilke instansvariabler?

b) Hvilken forbindelse er én-til-én, og hvilken er én-til-mange?

c) Skal samlingen være en liste eller en ordbok? Begrunn.

📝Oppgave 7
Eksamensnivå, sjanger F

Les spesifikasjonen:

> En brettspillklubb har et navn, flere medlemmer og flere spill. Hvert medlem
> har et navn og et antall kvelder det har deltatt på, som starter på null. Hvert
> spill har en tittel, et antall spillere det passer for, og én eier, som er
> et medlem. Et medlem kan registrere en kveld. Klubben kan melde inn et medlem,
> legge til et spill, finne et spill på tittel, og skrive ut alle spillene med
> eier.

a) Sett opp skjelettet for de tre klassene.

b) Skriv klassene ferdig, med ordbok for spillene og liste for medlemmene.

c) Test modellen: to medlemmer, tre spill, én kveld registrert, ett oppslag
som lykkes og ett som ikke gjør det.

d) Hvilken instansvariabel er en én-til-én-forbindelse, og hva ville gått tapt
om du hadde lagret eierens navn som tekst i stedet?

📝Oppgave 8
Eksamensnivå, sjanger I

En medstudent har modellert denne spesifikasjonen:

> Et bibliotek har flere bøker. Hver bok har en tittel og en forfatter.
> Biblioteket kan legge til en bok og skrive ut alle bøkene.

slik:

class Bibliotek:
    def __init__(self):
        self._titler = []
        self._forfattere = []

    def legg_til(self, tittel, forfatter):
        self._titler.append(tittel)
        self._forfattere.append(forfatter)

    def skriv_alle(self):
        for i in range(len(self._titler)):
            print(self._titler[i], "av", self._forfattere[i])

a) Hva er den grunnleggende modelleringsfeilen?

b) Gi et konkret moteksempel: en operasjon spesifikasjonen ikke nevner,
men som en senere deloppgave lett kunne be om, og som modellen ikke tåler.

c) Skriv den riktige modellen.

API- og konstruksjonsliste

Begrepsbank

Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder kjernestoffet.

Spesifikasjon

Den tekstlige beskrivelsen Oppgave 4 starter med — noen få avsnitt som forteller hvilke ting som finnes, hva de har, og hva de kan gjøre.

Den ser ut som løpende tekst, men er en oppskrift. Substantivene gir klassene, egenskapene gir instansvariablene, «kan gjøre»-setningene gir metodene.

Les den to ganger før du skriver noe. Første gang for å forstå domenet, andre gang med blyant for å stryke under.

Modellering

Å komme fra spesifikasjonen til et klasseoppsett: hvilke klasser, hva hver av dem husker, hva de kan gjøre, og hvordan de er knyttet sammen.

Det er det første steget i Oppgave 4, og det avgjør de seks–sju deloppgavene under. Bommer du her, drar feilen med seg alt.

Steget tar tre–fire minutter og er verdt poeng i seg selv — selv om du ikke rekker å fylle inn alle metodekroppene.

Substantivregelen

Hvert substantiv som er en ting med egne egenskaper, blir en klasse.

Bok har tittel og forfatter — en ting. Tittel har ingen egenskaper — en
egenskap.

Tommelfingerregelen: kan du tenke deg flere av den, og har hver av dem sine egne opplysninger, er det en klasse. Eksamensoppgavene har typisk tre til fem.

«Kan gjøre»-regelen

Hver setning i spesifikasjonen som sier at noe kan gjøres, blir en metode.

«En bok kan lånes ut» gir laan_ut(self). «Klubben kan finne et spill på tittel»
gir finn_spill(self, tittel).

Trenger metoden en verdi utenfra, blir den en parameter. Metodenavn skrives med små bokstaver, gjerne som verb, på norsk uten æ, ø og å.

Hvor en metode hører hjemme

I den klassen som eier opplysningene metoden endrer.

Utlånsstatusen bor i Bok, så laan_ut hører hjemme der. Legger du den i
Bibliotek, må den røre bok._utlaant direkte — feilkode #11.

Spør «hvem sine tall er dette?», så plasserer metoden seg selv. Det er den enkleste av alle modelleringsreglene, og den som oftest avgjør et plasseringsspørsmål.

Skjelett

Klassene med instansvariabler og metodesignaturer, uten ferdige kropper.

Skriv det først, hver gang. Da ser du hele modellen før du bruker tid på detaljer, og du har allerede sikret poengene for strukturen.

På kladdepapiret holder klassenavn, en strek under, og metodenavnene i en liste.
Det tar ett minutt.

Multiplisitet

Hvor mange objekter av den ene klassen hvert objekt av den andre er knyttet til.

Én-til-én: self._eier = person — ett objekt.
Én-til-mange: self._boker = [] eller {} — en samling.

Ordene avgjør: «hver konto har en eier» mot «senteret har flere saler».
Les den setningen to ganger.

Liste eller ordbok

Ett spørsmål: skal noe slås opp på et navn eller en kode?

Ja: ordbok, med navnet som nøkkel og objektet som verdi. Nei: liste.

I tvil, velg liste. Å lete gjennom en liste med en løkke er alltid mulig og gir uttelling; ordboka er bare ryddigere når oppslaget er hovedjobben.

Objektreferanse i samlingen

Samlingen holder objektene, ikke opplysninger om dem:
self._boker.append(bok), ikke self._boker.append(bok.hent_tittel()).

Lagrer du rådata, mister du metodene, og alle senere deloppgaver — «skriv ut alle», «finn den beste», «lån ut» — faller sammen.

Det er den dyreste enkeltbeslutningen i hele Oppgave 4.

Parallelle lister

Anti-mønsteret: to lister som holder opplysninger om det samme, og som må holdes i takt manuelt.

self._titler og self._forfattere virker helt til noen fjerner ett element fra den ene. Da påstår programmet at feil forfatter skrev boka — uten en eneste feilmelding.

Løsningen er alltid den samme: én klasse som holder begge opplysningene, og én liste av de objektene.

Startverdi i konstruktøren

En instansvariabel som ikke kommer utenfra: self._solgte = 0,
self._utlaant = False, self._boker = [].

Kjenn dem igjen i teksten på «starter på null», «er ikke utlånt når den registreres» og «har ingen … til å begynne med».

De er det som oftest glemmes, og de er et selvstendig poeng. Uten dem finnes ikke variabelen, og første metode som leter etter den, stopper programmet.

Rimelig antakelse

Eksamensoppgavene er ofte underspesifiserte med vilje, og egne rimelige forutsetninger er uttrykkelig tillatt.

Dokumentér dem med én kommentarlinje der valget tas:
# forutsetter at et stativ ikke kan ha null plasser.

Typiske ting å dokumentere: hva som skjer ved en manglende nøkkel, om en verdi kan være negativ, og om en klasse teksten så vidt nevner, er tatt med.

Kommentarer i en besvarelse

Boka skriver ingen kommentarer, med ett unntak: den dokumenterte antakelsen.

Ingen kommentarer som gjentar hva koden gjør, ingen overskrifter, ingen seksjonsstreker. De tar plass og gir ingen uttelling.

Velskrevne metodenavn gjør jobben bedre enn en kommentar: hent_ledige() sier mer enn # regner ut ledige plasser.

Klasser i egne filer

I et virkelig program bor hver klasse i sin egen fil, og filene hentes inn med en linje øverst i programmet.

Denne boka skriver alle klassene i samme blokk, slik at hvert eksempel kan kjøres nøyaktig som det står.

På eksamen teller ingen filer. Nevner oppgaveteksten filstrukturen, skriv en kommentar om at hver klasse hører hjemme i sin egen fil.

Delkarakter per deloppgave

Oppgave 4 er delt i seks–sju deloppgaver, og hver av dem rettes for seg.

Konsekvensen er taktisk: stå aldri fast. Får du ikke til c), hopp til d) — den rettes uansett.

Og du kan bruke en metode oppgaveteksten beskriver selv om du ikke har rukket å skrive den. Sier teksten at klassen har hent_ledige(), kall den.

Riktig struktur belønnes

En løkke med en indeksfeil, en glemt aksessor eller en litt klønete betingelse trekker lite når modellen er riktig.

Det motsatte gjelder også: perfekt skrevne metoder på en gal modell hjelper ikke, fordi feilen gjentar seg i hver deloppgave.

Bruk derfor de første minuttene på modellen, ikke på å pusse den første metodekroppen.

Å velge hvilke aksessorer som trengs

Skriv dem oppgaven faktisk bruker, ikke alle mulige.

En ytterklasse som setter inn i en ordbok, trenger elementets hent_navn(). En utskriftsmetode som delegerer, trenger ingen aksessor i det hele tatt.

En klasse med åtte unødvendige sett-metoder er dårligere enn en med to nødvendige: hver overflødig mutator åpner et hull i kontrollen.

Aksessor som regner ut

En aksessor trenger ikke å returnere en lagret verdi — den kan regne den ut:
return self._kapasitet - len(self._paameldte).

For den som bruker klassen, er det ingen forskjell, og det er poenget.
Grensesnittet lover et tall, ikke en bestemt lagringsmåte.

Å regne ut i stedet for å lagre er dessuten tryggere: verdien kan aldri komme i utakt med de tallene den bygger på.

Tre til fem klasser

Størrelsen på en typisk Oppgave 4.

Har du funnet to, har du sannsynligvis slått sammen noe — se etter en «boks»
som er blitt til to instansvariabler i stedet for en klasse.

Har du funnet åtte, har du gjort egenskaper om til klasser. En Tittel-klasse med bare en tekst i seg er ren støy.

Rekkefølgen du skriver i

Modell, skjelett, kropper — i den rekkefølgen.

Modellen er tre–fire minutter med blyant. Skjelettet er ett minutt til. Først da begynner du å skrive kode, og da vet du hvor hver metode hører hjemme.

Motsatt rekkefølge — å skrive ferdig kode på den første klassen med én gang — er den vanligste tidsfellen i hele eksamen.

Repetisjon — de åtte setningene som må sitte

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.

Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.