Tilbake
22

22 Objektorientert programmering

Grunnleggende OOP-konsepter: klasser, objekter, metoder og arv.

70 min
7 oppgaver
KlasseObjektAttributtMetodeKonstruktør
Du leser den lesevennlige versjonen
Din fremgang i kapitlet
0 / 7 oppgaver

Kode som ligner virkeligheten

Tenk på sensorene i et smarthus: en temperatursensor på stua, en til på badet, en fuktighetssensor i kjelleren. De er alle «sensorer» -- de har de samme egenskapene og kan gjøre de samme tingene -- men hver av dem har sine egne verdier akkurat nå. Hvordan ville du representert dette i kode på en ryddig måte?

Svaret er objektorientert programmering, ofte forkortet OOP. Det er en måte å strukturere kode på ved å modellere «ting» fra den virkelige verden som objekter. I dette kapittelet skal vi lære å tenke på denne måten. Vi starter med de to grunnbegrepene -- klassen, som er oppskriften, og objektet, som er den konkrete tingen. Vi ser hvordan objekter får egenskaper (attributter) og handlinger (metoder), og hvordan den mystiske self binder det hele sammen. Til slutt møter vi arv og polymorfisme, som lar oss bygge nye klasser oppå gamle uten å gjenta oss selv. OOP er kanskje den mest naturlige måten å tenke programmering på -- for verden er jo full av ting.

Klasser og objekter

La oss begynne med det viktigste skillet i hele OOP: forskjellen på en klasse og et objekt. En klasse er en oppskrift eller mal -- den definerer hvilke egenskaper og handlinger noe skal ha, men er ikke en ting i seg selv. Et objekt (også kalt en instans) er en konkret ting laget fra klassen, med sine egne verdier. En god analogi: «Hund» er en klasse, mens «Fido» og «Rex» er objekter av den klassen. Klassen sier at hunder har navn og alder; objektene har de bestemte verdiene.

I elektronikkens verden kunne vi hatt en klasse Sensor, og objektene temperatursensor1, temperatursensor2 og fuktighetssensor1. Objektenes egenskaper kaller vi attributter -- som sensor.verdi = 23.5 eller sensor.type = "temperatur" -- og handlingene de kan utføre kaller vi metoder, som sensor.les() eller sensor.kalibrer().

I Python definerer vi en klasse med nøkkelordet class. En spesiell metode kalt __init__ er konstruktøren -- den kjøres automatisk hver gang vi lager et nytt objekt, og setter opp startverdiene. Skriver vi temp_sensor = Sensor("T001", "temperatur"), kjøres konstruktøren og objektet får sine attributter. Deretter kan vi bruke metodene, som temp_sensor.sett_verdi(23.5).

En detalj forvirrer mange i starten: parameteret self. Det er rett og slett en referanse til objektet selv, og det må være første parameter i alle metoder. Når du skriver temp_sensor.les_verdi(), sender Python automatisk objektet inn som self, slik at metoden vet hvilket objekts attributter den skal jobbe med. self.verdi betyr altså «dette objektets verdi».

📝Oppgave Quiz 1

Attributter -- objektets eget og det felles

Nå skal vi se nærmere på attributtene, for det finnes faktisk to slag. Instansattributter tilhører hvert enkelt objekt for seg. Skriver vi i konstruktøren self.pin = pin og self.hastighet = 0, så får hvert eneste motor-objekt sin egen pin og sin egen hastighet, uavhengig av de andre.

Men noen ganger vil vi at noe skal være felles for alle objektene av en klasse. Det kaller vi et klasseattributt. Tenk deg en Motor-klasse med et klasseattributt antall_motorer = 0, som økes med én hver gang konstruktøren kjører. Lager vi to motorer, vil Motor.antall_motorer vise 2 -- attributtet deles på tvers av alle objektene.

Det er også et poeng å verne om dataene. I Python markerer vi attributter vi mener er «private» ved å la navnet starte med en understrek, som self._saldo. Dette er en konvensjon, et signal om at man skal være forsiktig med å rote direkte i dem utenfra. I stedet lar vi metoder styre tilgangen -- en ta_ut-metode kan for eksempel sjekke at det er nok penger før den trekker fra.

En elegant videreføring av dette er property-dekoratøren, skrevet @property. Den gir kontrollert tilgang til attributter. Tenk på en Temperatur-klasse: med en property kan du lese t.celsius som om det var et vanlig attributt, men når du setter det med en setter, kan koden sjekke at verdien ikke er under absolutt nullpunkt på 273,15-273{,}15 °C. Du kan til og med lage en property fahrenheit som beregnes fra celsius hver gang du leser den. Slik kombinerer du enkel bruk med trygg kontroll.

📝Oppgave Quiz 2

Arv -- å bygge videre uten å gjenta seg

Vi kommer til OOPs kanskje mest kraftfulle idé. Tenk deg at du skal lage en temperatursensor-klasse og en fuktighetssensor-klasse. Begge trenger en sensor_id, begge har en verdi, begge kan leses. Skal du skrive den koden to ganger? Nei -- du bruker arv.

Med arv lager du først en baseklasse, for eksempel Sensor, som inneholder alt det felles. Deretter lar du TemperaturSensor og FuktighetsSensor arve fra Sensor. Da får de automatisk alle baseklassens attributter og metoder, og du legger bare til det som er spesielt for hver. En arvende klasse skrives class TemperaturSensor(Sensor):, der baseklassen står i parentes.

Når en arvende klasse har sin egen konstruktør, må den sørge for at baseklassens initialisering også kjøres. Det gjør den med super().__init__(), som kaller foreldreklassens konstruktør. Skriver TemperaturSensor sin super().__init__(sensor_id), blir self.sensor_id satt opp av Sensor, mens datterklassen i tillegg kan legge til sin egen enhet. Dette unngår kodeduplisering og holder hierarkiet logisk.

Den siste perlen er polymorfisme -- et fint ord for at objekter av forskjellige klasser kan behandles likt. Hvis både TemperaturSensor og FuktighetsSensor har en metode les_formatert(), kan vi skrive en funksjon som løkker gjennom en blandet liste av sensorer og kaller sensor.les_formatert() på hver -- og Python kaller automatisk den riktige versjonen for hver type. En temperatursensor gir «23.5 °C», en fuktighetssensor «65 %RH», men koden som styrer det er den samme. Slik gjør arv og polymorfisme det enkelt å utvide systemet med nye sensortyper uten å skrive om resten.

📝Oppgave Quiz 3

Oppsummering

Vi har lært å modellere virkeligheten i kode. Det grunnleggende skillet er mellom klassen, som er oppskriften, og objektet, som er den konkrete tingen med egne verdier. Objekter har attributter (egenskaper) og metoder (handlinger), og konstruktøren __init__ setter opp startverdiene. Parameteret self binder alt sammen ved å peke på objektet selv.

Vi så at attributter kan være instansattributter (egne for hvert objekt) eller klasseattributter (felles for alle), at en ledende understrek markerer noe som «privat», og at @property gir kontrollert tilgang. Til slutt møtte vi arv, der en datterklasse bygger videre på en baseklasse og bruker super().__init__(), og polymorfisme, der objekter av ulike klasser kan behandles likt og riktig metode kalles automatisk. Med disse verktøyene kan du bygge ryddige, utvidbare programmer som speiler den verdenen de skal styre.

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.