Periytyminen on olio-ohjelmoinnin mekanismi, jossa luokkien välille muodostetaan hierarkia: yksittäisestä luokasta, jotka kutsutaan yliluokaksi, johdetaan sitä tarkentavia ja täsmentäviä aliluokkia.
Tässä moduulissa opit käyttämään periytymissuhdetta kirjoittaessasi oliopohjaisia Python-olioita.
Tarkastellaan seuraavaa tilannetta, jossa ohjelma käsittelee Työntekijä-olioita:
class Työntekijä:
työntekijöiden_lukumäärä = 0
def __init__(self, etunimi, sukunimi):
Työntekijä.työntekijöiden_lukumäärä = Työntekijä.työntekijöiden_lukumäärä + 1
self.työntekijänumero = Työntekijä.työntekijöiden_lukumäärä
self.etunimi = etunimi
self.sukunimi = sukunimi
def tulosta_tiedot(self):
print(f"{self.työntekijänumero}: {self.etunimi} {self.sukunimi}")
työntekijät = []
työntekijät.append(Työntekijä("Viivi", "Virta"))
työntekijät.append(Työntekijä("Ahmed", "Habib"))
for t in työntekijät:
t.tulosta_tiedot()
Ohjelma luo kaksi työntekijä: Viivin ja Ahmedin, lisää heidät työntekijälistaan ja tulostaa listan sisällön:
1: Viivi Virta
2: Ahmed Habib
Jokaisella työntekijällä on kolme ominaisuutta: työntekijänumero, etunimi ja sukunimi. Työntekijänumero annetaan jokaiselle työntekijälle automaattisesti työntekijöiden lukumäärän perusteella. Työntekijöiden lukumäärä on tässä luokkamuuttuja: sen arvoa ei ole määritelty erikseen jokaiselle Työntekijä-luokan oliolle, vaan arvo on määritelty kertaalleen Työntekijä-luokalle. Huomaa, että luokkamuuttuja määritellään alustajan ulkopuolella, ja siihen viitattaessa käytetään self
-sana korvataan luokan nimellä.
Oletetaan, että kohtaamme uuden kehitystarpeen: osa työntekijöistä on tuntipalkkaisia työntekijöitä ja osa kuukausipalkkaisia. Miten palkkatieto tulisi lisätä Työntekijä-luokan ominaisuuksien luetteloon?
Yksi ratkaisu olisi lisätä kaksi eri ominaisuutta: tuntipalkka ja kuukausipalkka. Ratkaisu olisi kuitenkin epätarkka, ja sovellusta käytettäessä joutuisimme aina tarkastamaan kenttien arvojen perusteella, kumpi työntekijän alatyyppi on kyseessä. Lisäksi mikään ei teknisesti estäisi meitä määrittämästä samalle työntekijälle sekä tunti- että kuukausipalkkaa.
Otetaan ratkaisuksi Python-kielen periytymismekanismi. Kirjoitetaan Työntekijä
-luokalle kaksi tarkentavaa aliluokkaa: Tuntipalkkainen
ja Kuukausipalkkainen
. Kun luomme uuden olion, voimme tehdä siitä esimerkiksi Tuntipalkkainen
-aliluokan ilmentymän. Tällöin sillä on kaikki Työntekijä
-yliluokasta perityt ominaisuudet ja metodit (kuten etunimi
-ominaisuus ja työskentele
-metodi), mutta niiden lisäksi käytössä on vain tuntipalkkaisille työntekijöille aliluokassa määritetty
tuntipalkka
-ominaisuus.
Laajennettu ohjelma näyttää tältä:
class Työntekijä:
työntekijöiden_lukumäärä = 0
def __init__(self, etunimi, sukunimi):
Työntekijä.työntekijöiden_lukumäärä = Työntekijä.työntekijöiden_lukumäärä + 1
self.työntekijänumero = Työntekijä.työntekijöiden_lukumäärä
self.etunimi = etunimi
self.sukunimi = sukunimi
def tulosta_tiedot(self):
print(f"{self.työntekijänumero}: {self.etunimi} {self.sukunimi}")
class Tuntipalkkainen(Työntekijä):
def __init__(self, etunimi, sukunimi, tuntipalkka):
self.tuntipalkka = tuntipalkka
super().__init__(etunimi, sukunimi)
def tulosta_tiedot(self):
super().tulosta_tiedot()
print(f" Tuntipalkka: {self.tuntipalkka}")
class Kuukausipalkkainen(Työntekijä):
def __init__(self, etunimi, sukunimi, kuukausipalkka):
self.kuukausipalkka = kuukausipalkka
super().__init__(etunimi, sukunimi)
def tulosta_tiedot(self):
super().tulosta_tiedot()
print(f" Kuukausipalkka: {self.kuukausipalkka}")
työntekijät = []
työntekijät.append(Tuntipalkkainen("Viivi", "Virta", 12.35))
työntekijät.append(Kuukausipalkkainen("Ahmed", "Habib", 2750))
työntekijät.append(Työntekijä("Pekka", "Puro"))
työntekijät.append(Tuntipalkkainen("Olga", "Glebova", 14.92))
for t in työntekijät:
t.tulosta_tiedot()
Esimerkissä luodaan kaksi tuntipalkkaista työntekijää, yksi kuukausipalkkainen työntekijä sekä yksi työntekijä (Pekka), jonka osalta ei oteta kantaa, minkälainen hänen työsuhteensa on.
Toistorakenne tuottaa seuraavan tulosteen:
1: Viivi Virta
Tuntipalkka: 12.35
2: Ahmed Habib
Kuukausipalkka: 2750
3: Pekka Puro
4: Olga Glebova
Tuntipalkka: 14.92
Yliluokka-aliluokkasuhde ilmaistaan Pythonissa siten, että aliluokan määrittävään class
-lauseeseen lisätään sulkeisiin yliluokan nimi. Siis lauseen alku class Tuntipalkkainen(Työntekijä)
määrää, että Tuntipalkkainen-luokasta tulee Työntekijä-luokan aliluokka.
Aliluokalle kirjoitetaan tarvittaessa oma alustaja. Kun aliluokan ilmentymä luodaan, vain aliluokkaan kirjoitettu alustaja suoritetaan. Käytännössä usein halutaan toimia kuten esimerkissämme: ohjelmassa kutsutaan aliluokan alustajaa, joka puolestaan kutsuu yliluokan alustajaa. Tässä tapauksessa aliluokan alustaja antaa arvon tuntipalkalle, kun taas etunimi ja sukunimi on määritetty yliluokassa. Niiden arvot välitetään yliluokalle kutsumalla yliluokan alustajaa eli __init__
-metodia. Olion yliluokkaan päästään käsiksi super()
-funktion avulla: lause super().__init__(etunimi, sukunimi)
kutsuu siis yliluokan alustajaa, joka saa parametreinaan etu- ja sukunimen.
Yliluokassa määritetyt ominaisuudet näkyvät automaattisesti aliluokkaan. Voimme siis luoda Tuntipalkkainen
-luokan ilmentymän t
ja milloin tahansa hakea hänen etunimensä ilmaisulla t.etunimi
.
classDiagram
class Työntekijä {
static työntekijöiden_lukumäärä: int
+työntekijänumero: int
+etunimi: str
+sukunimi: str
+__init__(etunimi: str, sukunimi: str)
+tulosta_tiedot() void
}
class Tuntipalkkainen {
+tuntipalkka: float
+__init__(etunimi: str, sukunimi: str, tuntipalkka: float)
+tulosta_tiedot() void
}
class Kuukausipalkkainen {
+kuukausipalkka: float
+__init__(etunimi: str, sukunimi: str, kuukausipalkka: float)
+tulosta_tiedot() void
}
Työntekijä <|-- Tuntipalkkainen
Työntekijä <|-- Kuukausipalkkainen
Kun tarkastelemme edellä olevaa esimerkkiä, havaitsemme, että Työntekijä
-yliluokkaan kirjoitettu tulosta_tiedot
-metodi tulostaa henkilön etu- ja sukunimen. Metodi toimii hyvin silloin, kun henkilö on luotu Työntekijä
-luokan ilmentymäksi ottamatta kantaa siihen, onko hän tunti- vai kuukausipalkkainen. Toisaalta esimerkiksi tuntipalkkaisten työntekijöiden tietojen tulostamiseen metodi on liian suppea: se tulostaa nimitiedot mutta ei pääse käsiksi aliluokassa määritettyyn tuntipalkkaan.
Ongelma ratkeaa ylikirjoittamalla tulosta_tiedot
-metodi. Ylikirjoittaminen tarkoittaa sitä, että aliluokkaan luodaan toinen toteutus yliluokassa olevasta metodista. Aliluokassa oleva, ylikirjoitettu metodi menee edelle yliluokassa määritellystä metodista. Kun siis kirjoitamme t.tulosta_tiedot()
oliolle, joka on Tuntipalkkainen
-luokan ilmentymä, kutsutaan automaattisesti Tuntipalkkainen
-luokassa olevaa versiota metodista. Jos sama metodikutsu kirjoitetaan Työntekijä
-luokan ilmentymäksi luodulle oliolle, kutsutaan yliluokassa olevaa versiota.
Ylikirjoittamisen ansiosta saamme ohjelmaan joustavuutta: työntekijät voivat olla keskenään erilaisia, ja niiden tietorakenteet voivat poiketa toisistaan. Tästä huolimatta kaikkien työntekijöiden tiedot saadaan pääohjelmassa tulostettua yksinkertaisella toistorakenteella:
for t in työntekijät:
t.tulosta_tiedot()
Kutsuttavan metodin muunnelmat ja toteutustekniset yksityiskohdat on piilotettu sinne minne ne kuuluvat: toteuttaviin luokkiin. Pääohjelmaan ne eivät säteile.
Toisinaan on tilanteita, joissa sama luokka halutaan määrittää kahden tai jopa useamman luokan aliluokaksi. Tätä piirrettä kutsutaan moniperinnäksi. Python-kielessä moniperintä on sallittua toisin kuin joissain muissa olio-ohjelmointikielissä.
Seuraava esimerkki kuvaa moniperintää. Määritetään kaksi luokkaa: Kulkuneuvo
ja Urheiluväline
. Kolmas luokka Polkupyörä
voidaan asettaa molempien mainittujen luokkien aliluokaksi.
class Kulkuneuvo:
def __init__(self, nopeus):
self.nopeus = nopeus
class Urheiluväline:
def __init__(self, paino):
self.paino = paino
class Polkupyörä(Kulkuneuvo, Urheiluväline):
def __init__(self, nopeus, paino, vaihteet):
Kulkuneuvo.__init__(self, nopeus)
Urheiluväline.__init__(self, paino)
self.vaihteet = vaihteet
pp = Polkupyörä(45, 18.7, 3)
print (pp.vaihteet)
print (pp.nopeus)
print (pp.paino)
Luomme Polkupyörä-olion, josta tulostamme vaihteiden lukumäärän, nopeuden ja painon. Vaihteiden lukumäärä on määritetty Polkupyörä
-luokassa. Nopeus periytyy Kulkuneuvo
-luokasta, ja paino periytyy Urheiluväline
-luokasta. Ohjelma tuottaa seuraavan tulosteen:
3
45
18.7
Tässä tapauksessa emme voi Polkupyörä
-luokan alustajasta viitata molempien yliluokkien alustajin super
-funktiolla tähän tapaan:
# Toimimattomat alustajien kutsut
super().__init__(nopeus)
super().__init__(paino)
Yliluokka, johon super
-funktio viittaa, määräytyy Pythonin metodien etsintäjärjestyksen perusteella. Tässä tapauksessa kumpikin lause kutsuisi Kulkuneuvo
-luokassa olevaa alustajaa, ja ohjelma toimisi väärin.
Voimme kutsua yliluokkien alustajia vaihtoehtoisella notaatiolla, jossa yliluokka mainitaan nimeltä:
Kulkuneuvo.__init__(self, nopeus)
Urheiluväline.__init__(self, paino)
classDiagram
class Kulkuneuvo {
+nopeus: int
+__init__(nopeus: int)
}
class Urheiluväline {
+paino: int
+__init__(paino: int)
}
class Polkupyörä {
+vaihteet: int
+__init__(nopeus: int, paino: int, vaihteet: int)
}
Kulkuneuvo <|-- Polkupyörä
Urheiluväline <|-- Polkupyörä
Seuraavassa moduulissa haetaan tietoa ohjelman käyttöön ulkoisista palveluista.