Programmering nivå 2

Kap 3.6 – Arv och polymorfism

Bygg vidare på klasser och låt samma metodnamn ge olika beteenden.

Mål med lektionen

När du har arbetat klart med denna lektion ska du:

  • Förstå vad arv innebär i objektorienterad programmering.
  • Kunna skapa en klass som ärver från en annan klass.
  • Förstå grunderna i polymorfism och hur metoder kan överskuggas, alltså override.

Så här lär du dig bäst

Titta på hur du kan bygga vidare på en redan existerande klass. Börja med ett enkelt exempel och försök sedan skriva en egen basklass och subklass. Kör koden och se hur objekten beter sig beroende på vilken klass de tillhör. Läs gärna vidare om super() när du vill fördjupa dig.

Viktigt fokus: använd inte arv bara för att det går. Arv passar när en subklass verkligen är en mer specialiserad version av basklassen, till exempel att Dog är en typ av Animal.

Centrala begrepp

  • Arv: en klass, subklassen, kan ärva attribut och metoder från en annan klass.
  • Superklass eller basklass: den ursprungliga klassen vars beteenden ärvs.
  • Subklass: en ny klass som bygger vidare på en annan.
  • Override: en metod i subklassen ersätter metoden i basklassen.
  • Polymorfism: samma metodnamn kan användas på olika objekt och ge olika resultat.

Begreppen förklarade med exempel

Arv

Arv betyder att en klass kan få egenskaper och metoder från en annan klass. Om flera klasser har något gemensamt kan du lägga det gemensamma i en basklass. Då slipper du skriva samma kod flera gånger.

class Animal:
    def breathe(self):
        print("Djuret andas.")

class Dog(Animal):
    def wag_tail(self):
        print("Hunden viftar på svansen.")

dog = Dog()
dog.breathe()

Klassen Dog har en egen metod, wag_tail(), men ingen egen breathe(). Objektet kan ändå använda breathe() eftersom Dog ärver från Animal.

UML-diagrammet visar arvet med en pil från subklassen till basklassen.

classDiagram
    direction BT
    Dog --> Animal : inherits

    class Animal {
        +breathe()
    }

    class Dog {
        +wag_tail()
    }

Superklass eller basklass

Superklassen, även kallad basklassen, är klassen som andra klasser ärver från. I exemplet ovan är Animal superklassen. Den beskriver sådant som kan gälla för flera sorters djur.

class Animal:
    def speak(self):
        print("Djuret låter.")

Man kan tänka att Animal är den allmänna modellen. Den behöver inte beskriva exakt hur varje djur låter, men den ger en gemensam struktur.

Subklass

En subklass är en klass som bygger vidare på en superklass. I Python skriver du superklassens namn inom parentes efter subklassens namn.

class Dog(Animal):
    def wag_tail(self):
        print("Hunden viftar på svansen.")

Här betyder Dog(Animal) att Dog är en subklass till Animal. Dog kan använda metoder från Animal, men kan också få egna metoder eller skriva om ärvda metoder.

classDiagram
    direction BT
    Dog --> Animal : inherits

    class Animal {
        +speak()
    }

    class Dog {
        +wag_tail()
    }

Override

Override betyder att en subklass skriver en egen version av en metod som redan finns i basklassen. Metoden har samma namn, men beteendet blir annorlunda.

class Animal:
    def speak(self):
        print("Djuret låter.")

class Dog(Animal):
    def speak(self):
        print("Hunden säger voff.")

När speak() körs på ett Dog-objekt används hundens version. Basklassens metod finns fortfarande, men subklassens metod tar över för just Dog.

Polymorfism

Polymorfism betyder att samma metodanrop kan användas på flera olika objekt. Programmet behöver inte veta exakt vilken klass objektet har, så länge objektet har metoden som anropas.

animals = [Dog(), Cat(), Bird()]

for animal in animals:
    animal.speak()

Loopen kör alltid animal.speak(). Ändå blir utskriften olika, eftersom Dog, Cat och Bird har egna versioner av metoden.

classDiagram
    direction BT
    Dog --> Animal : inherits
    Cat --> Animal : inherits
    Bird --> Animal : inherits

    class Animal {
        +speak()
    }

    class Dog {
        +speak()
    }

    class Cat {
        +speak()
    }

    class Bird {
        +speak()
    }

Skillnaden mellan override och polymorfism

Override handlar om hur en subklass ändrar en metod. Polymorfism handlar om hur samma metodanrop kan fungera på flera olika objekt.

class Animal:
    def speak(self):
        print("Djuret låter.")

class Dog(Animal):
    def speak(self):
        print("Hunden säger voff.")

I koden ovan sker override, eftersom Dog skriver en egen version av speak(). Metoden i Animal finns kvar, men för Dog-objekt används hundens version.

animals = [Dog(), Animal()]

for animal in animals:
    animal.speak()

I loopen ovan ser du polymorfism. Samma rad, animal.speak(), körs för båda objekten. Resultatet blir ändå olika eftersom objekten tillhör olika klasser.

En enkel minnesregel är: override betyder att subklassen skriver om metoden. Polymorfism betyder att samma metodanrop kan ge olika beteende. Override gör ofta polymorfism möjlig.

Kan man ha override utan polymorfism?

Ja. Om du bara skapar ett Dog-objekt och kör metoden på det objektet har Dog fortfarande gjort override av speak(). Men du använder inte riktigt polymorfism, eftersom programmet inte behandlar flera olika objekt med samma metodanrop.

dog = Dog()
dog.speak()

Här sker override, men exemplet visar inte polymorfism i praktiken.

Kan man ha polymorfism utan arv?

Ja, i Python kan olika klasser ha samma metodnamn utan att ärva från samma basklass. Om programmet kan köra samma metodanrop på båda objekten är det fortfarande polymorfism.

class Dog:
    def speak(self):
        print("Voff")

class Robot:
    def speak(self):
        print("Beep")

things = [Dog(), Robot()]

for thing in things:
    thing.speak()

Dog och Robot ärver inte från samma klass. Ändå fungerar samma rad: thing.speak(). Det fungerar eftersom båda objekten har en metod som heter speak().

I den här kursens OOP-exempel kommer polymorfism ofta tillsammans med arv och override, men i Python är arv inte ett absolut krav för polymorfism.

Kort om andra språk: I vissa språk, till exempel Java och C#, används ofta interface för polymorfism utan arv från samma klass. I Python räcker det att objekten har den metod som anropas.

När passar arv?

Arv passar bäst när du kan säga "är en". En hund är ett djur. En rektangel är en form. En elev är en person. Då kan en subklass bygga vidare på en basklass.

Situation Passar arv? Varför?
Dog och Animal Ja En hund är ett djur.
Rectangle och Shape Ja En rektangel är en form.
Car och Engine Nej, oftast inte En bil är inte en motor. En bil har en motor.

Om relationen är "har en" i stället för "är en" är arv ofta fel modell. Då är det bättre att låta ett objekt innehålla ett annat objekt.

Arv i Python

Python använder parenteser för att visa att en klass ärver från en annan:

class Animal:
    def speak(self):
        print("Djuret låter.")

class Dog(Animal):
    def speak(self):
        print("Hunden säger voff.")

Användning:

animal = Animal()
dog = Dog()

animal.speak()  # Djuret låter.
dog.speak()     # Hunden säger voff.

Här ärver Dog från Animal. Parentesen i class Dog(Animal): visar arvet. Eftersom Dog har en egen metod som också heter speak() används hundens version när dog.speak() körs. Det är override.

Polymorfism i praktiken

animals = [Dog(), Animal()]

for animal in animals:
    animal.speak()

Alla objekt kan hanteras med samma metodanrop, men svarar på olika sätt beroende på vilken klass de tillhör. Variabeln animal får först hålla ett Dog-objekt och sedan ett Animal-objekt. Samma rad, animal.speak(), fungerar för båda.

Flera subklasser med polymorfism

class Cat(Animal):
    def speak(self):
        print("Katten säger mjau.")

class Bird(Animal):
    def speak(self):
        print("Fågeln kvittrar.")

animals = [Dog(), Cat(), Bird()]

for animal in animals:
    animal.speak()

I detta exempel är Dog, Cat och Bird olika subklasser till Animal. De har samma metodnamn, speak(), men varje klass har sin egen version. Det gör att listan kan innehålla olika typer av objekt utan att loopen behöver specialfall för hund, katt eller fågel.

Interaktiv övning: arv och polymorfism

Välj vilka objekt som ska finnas i listan och kör loopen. Alla objekt anropas med samma metod, speak(), men utskriften beror på vilken klass objektet tillhör.

Välj ett exempel och tryck på Kör exempel.

Programmering 2-koppling: Dog, Cat och Bird ärver från Animal, men skriver om speak(). Det är override. När loopen kör animal.speak() används rätt version automatiskt.

Beräkningsexempel med olika former

Det här exemplet visar polymorfism med ett mer programmeringsnära problem. Alla former har metoden area(), men varje form räknar ut arean på sitt eget sätt.

class Shape:
    def area(self):
        return 0

class Rectangle(Shape):
    def __init__(self, width, height):
        self._width = width
        self._height = height

    def area(self):
        return self._width * self._height

class Circle(Shape):
    def __init__(self, radius):
        self._radius = radius

    def area(self):
        return 3.14 * self._radius ** 2

shapes = [Rectangle(4, 5), Circle(3)]

for shape in shapes:
    print("Area:", shape.area())

Loopen behöver inte veta om objektet är en Rectangle eller en Circle. Den anropar bara shape.area(). Det är polymorfism i praktiken.

classDiagram
    direction BT
    Rectangle --> Shape : inherits
    Circle --> Shape : inherits

    class Shape {
        +area()
    }

    class Rectangle {
        -width
        -height
        +area()
    }

    class Circle {
        -radius
        +area()
    }

super(): utöka basklassens metod

class Person:
    def __init__(self, name):
        self.name = name

    def introduce(self):
        print(f"Jag heter {self.name}.")

class Student(Person):
    def __init__(self, name, school):
        super().__init__(name)
        self.school = school

    def introduce(self):
        super().introduce()
        print(f"Jag går på {self.school}.")

super() gör det möjligt att återanvända basklassens kod och sedan lägga till mer beteende i subklassen.

Utan super().__init__(name) hade Student behövt upprepa koden som sparar self.name. Med super() kan subklassen använda basklassens initiering och bara lägga till det som är nytt: self.school.

classDiagram
    direction BT
    Student --> Person : inherits

    class Person {
        +name
        +introduce()
    }

    class Student {
        +school
        +introduce()
    }

Öva: använd arv

Skapa filen kap3-6-vehicles.py. Målet är att träna på arv, override och polymorfism i samma program.

  1. Skapa en basklass Vehicle med metoden drive().
  2. Skapa två subklasser: Car och Bike, som överskuggar drive().
  3. Skapa en lista med både bilar och cyklar och låt dem köra i en loop.
  4. Lägg till attributet brand i basklassen och använd super().__init__(brand) i subklasserna.
  5. Testa att loopen kan köra vehicle.drive() utan att kontrollera om objektet är Car eller Bike.

När programmet fungerar ska du kunna lägga till ett nytt objekt i listan utan att ändra själva loopen.

När du har försökt själv kan du ladda ner ett lösningsförslag: kap3-6-vehicles-losningsforslag.py

Sammanfattning

  • Arv låter en klass återanvända kod från en annan.
  • Subklasser kan ändra hur en metod fungerar.
  • Polymorfism låter samma metodanrop bete sig olika beroende på objekt.
  • super() kan användas för att återanvända logik från basklassen.

Tillbaka till Kapitel 3