Tilbake
1.5
Polymorfisme og abstraksjon

1.5 Polymorfisme og abstraksjon

Polymorfisme, abstrakte klasser og grensesnitt.

65 min
6 oppgaver
PolymorfismeAbstraksjonGrensesnittDuck typing
Du leser den lesevennlige versjonen
Din fremgang i kapitlet
0 / 6 oppgaver

Én knapp, mange resultater

Tenk på en universalfjernkontroll. Den har én «spill av»-knapp som fungerer enten du peker på TV-en, DVD-spilleren eller spillkonsollen. Knappen er den samme, men resultatet er forskjellig. Det er nettopp dette polymorfisme handler om. Ordet betyr bokstavelig «mange former» (gresk poly = mange, morph = form), og i programmering vil det si at objekter av ulike klasser kan behandles likt – for eksempel ved at vi kaller samme metode – men oppføre seg forskjellig.

Sammen med polymorfisme kommer abstraksjon, som handler om å definere hva noe skal gjøre uten å bestemme hvordan. Det er som en stillingsbeskrivelse: den sier hva jobben innebærer, men ikke nøyaktig hvordan hver ansatt løser oppgavene.

Det klassiske eksempelet er geometriske former. Vi kan lage en Sirkel, et Rektangel og en Trekant, der hver klasse har sin egen areal-metode. En sirkel regner math.pi * radius ** 2, et rektangel lengde * bredde, en trekant 0.5 * grunnlinje * høyde. Det fine kommer når vi samler dem i en liste og behandler dem likt:

former = [Sirkel(5), Rektangel(4, 6), Trekant(3, 8)]
for form in former:
    print(f"{form.beskrivelse()}: areal = {form.areal():.2f}")

Vi kaller areal() uten å bry oss om hvilken klasse objektet tilhører. Grensesnittet er det samme, men oppførselen er forskjellig.

Duck typing – om det kvakker som en and

Python er dynamisk typet. Det betyr at Python ikke sjekker hvilken klasse et objekt tilhører – den sjekker bare om objektet har metoden vi prøver å kalle. Dette kalles duck typing, etter uttrykket «hvis det går som en and, svømmer som en and og kvakker som en and, er det sannsynligvis en and». I praksis betyr det at du ikke trenger arv for å få polymorfisme i Python. Så lenge objektene har de riktige metodene, fungerer det.

Tre klasser Hund, Katt og Ku uten noen felles superklasse kan alle ha en lyd()-metode. En funksjon som går gjennom en liste og kaller lyd() på hvert dyr, spør egentlig bare: «Har du en lyd-metode?» Har den det, fungerer det. Men her ligger også risikoen: sender du inn et objekt som ikke har metoden, får du en AttributeError. Feilen oppdages først ved kjøring, ikke før. Det er prisen for fleksibiliteten i duck typing.

Det finnes altså to hovedformer for polymorfisme i Python: duck typing, der riktig metode er nok uavhengig av klasse, og metode-overstyring (override), der subklasser definerer sin egen versjon av en metode fra superklassen.

📝Oppgave Quiz 1

Abstrakte klasser og protokoller

Noen ganger vil vi lage en superklasse som aldri skal instansieres direkte. En generisk Form gir ikke mening alene – hva slags form er det? Den finnes bare for å definere et felles grensesnitt subklassene implementere. Til dette har Python modulen abc (Abstract Base Classes). En abstrakt klasse kan ikke instansieres, og en abstrakt metode, markert med @abstractmethod, har ingen implementasjon – subklasser må overstyre den:

from abc import ABC, abstractmethod

class Form(ABC):
    @abstractmethod
    def areal(self):
        pass

    def vis_info(self):  # vanlig metode, arves
        print(f"Areal: {self.areal():.2f}")

Hvis en subklasse glemmer å implementere en abstrakt metode, kan du ikke lage objekter av den – Python gir TypeError. Det er en kraftig sikkerhet: den garanterer at alle subklasser har funksjonaliteten vi forventer. Prøver du å lage Form() direkte, får du også TypeError.

I språk som Java og C# finnes egne grensesnitt (interfaces). Python har ikke et interface-nøkkelord, men to alternativer: abstrakte klasser, som vi nettopp så, og protokoller (Protocol fra typing). En protokoll definerer hvilke metoder et objekt må ha, uten å kreve arv – det er en formalisering av duck typing. Forskjellen er viktig: abstrakte klasser krever arv og gir feil ved kjøring, mens protokoller ikke krever arv og primært sjekkes av typesjekkere som mypy.

📝Oppgave Quiz 2

Åpen for utvidelse, lukket for endring

Polymorfisme og abstraksjon gjør det mulig å følge et viktig designprinsipp: åpen/lukket-prinsippet. Det sier at programvare skal være åpen for utvidelse, men lukket for endring. Med andre ord: vi skal kunne legge til ny funksjonalitet uten å rote i eksisterende kode.

Tenk på et betalingssystem. En dårlig løsning er en lang if/elif-kjede som sjekker betalingstype – «kort», «vipps», «kontant». Problemet er at hver gang du legger til en ny betalingsmåte, må du endre den eksisterende funksjonen. En if/elif-kjede som sjekker typer er ofte et varseltegn på nettopp dette. En god løsning bruker en abstrakt Betaling-klasse med en betal-metode, og én subklasse per betalingsmåte. Vil du legge til kryptovaluta, lager du bare en ny KryptoBetaling-klasse – ingen eksisterende kode røres.

Polymorfisme er heller ikke noe vi bare lager selv; det er innebygd i Python via dunder-metoder (dobbel understrek). len() bruker __len__(), print() bruker __str__(), og in bruker __contains__(). Implementerer du disse i dine egne klasser, blir objektene kompatible med Pythons innebygde funksjoner. En Handlekurv med __len__, __str__ og __contains__ lar deg skrive len(kurv), print(kurv) og "Melk" in kurv – ren polymorfisme i praksis.

📝Oppgave Quiz 3

Oppsummering

Vi begynte med én knapp som styrer mange apparater, og det er kjernen i polymorfisme: ulike objekter behandles likt, men oppfører seg forskjellig basert på sin klasse. I Python får vi dette gratis gjennom duck typing – Python sjekker ikke klasse, bare om objektet har riktig metode – og gjennom metode-overstyring.

Abstrakte klasser (via abc) kan ikke instansieres og tvinger subklasser til å implementere bestemte metoder, markert med @abstractmethod. Protokoller er et mer moderne alternativ som definerer grensesnitt uten arv, primært for typesjekkere. Alt dette gjør at vi kan følge åpen/lukket-prinsippet: kode skal være åpen for utvidelse, men lukket for endring – nye klasser i stedet for endrede if/elif-kjeder. Til slutt så vi at dunder-metoder som __len__, __str__ og __contains__ lar våre egne objekter spille på lag med Pythons innebygde funksjoner. Med dette har du fullført grunnlaget i objektorientert programmering.

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.