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.
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 själv
Övning 1: En metod som ärvs
Skapa basklassen Animal med metoden breathe(). Skapa subklassen
Dog med den egna metoden wag_tail(). Skapa ett hundobjekt och anropa
både den ärvda och den egna metoden.
Fundera: I vilken klass är breathe()
skriven?
När du har försökt själv: visa lösningsförslaget till övning 1 på GitHub.
Övning 2: Skriv över en metod
Skapa basklassen Animal med metoden speak(). Skapa subklasserna
Dog och Cat och skriv en egen version av speak() i varje
subklass. Anropa metoden på alla tre typerna av objekt.
Fundera: Vilka två metoder är exempel på override?
När du har försökt själv: visa lösningsförslaget till övning 2 på GitHub.
Övning 3: Återanvänd konstruktorn med super()
Skapa basklassen Person med attributet name. Skapa subklassen
Student med det extra attributet school. Använd
super().__init__(name) och skriv över metoden introduce().
Fundera: Vilken kod slipper du upprepa tack vare
super()?
När du har försökt själv: visa lösningsförslaget till övning 3 på GitHub.
Övning 4: Polymorfism i en lista
Skapa basklassen Instrument och subklasserna Guitar, Piano
och Drum. Alla ska ha metoden play() med olika utskrift. Lägg ett objekt
av varje subklass i en lista och anropa play() i en loop.
Fundera: Varför behöver loopen inte känna till objektens exakta klasser?
När du har försökt själv: visa lösningsförslaget till övning 4 på GitHub.
Övning 5: Fordonsregister
Svårare övning: Här kombinerar du arv, override,
super(), listor och polymorfism.
Skapa Vehicle med attributet brand och metoden drive().
Skapa Car, Bike och Bus som använder basklassens konstruktor
och skriver över metoden. Lägg blandade fordon i en lista och låt alla köra i samma loop.
Fundera: Vad behöver ändras i loopen om ytterligare en subklass läggs till?
När du har försökt själv: visa lösningsförslaget till övning 5 på GitHub.
Övning 6: Löner för olika anställda
Svårare övning: Här använder du polymorfism för en beräkning som skiljer sig mellan flera subklasser.
Skapa basklassen Employee med namn och metoden calculate_salary().
Skapa MonthlyEmployee, som returnerar en månadslön, och
HourlyEmployee, som beräknar timlön gånger antal timmar. Använd
super(), lägg blandade objekt i en lista och skriv ut varje lön samt den totala
lönekostnaden med samma metodanrop.
Fundera: Vilken del är override och vilken del visar polymorfism?
När du har försökt själv: visa lösningsförslaget till övning 6 på GitHub.
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.