Arv#

Arv er når man har en mer generell klasse som man lager subklasser av. Dette er veldig nyttig når vi skal modellere ting med objekter.

Eksempel: Bompengesystem#

I dette eksempelet skal vi lage et bompengesystem for ulike kjøretøy. Ulike kjøretøy betaler ofte ulikt.

En bil er en form for kjøretøy. Hvis vi lager en klasse Kjøretøy, så vil vi at Bil skal arve fra denne klassen, fordi en bil også er et kjøretøy.

Bil er en subklasse av Kjøretøy, mens Kjøretøy er bilers superklasse.

class Kjøretøy:
    def __init__(self, merke):
        self.merke = merke 
    
    def tut(self):
        print("Tut!")

class Bil(Kjøretøy):
    def __init__(self, merke):
        super().__init__(merke) # Arver data og metoder fra Kjøretøy
        self.pris = 50

bil = Bil("Toyota")
bil.tut()
Tut!

Vi ser at alle Kjøretøy-objekter skal ha et merke og kan tute med tut(). Selv om metoden tut() ikke står under Bil-klassen, så ser vi at Bil-objektet har arvet denne metoden fra Kjøretøy-klassen.

Å sjekke typen til et objekt#

type()-funksjonen lar oss sjekke om et objekt tilhører en spesifikk klasse.

class Kjøretøy:
    def __init__(self, merke):
        self.merke = merke
    
    def tut(self):
        print("Tut!")

class Bil(Kjøretøy):
    def __init__(self, merke):
        super().__init__(merke) # Arver data og metoder fra Kjøretøy
        self.pris = 50

bil = Bil("Toyota")

print(type(bil))
print(type(bil) == Bil)
<class '__main__.Bil'>
True

Vi ser at typen til bil er <class '__main__.Bil'>. type(bil) == Bil er True fordi bil er et objekt av klassen Bil.

Hvordan sjekke superklasser

I eksempelet over vil type(bil) == Kjøretøy evalueres til False. Hvorfor?

Vi kan tenke oss at type()-funksjonen sjekker navnet til klassen. Dette navnet er "Bil", ikke "Kjøretøy".

Men en bil er jo et kjøretøy også. Hvis vi ønsker å sjekke typen på en klasse som også gir True for en superklasse kan vi bruke isinstance()-funksjonen.

print(isinstance(bil, Kjøretøy))

Programmet over vil evalueres til True.

Utvidet eksempel: Bompengesystem#

La oss utvide bompengesystemet.

class Kjøretøy:
    def __init__(self, merke):
        self.merke = merke
    
    def tut(self):
        print("Tut!")

class Lastebil(Kjøretøy):
    def __init__(self, merke):
        super().__init__(merke)
        self.pris = 200

class Bil(Kjøretøy):
    def __init__(self, merke):
        super().__init__(merke)
        self.pris = 50

class ELBil(Bil):
    def __init__(self, merke):
        super().__init__(merke)
        self.pris = 25

lastebil = Lastebil("Mercedes")
bil = Bil("Toyota")
elbil = ELBil("Tesla")

print("Pris for lastebil:", lastebil.pris)
print("Pris for bil:", bil.pris)
print("Pris for elbil:", elbil.pris)
Pris for lastebil: 200
Pris for bil: 50
Pris for elbil: 25

Studer eksempelet nøye.

Vi ser at Lastebil og Bil arver fra Kjøretøy og har sine egne priser. ELBil arver fra Bil og har halvparten av prisen til Bil.

Objektorienterte modeller: UML#

Når vi har en del klasser som arver fra hverandre kan det være hensiktsmessig å lage en oversikt, en modell.

I UML-diagrammer er det vanlig å representere klasser som bokser med tre deler; en del for navnet til klassen, en del for attributtene og en del for metodene. Pilene representerer arv. Subklasser har piler som peker til superklasser.

        ---
title: Klassediagram/UML
---
classDiagram
	class Kjøretøy
	Kjøretøy : +str merke
	Kjøretøy : +tut()
	class Lastebil
	Lastebil : +int pris
	class Bil
	Bil : +int pris
	class ELBil
	ELBil : +int pris
	Kjøretøy <|-- Lastebil
	Kjøretøy <|-- Bil
	Bil <|-- ELBil
    

Denne modellen viser hvilke klasser som arver fra hvilke. Dette kalles et klassehierarki. Desto lengre nedover i modellen vi kommer, desto mer spesialiserte blir klassene. Det vil si at klassene får flere, mere spesifikke, egenskaper. Beveger vi oss oppover i modellen blir klassene mer generaliserte. Det vil si at klassene får færre, mer generelle, egenskaper.

For å lage modeller anbefaler jeg å bruke Draw.io.

Inneholderklasser#

En annen relasjon mellom klasser er at en klasse kan inneholde en annen klasse. Det kan vi også vise i modellene våre.

La oss se på et eksempel.

class Elev:
    def __init__(self, navn):
        self.navn = navn

class Klasse:
    def __init__(self, navn):
        self.navn = navn
        self.elever = []

    def legg_til(self, elev : Elev):
        self.elever.append(elev)

klasse = Klasse("VG3")
klasse.legg_til(Elev("Omar"))
klasse.legg_til(Elev("Theodor"))

For å representere at en klasse kan inneholde andre klasser kan vi bruke en egen pil.

        ---
title: Klassediagram/UML
---
classDiagram
	class Elev
    Elev : +str navn
    class Klasse
    Klasse : +str navn
    Klasse : +list elever
    Klasse : legg_til(Elev)
    Klasse o-- Elev
    

Oppgaver#

Oppgave 1 🚗

Kopier alle klassene fra eksempelet med bompengesystemet.

  1. Lag en funksjon bom(x) som tar inn et objekt x. Funksjonen skal skrive ut hvilken type kjøretøy som kjører igjennom bommen, hvilket merke den har og hva prisen blir.

  2. Test funksjonen ved å lage et objekt av hver type, Bil, Lastebil og ELBil.

Mulig utskrift: En ELBil med merke Tesla har passert og betale 25kr.

Oppgave 2 🐄

I denne oppgaven skal vi organisere husdyr.

  1. Lag en klasse Husdyr. Alle Husdyr-objekter skal ha en attributt navn som settes som argument til konstruktøren.

  2. Lag en klasse Kylling. Den skal arve fra Husdyr og ha en metode lyd() som returnerer "Kykkeliky!".

  3. Lag to nye selvvalgte husdyr som også arver fra Husdyr-klassen. De skal også ha sin egen lyd()-metode som returnerer en egen lyd.

  4. Lag en modell av klassene som viser hvilke klasser som arver fra hvilke

Fint! Nå skal vi organisere disse dyrene i en liste.

  1. Lag en liste med husdyr som heter husdyrliste. Legg inn et objekt av hver subklasse av Husdyr inn i denne listen.

  2. Lag en funksjon hils(x) som tar et objekt x som argument. Den skal skrive ut navnet til dyret og hvilken lyd det lager ved å bruke lyd()-metoden.

  3. Send hvert objekt i husdyrlisten inn i hils()-funksjonen.

Mulig utskrift: Bertha sier Kykkeliky!

Oppgave 3 💸

I denne oppgaven skal vi lage et system for bankkontoer.

  1. Lag en klasse Bankkonto. En bankkonto skal ha en saldo (tall) som settes som argument til konstruktøren.

  2. Lag en klasse Sparekonto. En Sparekonto skal arve fra Bankkonto og i tillegg ha en rente på 3.5%. Det kan være lurt å lagre dette som et desimaltall istedet.

  3. Lag en klasse Brukskonto. En Brukskonto skal arve fra Bankkonto og i tillegg ha en rente på 0.5%.

  4. Lag en modell av klassene som viser hvilken klasse som arver fra hvilken.

Nå skal vi legge til litt funksjonalitet.

  1. Utvid Bankkonto med en metode oppdater(). Denne skal regne ut hvor mye penger man skal få i rente og legge det til på saldoen.

  2. Opprett et Sparekonto-objekt og et Brukskonto-objekt med like mye penger i saldo. Hvor mye penger har hver konto i saldo etter fem oppdateringer?

Bonus: Gjør slik at man får en feilmelding (eller bare printet til terminalen) når man forsøker å opprette et objekt av Bankkonto (altså ikke Sparekonto eller Brukskonto). Dette kan du gjøre ved å endre på __init__()-metoden eller __new__()-metoden.

Oppgave 4 🏫

I denne oppgaven skal vi lage et system for skoler.

  1. Lag en klasse Person som skal ha attributten navn som settes som argument til konstruktøren.

  2. Lag en klasse Elev som skal være en subklasse av Person. Elev skal i tillegg ha attributten karakterliste som skal være en tom liste fra begynnelsen av.

  3. Legg til en metode legg_til_karakter som tar en argument karakter som heltall og setter det inn i Elev-objektets attributt karakterliste.

  4. Lag en klasse Lærer som skal være en subklasse av Person. Lærer skal i tillegg ha attributten fagområde som en string som skal settes som argument til konstruktøren (f.eks «Matematikk/fysikk» eller «Elektro»).

Nå skal vi lage en overordnet struktur for skolen.

  1. Lag en klasse Klasse. Et Klasse-objekt skal ha en attributt navn som en string som settes som argument til konstruktøren. Et Klasse-objekt skal i tillegg ha attributtene elevliste og lærerliste som skal være tomme lister fra begynnelsen av.

  2. Lag en metode legg_til_person som tar inn et objekt og skjekker om objektet er et Elev-objekt eller et Lærer-objekt og legger objektet inn i riktig liste.

  3. Lag en klasse Skole. Et Skole-objekt skal ha attributtene navn og klasseliste. navn skal settes som argument til konstruktøren og klasseliste skal være en tom liste fra begynnelsen av.

  4. Legg til en metode legg_til_klasse til Skole som tar et Klasse-objekt og legger det inn i klasseliste.

Nå skal vi sørge for at vi får hensiktsmessig utskrift av objektene.

  1. Legg til __str__-metoder for Elev, Lærer, Klasse og Skole som returnerer hensiktsmessig utskrift av attributtene.

  2. Test klassene ved å lage objekter, bruke metoder og skrive ut objekter.

  3. Tegn klassehierarkiet.

Oppgave 5

Implementér følgende klassediagram som et objektorientert program i Python 🐍

        classDiagram
	class Party
	Party : +list members
	Party : +add_member(member)
	Party : +remove_member(member) 
	class Member
	Member : +str name
	Member : +int max_HP
	Member : +int HP
    Member : +int MP
	class Warrior
	Warrior : +shout()
	class Priest
	Priest : +heal(member)
	class Rogue
	Rogue : +hide()
	Party o-- Member
	Member <|-- Warrior
	Member <|-- Priest
	Member <|-- Rogue
    

Her er noen krav:

  • Metodene shout() og hide() skal bare printe noe til terminalvinduet.

  • Metoden heal(member) skal:

    • øke HP for member med 5, men bare opp til max_HP.

    • bruke opp 5 MP, og bare funke dersom en Priest har mer enn 5 MP.

  • Legg til __str__()-metoder for å få en hensiktsmessig utskrift når man printer de ulike objektene.