• Ei tuloksia

Johdetut luokat ja public-periytymistapa Johdantoa 12. Periytyminen Osa 5: Periytyminen ja polymorfismi © Jukka Jauhiainen OAMK Tekniikan yksikkö 2010 T740103 Olio-ohjelmointi

N/A
N/A
Info
Lataa
Protected

Academic year: 2022

Jaa "Johdetut luokat ja public-periytymistapa Johdantoa 12. Periytyminen Osa 5: Periytyminen ja polymorfismi © Jukka Jauhiainen OAMK Tekniikan yksikkö 2010 T740103 Olio-ohjelmointi"

Copied!
12
0
0

Kokoteksti

(1)

12. Periytyminen 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.

Johdetut luokat ja public-periytymistapa

(2)

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;

public:

void duunaile();

duunari();

~duunari();

};

class pomo : public duunari {

private:

int alaistenLKM;

public:

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 {

private:

protected:

(3)

public:

};

class aliluokka : public yliluokka {

private:

public:

};

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ä (public) 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 tieto- tai 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

(4)

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 ? Molemmat ovat monikulmioita (engl. polygon) ja molempien pinta-ala lasketaan korkeuden ja leveyden avulla. Tämä yhteys voitaisiin esittää luokkahierarkiana

CPolygonon kantaluokka, josta voidaan periyttää johdetut luokat CRectangle(suorakulmio) ja Ctriangle(kolmio).

Seuraavassa on esitetty luokan CPolygon määrittely:

class CPolygon { protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b;}

};

(5)

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 { public:

int area (void)

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area (void)

{ 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.

Private- ja protected-periytyminen

Edellä tarkasteltiin public-periytymistä.

class aliluokka : public yliluokka {

private:

public:

};

(6)

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 protected-tyyppisiksi). 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 protected-tyyppisesti ? class CRectangle: protected CPolygon {

public:

int area (void)

{ return (width * height); } };

CPolygon sisältää julkisen aliohjelmajäsenen set_values(). Määrityksen jälkeen se muuttuisi protected-tyyppiseksi. Onko tällä muutoksella vaikutuksia ohjelman toimintaan ?

Vastaavasti määrittely:

class CRectangle: private CPolygon { public:

int area (void)

{ return (width * height); } };

muuttaisi set_values()-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.

(7)

13. Polymorfismi

Eräs johdetun luokan keskeinen ominaisuus on, että osoitin johdettuun luokkaan on tyyppiyhteensopiva kantaluokkaan osoittavan osoittimen kanssa. Seuraavassa ohjelmassa on luotu kaksi osoitinta luokkaan CPolygon (ppoly1 ja ppoly2) ja asetetaan ne osoittamaan luokan olioihin rect ja trgl. Tämä on mahdollista, koska sekä luokat CRectangle että CTriangle on johdettu CPolygon-luokasta. Ainoa rajoitus on, että koska ppoly1 ja ppoly2 ovat tyyppiä CPolygon, niillä on käytössä vain ne palvelut, jotka CPolygon tarjoaa, ei johdettujen luokkien palvelut. Esimerkiksi siis pinta-alan laskenta ei ole näiden

osoittimien avulla mahdollista, koska ne määritellään johdetuissa luokissa.

Jos pinta-ala haluttaisiin laskea luokkaan CPolygon osoittavien osoittimien avulla, pitäisi laskenta olla määritelty tässä luokassa. Ongelmaksi muodostuu, että pinta-ala on erilainen kolmiolla ja suorakulmiolle.

Ongelma on mahdollista ratkaista virtuaalisten jäsenten avulla.

// pointers to base class

#include <iostream>

using namespace std;

class CPolygon { protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

};

class CRectangle: public CPolygon { public:

int area ()

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area ()

{ return (width * height / 2); } };

int main () { CRectangle rect;

CTriangle trgl;

CPolygon * ppoly1 = &rect;

CPolygon * ppoly2 = &trgl;

ppoly1->set_values (4,5);

ppoly2->set_values (4,5);

(8)

cout << rect.area() << endl;

cout << trgl.area() << endl;

return 0;

}

Virtuaaliset luokan jäsenet

Luokan jäsen, joka voidaan määritellä uudelleen johdetussa luokassa on nimeltään virtuaalinen jäsen. Tällaisen jäsenen eteen tulee luokan määrittelyssä avainsana virtual.

// virtual members

#include <iostream>

using namespace std;

class CPolygon { protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

virtual int area () { return (0); } };

class CRectangle: public CPolygon { public:

int area ()

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area ()

{ return (width * height / 2); } };

int main () { CRectangle rect;

CTriangle trgl;

CPolygon poly;

CPolygon * ppoly1 = &rect;

CPolygon * ppoly2 = &trgl;

CPolygon * ppoly3 = &poly;

ppoly1->set_values (4,5);

ppoly2->set_values (4,5);

ppoly3->set_values (4,5);

(9)

cout << ppoly1->area() << endl;

cout << ppoly2->area() << endl;

cout << ppoly3->area() << endl;

return 0;

}

Nyt jokaisella luokalla (Cpolygon, CRectangle ja CTriangle) on samat jäsenet: width, height, set_values() ja area(). Jäsenfunktio area() on määritelty virtuaaliseksi kantaluokassa, koska jokainen johdettu luokka toteuttaa oman pinta-alan laskentansa. Jos avainsana virtual poistettaisiin arean edestä, olisivat kaikki pinta-alat nollia. Tämä siksi, että ppoly1, ppoly2 ja ppoly3 ovat osoittimia kantaluokkaan CPolygon, jossa pinta-alaksi tulee aina nolla.

Toisin sanoen: Jos osoitin on kantaluokan tyyppiä mutta osoittaa johdetun luokan olioon, avainsana virtual mahdollistaa oikean jäsenfunktion kutsumisen. Luokka, jossa on tai joka on perinyt virtuaalisen funktion, sanotaan polymorfiseksi luokaksi.

Abstraktit kantaluokat

Abstraktit kantaluokat ovat hyvin samanlaisia kuin edellisen esimerkin CPolygon-luokka. Erona on, että abstraktissa kantaluokassa ei tarvitse määritellä area()-funktion toimintaa. Sen sijaan luokan nimen jälkeen kirjoitetaan luokan määrittelyssä =0. Abstrakti kantaluokka CPolygon näyttäisi tältä:

// abstract class CPolygon class CPolygon {

protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

virtual int area () =0;

};

Funktiolle area() ei siis ole määritelty toimintaa. Tällaista funktiota kutsutaan puhtaaksi virtuaaliseksi funktioksi. Kaikkia sellaisia luokkia, jotka sisältävät ainakin yhden puhtaan virtuaalisen funktion sanotaan abstrakteiksi kantaluokiksi. Pääasiallinen ero aikaisempaan polymorfiseen luokkaan on, että koska abstraktissa kantaluokassa ainakin yhdellä jäsenfunktiolla ei ole määritystä, abstraktista kantaluokasta ei voi suoraan luoda olioita. Vaikka luokasta ei voikaan luoda olioita, voidaan luokkaan osoittaa osoittimilla. Siten määritys

CPolygon poly;

(10)

On virheellinen jos CPolygon on abstrakti kantaluokka, mutta osoittimet CPolygon * ppoly1;

CPolygon * ppoly2;

ovat OK. Näillä osoittimilla voidaan osoittaa johdettujen luokkien olioihin.

// abstract base class

#include <iostream>

using namespace std;

class CPolygon { protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

virtual int area (void) =0;

};

class CRectangle: public CPolygon { public:

int area (void)

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area (void)

{ return (width * height / 2); } };

int main () { CRectangle rect;

CTriangle trgl;

CPolygon *ppoly1 = &rect;

CPolygon *ppoly2 = &trgl;

ppoly1->set_values (4,5);

ppoly2->set_values (4,5);

cout << ppoly1->area() << endl;

cout << ppoly2->area() << endl;

return 0;

}

Edellistä ohjelmaa tutkailemalla havaitaan, että voimme viitata sekä luokkaan

CRectangle

että

luokkaan CTriangle osoittimella luokkaan CPolygon. On esimerkiksi mahdollista tehdä

(11)

jäsenfunktio luokkaan CPolygon, joka tulostaa näytölle olion pinta-alan, vaikka luokassa itsessään ei ole pinta-alan laskentaa:

// pure virtual members can be called // from the abstract base class

#include <iostream>

using namespace std;

class CPolygon { protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

virtual int area (void) =0;

void printarea (void)

{ cout << this->area() << endl; } };

class CRectangle: public CPolygon { public:

int area (void)

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area (void)

{ return (width * height / 2); } };

int main () { CRectangle rect;

CTriangle trgl;

CPolygon *ppoly1 = &rect;

CPolygon *ppoly2 = &trgl;

ppoly1->set_values (4,5);

ppoly2->set_values (4,5);

ppoly1->printarea();

ppoly2->printarea();

return 0;

}

Viimeisessä esimerkissä olioille varataan muistia dynaamisesti:

// dynamic allocation and polymorphism

#include <iostream>

using namespace std;

class CPolygon {

(12)

protected:

int width, height;

public:

void set_values (int a, int b) { width=a; height=b; }

virtual int area (void) =0;

void printarea (void)

{ cout << this->area() << endl; } };

class CRectangle: public CPolygon { public:

int area (void)

{ return (width * height); } };

class CTriangle: public CPolygon { public:

int area (void)

{ return (width * height / 2); } };

int main () {

CPolygon *ppoly1 = new CRectangle;

CPolygon *ppoly2 = new CTriangle;

ppoly1->set_values (4,5);

ppoly2->set_values (4,5);

ppoly1->printarea();

ppoly2->printarea();

delete ppoly1;

delete ppoly2;

return 0;

}

Huomaa, että vaikka osoittimien ppoly1 ja ppoly2 tyyppi on CPolygon, mutta kun niille allokoidaan muistia new-metodilla, ovat tyypit CRectangle ja CTriangle. Tällöin siis käytännössä ppoly1 on tyyppiä CRectangle ja ppoly2 tyyppiä CTriangle.

Viittaukset

LIITTYVÄT TIEDOSTOT

gy system that separated research institutions from firms prior to 1989, there were cases where the industrial research institution was very weakly supervised by the

Itäsuomalaiseen sukunimijärjestelmään kuuluvien sukunimien keskeisimpiä tunto- merkkejä olivat nimen periytyminen suku- polvesta toiseen, sisarusten samannimi- syys,

T his paper studies earnings management in public and private companies and in par- ticular whether earnings management is a func- tion of a company’s leverage� public and private

Permiläisissä kielissä ei esiinny itsenäisenä lekseeminään sanaa *gɔr ’vuori’, mutta komin yhdyssana goruv ’vuoren alarinne’ (uv ’alaosa, -puoli’) ja udmurtin johdos

The public-private co-operation in land development can have many different forms, from the traditional model, in which the municipalities produce building sites, to the

  Liskovin korvausperiaate: alityypit Liskovin korvausperiaate: alityypit käyttäytyvät siten kuin niiden käyttäytyvät siten kuin niiden ylityyppien määrittelyt

  Liskovin korvausperiaate: alityypit Liskovin korvausperiaate: alityypit käyttäytyvät siten kuin niiden käyttäytyvät siten kuin niiden ylityyppien määrittelyt

public class OrderImp OrderImp implements implements Order Order { { private. private List List&lt; &lt;Item Item&gt; itsItems;