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: använd arv
Skapa filen kap3-6-vehicles.py. Målet är att träna på arv, override och polymorfism i
samma program.
- Skapa en basklass
Vehiclemed metodendrive(). - Skapa två subklasser:
CarochBike, som överskuggardrive(). - Skapa en lista med både bilar och cyklar och låt dem köra i en loop.
- Lägg till attributet
brandi basklassen och användsuper().__init__(brand)i subklasserna. - Testa att loopen kan köra
vehicle.drive()utan att kontrollera om objektet ärCarellerBike.
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.