4. Periytyminen 4.1. Johdantoa Käytännössä vähänkään laajemmissa ohjelmissa joudutaan laatimaan useita luokkia, joiden pitäisi pystyä välittämään tietoa toisilleen. Ohjelmien ylläpidon kannalta olisi lisäksi suotavaa, että samaa koodia ei ole useassa eri paikassa. Olio-ohjelmoinnissa luokat voidaan järjestää siten, että ne pystyvät jakamaan yhteisiä tietoja ja aliohjelmia. Periytyminen tarkoittaa, että jonkin ns. kantaluokan (base class) ominaisuuksia voidaan periyttää (inherit) kantaluokasta johdetulle luokalle (derived class). Kokonaisuudessa luokkien periytyminen on varsin monimutkaista. Tarkastellaan tällä kurssilla yksinkertaisinta tapausta eli ns. palveluliittymän periytymistä eli public-periytymistä. Tarkemmin periytyminen on käsitelty esimerkiksi Markun materiaalissa ja Hietasen kirjassa. Kun tietokoneohjelmalla pyritään mallintamaan jotain reaalimaailman ilmiötä, päädytään väistämättä määrittelemään erilaisia käsitteitä ja niiden välisiä suhteita. Yritäpä esimerkiksi mallintaa, mikä auto on. Pian joudut turvautumaan sellaisiin käsitteisiin kuten pyörä, moottori, kuljettaja, jalankulkija, kuorma-auto, henkilöauto, linja-auto, ambulanssi, tie, öljy jne Olio-ohjelmoinnissa luokkia käytetään reaalimaailman käsitteiden kuvaamiseen. Kysymys kuuluukin, miten käsitteiden välisiä yhteyksiä kuvataan? Miten pyörä liittyy autoon? Entä moottori jne? Periytymisen avulla voidaan ilmaista käsitteiden välisiä hierarkkisia yhteyksiä ohjelmointikielellä. Esimerkiksi ympyrällä, suorakulmiolla ja kolmiolla on jotain yhteistä. Kaikki ovat geometrisia muotoja, joilla on tietty pinta-ala. Kaikkien pinta-ala kuitenkin tunnetusti lasketaan eri tavalla. Ohjelmassa täytyy siten määritellä kolme luokkaa: Circle, Triangle ja Shape. Luokka Shape on kantaluokka ja Circle ja Triangle ovat kantaluokasta johdettuja luokkia. 4.2. Johdetut luokat ja public-periytymistapa
Tarkastellaan ohjelmaa, jonka on tarkoitus ylläpitää yrityksen henkilötietojärjestelmää. Yrityksessä on kahdenlaisia työntekijöitä, tavallisia työntekijöitä ja eri tason johtajia. Luokan määritys voisi näyttää tältä: class duunari protected: string etunimi,sukunimi; int osasto; double palkka; void duunaile(); duunari(); ~duunari(); class pomo : public duunari int alaistenlkm; void johda(); pomo(); ~pomo(); } Johtaja on välttämättä aina myös yrityksen palkkalistoilla oleva työntekijä, mutta kaikki työntekijät eivät ole johtajia. Johtajaan siis liittyy tietoja, joita työntekijään ei liity, esimerkiksi alaisten lukumäärä. Johtaja on työntekijä, johon liittyy joitakin lisäominaisuuksia. Englanninkielessä puhutaan is-a-periytymisestä: A manager is an employee. Olio-ohjelmoinnin käsitteillä asia voidaan esittää siten, että luokka pomo on johdettu luokasta duunari. Toisin päin ilmaistuna, luokka duunari on kantaluokka luokalle pomo. Luokka pomo sisältää (perii) kaikki luokan duunari ominaisuudet ja siihen sisältyy omia ominaisuuksia, joita ei kuulu luokkaan duunari. Johdettu luokka on siis yleensä aina suurempi kuin kantaluokka. Se sisältää enemmän tieto- ja / tai aliohjelmajäseniä. Kantaluokan ja johdetun luokan ohella käytetään termejä yliluokka ja aliluokka. Public-periytymistavan yleinen muoto on class yliluokka protected:
class aliluokka : public yliluokka Luokan määrittelyssä esiintyy uusi avainsana protected. Sana protected määrittelee luokan suojatun jäsenen. Yksityisen ja suojatun jäsenen välinen ero on siinä, miten kyseisestä luokasta johdetut luokat pystyvät käsittelemään niiden sisältämiä tietoja: Suojattuja jäseniä (protected) voivat käsitellä myös luokasta johdetut luokat. Yksityisiä jäseniä (private) voi käsitellä vain luokka itse, ei siitä johdetut luokat. Julkisia jäseniä voi käsitellä mikä tahansa, myös luokkaan kuulumaton aliohjelma, mukaan lukien main(). Edellä siis luokan duunari kaikki tietojäsenet on määriteltävä suojatuiksi, koska siitä johdetun luokan pomo pitää pystyä käsittelemään duunarin tietoja. Sen sijaan luokasta pomo ei ole johdettu luokkia, eikä duunarin tarvitse tietää, montako alaista pomolla on. Siksi luokan pomo ainoa tietojäsen alaistenlkm pitää määritellä yksityiseksi. Alla olevaan taulukkoon on koottu, kenellä on oikeus käsitellä luokan jäseniä (joko tietotai aliohjelmajäseniä). Access public protected private Saman luokan jäsenet kyllä kyllä kyllä Johdettujen luokkien jäsenet kyllä Kyllä ei Ei-jäsenet kyllä ei ei Kanta- ja johdettu luokka käsittelevät kumpikin omia yksityisiä jäseniään. Johdetun luokan aliohjelmissa voidaan viitata kantaluokan protected- ja public-määreen jäljessä esiteltyihin jäseniin. Public-periytymistapa pitää kantaluokan jäsenten näkyvyyssäännöt johdetussa luokassa ennallaan. Jos johdetusta luokasta periytetään uusi johdettu luokka, näkee uusi luokka ylimmän luokan protected-jäsenet myös oman kantaluokkansa protected-jäseninä. Vastaavasti näkyvät ylimmän luokan public-jäsenet. Kanta- ja johdettu luokka voivat sisältää tarvittaessa samannimisiä tietoja ja aliohjelmia. Tässä tapauksessa johdettu luokka peittää kantaluokan vastaavannimisen aliohjelman. Esimerkki: Monikulmio
Mitä yhteistä on suorakulmiolla ja kolmiolla? Molempien pinta-ala lasketaan korkeuden ja leveyden avulla. Tämä yhteys voitaisiin esittää luokkahierarkiana CPolygon on kantaluokka, josta voidaan periyttää johdetut luokat CRectangle (suorakulmio) ja Ctriangle (kolmio). Seuraavassa on esitetty luokan CPolygon määrittely: class CPolygon protected: int width, height; void set_values (int a, int b) width=a; height=b;} Luokka sisältää suojatut tietojäsenet width ja height, sekä yhden julkisen aliohjelmajäsenen set_values. Johdetut luokat perivät kantaluokan jäsenet, joten niihin tarvitsee erikseen kirjoittaa ainoastaan metodit, jotka laskevat pinta-alan (pinta-alan laskentahan on tunnetusti erilainen kolmiolle ja suorakulmiolle): class CRectangle: public CPolygon return (width * height); } class CTriangle: public CPolygon return (width * height / 2); } Pääohjelmassa oliota voidaan kutsua esimerkiksi seuraavasti: int main () CRectangle rect;
} CTriangle trgl; rect.set_values (4,5); trgl.set_values (4,5); cout << rect.area() << endl; cout << trgl.area() << endl; return 0; Edellä luodaan suorakulmio-olio rect ja kolmio-olio trgl. Molemmat voivat käyttää kantaluokassa määriteltyä metodia set_values. Molemmilla oliolla on kuitenkin oma pinta-alan laskentametodinsa. 4.3. Private- ja protected-periytyminen Edellä tarkasteltiin public-periytymistä. class aliluokka : public yliluokka Public-periytymisessä johdetun luokan jäsenillä on samat suojausmekanismit kuin kantaluokassa. Jos jäsen on kantaluokassa tyyppiä protected, on se myös johdetussa luokassa protected. Edellä kaksoispisteen jäljessä oleva määre kertoo luokan jäsenten minimisuojaustason. Jos periytyminen määritellään olevan tyyppiä protected, ovat kaikki johdetun luokan jäsenet vähintään tyyppiä protected (kantaluokan public-jäsenet muuttuvat protectedtyyppisiksi). Jos periytyminen määritellään olevan tyyppiä private, ovat kaikki johdetun luokan jäsenet tyyppiä private (siis kantaluokan sekä public- että protected-jäsenet muuttuvat private-tyyppisiksi). Johdetulla luokalla voi olla omia jäseniä, jotka eivät periydy kantaluokasta, joilla on löyhemmät suojausominaisuudet. Mitä tapahtuu, jos CRectangle määriteltäisiin periytyväksi luokasta CPolygon protectedtyyppisesti? class CRectangle: protected CPolygon return (width * height); } CPolygon sisältää julkisen aliohjelmajäsenen setvalues. Määrityksen jälkeen se muuttuisi protected-tyyppiseksi. Onko tällä muutoksella vaikutuksia ohjelman toimintaan? Vastaavasti määrittely class CRectangle: private CPolygon
return (width * height); } muuttaisi setvalues-metodin private-tyyppiseksi. Entä onko tällä vaikutusta ohjelman toimintaan? Kummassakaan edellä mainitussa tapauksessa set_values-metodia ei enää voi kutsua pääohjelmasta, koska main() ei ole luokan CPolygon jäsen eikä aliluokka. Käytännössä public-periytyminen on ylivoimaisesti yleisin..