- COMP.CS.140
- 5. Periytyminen
- 5.1 Modulaarisuus: periytyminen
- 5.1.2 Periytyminen
Periytyminen¶
Asioiden luokittelu ja kategorisointi luonnollista ihmiselle. Periytyminen on olio-ohjelmoinnissa keskeisesti käytetty ominaisuus, jossa tehdään uusi luokka jonkin olemassa olevan mallin eli toisen luokan pohjalta. Mahdollisuuden luoda uusia luokkia johonkin luokkaan pohjaten voidaan sanoa olevan yksi olio-ohjelmoinnin ja oliosuunnittelun tärkeimmistä ominaisuuksista. Periytymisessä jokin luokka siis pohjautuu toiseen luokkaan niin, että toisen luokan ominaisuudet periytyvät sille. Periyttämällä luotuu luokkaan voidaan lisätä uusia ominaisuuksia ja pohjana olleen luokan ominaisuuksia tarvittaessa muuttaa. Luokat muodostavat näin periytymis- eli luokkahierarkioita – rakanteeltaan periytymispuita, joissa alempana oleva luokka perii ylempänä puussa olevan luokan ominaisuudet.
Periytymisen keskeisiä termejä ovat:
Periytyminen (engl. inheritance) (myös perintä, johtaminen): Uuden luokan muodostaminen olemassa olevan luokan pohjalta siten, että uudella luokalla on aiemman kaikki ominaisuudet.
Kantaluokka (engl. base class) (myös yliluokka (engl. super class, parent class)): Alkuperäinen, luokkahierarkiassa ylempänä oleva luokka, jonka ominaisuudet periytettävä luokka perii.
Aliluokka (engl. subclass) (myös periytetty luokka, johdettu luokka (engl. derived class)): Uusi luokka, joka perii alkuperäisen eli kantaluokan ominaisuudet ja johon voidaan lisätä periytyneiden ominaisuuksien oheen uusia ominaisuuksia.
Periytymishierarkia (engl. inheritance hierarchy) (myös luokkahierarkia (engl. class hierarchy)): Kanta- ja aliluokista muodustuva puurakenne. Periytymishierarkiassa alempana olevat luokat perivät ylempänä olevien ominaisuudet.
Esi-isä (engl. ancestor): Periytymishierarkiassa luokasta ylöspäin edetessä vastaan tulevat kantaluokka, kantaluokan kantaluokka jne.
Jälkeläinen (engl. descendant): Kantaluokasta periytetyt luokat ja niistä edelleen periytetyt luokat.
Luokkahierarkian yläosan luokat toimivat ikäänkuin yläkäsitteinä, jotka määräävät alempien luokkien rajapintaa. Niiden rooli on ryhmitellä todellisia luokkia yhteen sen mukaan, että niiden olioilla on yhteneväinen rajapinta tai toiminnallisuus. Eliöstä kuvaavassa hierarkiassa luokkia, joista ohjelmassa voi tehdä olioita, ovat alimman tason luokat Vesinokkaeläin (Platypus), Ihminen (Human), Kana (Hen) ja Satakieli (Nightingale). Näiden tehtävänä on toteuttaa kantaluokkatasolla määritellyt palvelut luokalle sopivalla tavalla. Periytyminen erottaakin rajapinnan ja sen toteutuksen toisistaan: kantaluokan tasolla määritellään rajapinta, aliluokka toteuttaa sen haluamallaan tavalla eli erikoistaa ylempänä hierarkiassa määriteltyä toiminnallisuutta. Ylätason luokat, jotka on merkitty “{Abstract}” kuten esimerkiksi Eliö (Organism) ja Lintu (Bird), ovat ryhmitteleviä luokkia, joista ei ole mielekästä tehdä ohjelmassa oliota. Niillä voi myös olla rajapinnassaan palveluita, joita ei toteuteta kantaluokan tasolla ollenkaan. Tällaisia luokkia kutsutaan abstrakteiksi kantaluokiksi (engl. abstract base class).
Periytymishierarkia kuvaa luokkien välisiä suhteita. Eliöstössä luokka Eliö sisältää kaikille eliöille yhteisen rajapinnan: kaikki eliöt osaavat lisääntyä.
Tämä tarkoittaa sitä, että ohjelmassa jokaisella eliöllä on ulospäin samalta näyttävä lisääntymisominaisuus, jota kutsua.
Ominaisuuden toteutus voi olla hyvinkin erilainen luokkien välillä: vesinokkaeläin ja kana lisääntyvät hyvin eri tavoilla.
Periytetyt luokat kuten Lintu ja Sieni tarjoavat automaattisesti rajapinnassaan Eliön palvelut. Ne lisäävät tähän omat erityisesti luokalle kuuluvat palvelut eli esimerkiksi Lintu pystyy lisääntymisen lisäksi laulamaan.
Periytymisen etuna on se, että sen avulla ohjelmassa voidaan kaikista nisäkkäistä puhua käyttämällä Nisäkäs-luokkaa.
Hyvä esimerkki tästä on Javassa equals
-vertailu, joka on mahdollista mille tahansa Javan luokalle eli Object
:lle.
Myös, jos kaikille ohjelman Linnuille tarvittaisiin lisätä jokin uusi palvelu, riittää, että se lisätään Lintu
-luokkaan.
Vesinokkaeläin on periytymishierarkioissa loistava esimerkki myös siitä, miten vaikeaa luonnollisia ilmiöitä on hierarkisesti esittää. Sehän on nisäkäs, joka munii munia. Lisäksi sillä on ankan nokka. Onkin hyvä pitää mielessä, että periytymishierarkiat ovat aina käyttötilanteesta riippuvaisia eikä toteutetussa ohjelmassa esiintyvät oliot aina vastaan suoraan todellisia tai määriteltyjä olioita. Esimerkiksi yliopiston henkilökuntaa voidaan mallintaa kahdella hyvin erilaisella periytymishierarkialla.
Luokilla on hierarkiassa erilaisia rooleja sen mukaan, miten ne sijaitsevat periytymispuussa:
Rajapinnat tai rajapintaluokat määrittävät vain rajapinnan, jonka jokin toinen luokka toteuttaa
Abstraktit kantaluokat määrittävät aliluokkien yhteistä rajapintaa ja toiminnallisuutta, mutta niistä ei voi luoda ohjelmassa oliota (ne eivät ole kokonaan toteutettuja).
Konkreettiset luokat toteuttavat koko rajanpintansa ja niistä luodaa ohjelmassa olioita.
Periytymisen käytön päämotivaattoreita ovat toiminnallisuuden uudelleenkäyttäminen (kootaan yhteinen toiminnallisuus osaksi kantaluokkaa) sekä toiminnallisuuden laajentaminen ja erikoistaminen (kantaluokan rajapinnan lisäksi toteutetaan aliluokan tasolla uusia palveluita tai muutetaan jonkin toteutusta aliluokalle sopivaksi).
Uudelleenkäyttö: yleistäminen¶
On varsin yleistä, että luokilla on yhteisiä ominaisuuksia. Esimerkiksi kaikki näytölle piirrettävät elementit käyttäytyvät tietyiltä osin samalla tavalla. Tällaisessa tilanteessa on hyödyllistä, kun luokille yhteiset toiminnallisuudet voidaan kasata yhteen omaksi kantaluokkakseen, josta sitten yhteinen toiminnallisuus peritytetään aliluokille. Aliluokat välttyvät kantaluokkatasolla toteutetun toiminnallisuuden uudelleenkirjoittamiselta. Tätä yhteisten osien kokoamista yhteiseksi kantaluokaksi kutsutaan yleistämiseksi (engl. generalization). Yleistämisen avulla ohjelmakoodin ylläpidettävyys paranee. Lisäksi sen toiminnallisuuden varmistaminen on helpompaa, kun vain yksi yhteinen toiminnallisuus tulee testata. Yleistäminen myös mahdollistaa koodin uudelleenkäyttöä: kerran kantaluokkaan toteutettua ei tarvitse toteuttaa uudelleen. Toisaalta periytymisen käytössä yleistämisen kautta piilee vaara pitkistä periytymisketjuista, mikä pirstaloittaa koodia. Tällainen koodi on vaikeampaa lukea ja hahmottaa ohjelman toiminnallisuutta, koska toteutus on palasina usealla hierarkian tasolla. Periytymisen käyttö korostaakin dokumentaation merkitystä.
Toiminnallisuuden laajentaminen ja erikoistaminen¶
Yleistäminen tuo mukanaan mahdollisuuden lisätä yhteiseen toiminnallisuuteen aliluokassa omia palveluita. Tämä yksinkertainen ja jo aiemmilta kursseilta mahdollisesti tuttu periytymisen käyttömuotoa kutsutaan kantaluokan toiminnallisuuden laajentamiseksi (engl. extension). Kantaluokassa määritelty toiminnallisuus otetaan tällöin sellaisenaan aliluokkaan mukaan (uudelleenkäyttö) ja siihen lisätään aliluokan tarvitsemia lisäpalveluita (laajentaminen). Laajentamisen kohdalla on tärkeää muistaa, ettei periytymisen käyttö ole aina välttämätöntä. Kantaluokan toiminnallisuutta kannattaa laajentaa periyttämällä tilanteissa, jossa tarvitaan sekä kantaluokka että siitä laajennettu aliluokka. Mahdollista on myös lisätä kantaluokkaan itseensä toiminnallisuutta.
Edellä mainittu erikoistaminen on todennäköisesti periytymisen käytetyin muoto. Siinä aliluokka on kantaluokan erikoistus: se on ulkoiselta käyttäytymiseltään kantaluokkansa rajapinnan mukainen, mutta itse toiminnallisuus voi olla aliluokalle omansa.
Erilaiset tavat käyttää periytymistä myös täydentävät toisiaan. Oliosuunnittelussa on tärkeä miettiä, mikä lähestymistapa toimii tilanteessa parhaiten ja myös, onko periyttäminen ylipäänsä tarpeen.
Is-a-suhde¶
Aliluokka perii kaikki kantaluokan ominaisuudet. Sen rajapinta on siis sama kuin kantaluokalla. Keskeinen käsite, joka määrittää sitä, onko kahden luokan välillä periytymissuhde, on ns. is-a-suhde: aliluokan olio on myös kantaluokkansa olio. Periytymisen näkökulmasta is-a-suhde määrittää, onko kahden käsitteen välillä ohjelmassa periytymissuhde. Koira on (is-a) eläin. Kana on (is-a) lintu. Merkkijonotaulukko on (is-a) taulukko. jne. Vaikka joskus törmääkin tilanteisiin, joissa periytymissuhde on paikallaan, vaikkei is-a-suhdetta käsitteiden välillä suoraan ole, on varsin perusteltua olettaa, että jos is-a-suhdetta ei ole, periytymistäkään ei tulisi käyttää.
Is-a-suhteen lisäksi ohjelmissa syntyy has-a-suhteita: luokan olio koostuu tietyntyyppisistä olioista. Autolla on (has-a) moottori. Kirjastonkirjalla on (has-a) palautuspäivä. jne. Has-a-suhde määrittää siis koostesuhteen: jollain oliolla on osana toteutustaan toinen olio.
Kun luokkahierarkiaa tarkastellaan is-a-suhteen kautta nähdään myös, miten aliluokan oliot ovat myös kantaluokan olioita.
Jokaisella aliluokan oliolla on myös kantaluokkaan kuuluvat osat: ne koostuvat kerroksellisesti kantaluokkiensa osista ja lopulta oman toteutuksensa lisäämistä osista.
Vaikka aliluokan oliossa on kaikkien sen kantaluokkien osat, sillä ei kuitenkaan ole pääsyä kantaluokkansa sisäiseen toteutukseen kuin kantaluokan julkisen rajapinnan kautta.
Kantaluokka voi tarjota aliluokan olioille oman rajapintansa – protected
-rajapinnan.
Periytyminen (kesto 25:11)
Polymorfismi¶
Periytymisen ohjelmointiin tuoma ehkä merkittävin hyöty on mahdollisuus käsitellä monentyyppisiä olioita yhdenmukaisesti yhteisen kantaluokan avulla. Tätä kutsutaam polymorfismiksi eli monimuotoisuudeksi (kreikan sanoista poly (moni) morphos (muoto)). Koska aliluokan olio on myös kantaluokkansa olio, sitä voidaan käsitellä ohjelmassa kantaluokan oliona. Polymorfismissa on kyse juuri tästä eli eri tyyppisiin olioihin on mahdollista viitata yhteisellä nimellä niiden kantaluokan avulla.
Polymorfismi näkyy käytännössä luokkahierarkiaa tarkastelemalla. Koska eläimen rajapintatasolla on määritelty, että kaikki eläimet osaavat liikkua, ohjelmassa on mahdollista käskeä kaikkia eläimiä liikkumaan murehtimatta siitä, mikä eläin milloinkin on kyseessä. Koska kuitenkin liikkumisen toteutus voi olla hyvin erilainen eri olioilla, se, mitä konkreettista toteutusta tulee kutsua, voidaan joutua päättelemään ohjelmassa vasta ajon aikana. Tätä kutsutaan dynaamiseksi (engl. dynamic) tai myöhäiseksi (engl. late) sitomiseksi (engl. binding).