olio-ohjelmoinnin perusteet luento 4: periytymisestä

Post on 20-Jan-2016

47 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Olio-ohjelmoinnin perusteet luento 4: Periytymisestä. Jani Rönkkönen jani.ronkkonen@lut.fi Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista. Sisältö. Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Periytyminen Toiminnan korvaaminen - PowerPoint PPT Presentation

TRANSCRIPT

1

Olio-ohjelmoinnin perusteetluento 4: Periytymisestä

Jani Rönkkönenjani.ronkkonen@lut.fi

Luennot muokattu Sami Jantusen ja Kari Smolanderin aikaisempien vuosien luennoista

2

Sisältö Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Periytyminen Toiminnan korvaaminen Moniperiytyminen Yhteenveto

3

PeriytyminenMuistatko vielä….?

Periytyminen tarkoittaa periaatetta siitä, että yleisempi määrittely on myös voimassa erikoistuneissa olioissa

Sanomme, että kukkakauppias perii myös kauppiaan ja ihmisen toiminnallisuuden

Ihminen

Kauppias

Kukkakauppias

4

PeriytyminenMuistatko vielä….?

Perinnän idea: Luokat voidaan organisoida

hierarkkisiin periytymispuihin Lapsiluokka perii vanhempiensa

tiedon ja toiminnallisuuden Abstrakti isäluokka on sellainen,

josta ei voida tehdä omaa oliota, mutta jota käytetään lapsiluokkien määrittelyssä

5

PeriytyminenMuistatko vielä….?

6

PeriytyminenMuistatko vielä….?

Eläin

...Niveljalkainen Selkäjänteinen

Hämähäkkieläin Hyönteinen Matelija Nisäkäs Lintu

...

......

... ......

Leppäkerttu Kissa Ihminen

...

7

LuokkahierarkiaMammal

Land-Mammal

int weight

int numLegs

Dogboolean rabid

Chihuahua

giveBirth( )

SheepDog

Kuinka monta attribuuttia koiralla on?

...

8

Huomaathan että:

Land-Mammal on Dog- luokan isäluokka (superclass), mutta Mammal –luokan lapsiluokka (subclass)

Dog –luokalla on kolme attribuuttiaweight, numLegs ja rabidkaksi attribuuttia perinnän kautta ja yksi omassa luokassa

9

Muistatko vielä? Koirat eläintarhassa CDog.h

Vielä paljon kerrottavaa Ei vielä ihan tyylipuhdas luokan esittely.

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

#ifndef CDog_H #define CDog_H#include <iostream.h>class CDog {

int weight;bool rabidOrNot;std:string name;

public:CDog (int x, std::string y);

~CDog(); //tuhoajan esittelybool getRabid ( )const;void setRabid (bool x);std::string getName ( )const;void setName (std::string z);int getWeight ( )const;void setWeight (int x);void eat( );void growl( )const;

}; #endif /* CDog_H */

10

Muistatko vielä? Koirat eläintarhassa CDog.cpp

#include <string.h>#include “CDog.h”

using namespace std;

// ConstructorCDog::CDog (int x, string y) {

rabidOrNot = false;weight = x;name = y;

}// destructorCDog::~CDog(){}void CDog::eat ( ) {

cout << name << “ is eating”<< endl;weight++;}void CDog::growl ( ) const{

cout << “Grrrr”;}

bool CDog::getRabid ( ) const{ return rabidOrNot;}void CDog::setRabid (bool x) { rabidOrNot = x;}int CDog::getWeight ( ) const{ return weight;}void CDog::setWeight (int y) { weight = y;}string CDog::getName ( ) const{ return name;}void setName (string z) { name = z;}

11

Ongelmia!

Mikä tahansa koira ei kelpaa!Puudeli se olla

pitää!

Miten saamme luotua puudelin siten, ettei tarvitsisi kirjoittaa paljon koodia uusiksi??

12

periytyminen:Puudeli on koira

Puudeli on koira (muistathan “is-a” testin)

Käytetään hyväksi CDog-luokan toteutus perimällä siitä CPoodle-luokka

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

CPoodle

13

No niin…. Ryhdytään hommiin!Luodaan puudeli-luokka (sekä .h, että .cpp tiedostot)

#include "CDog.h“ CPoodle.h

class CPoodle: public CDog {public:CPoodle(int x, std::string y);

};

Tässä suoritetaan periytyminen CDog -luokasta

#include <iostream>

#include "CPoodle.h"

using namespace std;

CPoodle::CPoodle(int x, string y) : CDog (x,y){

cout << “Tuli muuten tehtyä puudeli" << endl;}

CPoodle.cpp

14

Mitäs täällä tapahtuu?

Isäluokan rakentajaa kutsutaan aina!*

*Huomaa!: Jos isäluokan rakentajaa ei kutsuta eksplisiittisesti itse, kääntäjä yrittää kutsua automaattisesti isäluokan oletusrakentajaa (mitä ei tässä esimerkissä ole olemassa)

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaalia rakentaja tavaraa

15

Mitä tuli taas tehtyä??

Loimme puudeliluokan jolla on kaikki attribuutit ja metodit kun CDog-luokallakin

16

Ongelmia!

Puudelit ei sano “Grrrrrrr”! Eihän??? Ne sanoo “Yip”!

void CDog::growl ( ) {cout << “Grrrr”;

}

17

Muistatko vielä?Toiminnallisuuden korvaaminen

Eläin

Nisäkäs Lintu

Selkäjänteinen

Nisäkkäät synnyttävät eläviä poikasia

Linnut munivat munia The Australian Platypus on

nisäkäs, mutta luo munia

18

Muistatko vielä?Toiminnallisuuden korvaaminen

On mahdollista korvata (override) isäluokassa määritelty toiminnallisuus toteuttamalla lapsiluokkaan saman niminen toiminnallisuus

Sopivan metodin etsintä aloitetaan aina lapsiluokasta. Jos lapsiluokassa ei ole toteutettuna haluttua toiminnallisuutta, siirrytään etsimään sitä isäluokasta

19

Tehdäänpä jotain puudelin murinalle!!!

Korvataan CDog –luokan growl -metodi

Yksinkertaista, kirjoitetaan Puudeliluokkaan vain samanniminen metodi

GRRRRR

20

Kiltti puudeli!

YIP

CPoodle.cpp

CPoodle::CPoodle(int x, string y) : CDog (x,y){

cout << “Tuli muuten tehtyä puudeli" << endl;

}

void CPoodle::growl( ) const{

cout << "Yip!" << endl;

}

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {

public:

CPoodle(int x, std::string y);

void growl() const;

};

21

Mitä juuri opimme?Periytymisen määrittely

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {public:CPoodle(int x, std::string y);

};

Tässä suoritetaan periytyminen CDog -luokasta

Puhumme periytymisestä hetken kuluttua lisää!

22

Mitä juuri opimme?Muodostimien käyttö periytymisen yhteydessä

Isäluokan muodostinta kutsutaan aina!*

CPoodle.cpp

CPoodle::CPoodle(int x, string y):CDog(x,y)

{

cout << “Tuli muuten tehtyä puudeli" << endl;

}

Normaalia rakentaja tavaraa

Luokkia perittäessä on muodostinten ja purkajien käytössä on paljon huomioitavaa Puhumme tästä lisää ensi viikolla!

23

Mitä juuri opimme?Toiminnan korvaaminen

GRRRRYIPToiminnan korvaaminen on oleellinen osa periytymistä.

Puhumme tästä hetken kuluttua lisää!

24

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet periytyminen Toiminnan korvaaminen Moniperiytyminen Yhteenveto

25

Luokan näkyvyysmääreetclass CPoodle{public: //Tänne tulevat asiat näkyvät //luokasta ulosprotected: //Tänne tulevat asiat näkyvät //vain aliluokilleprivate: //Tänne tulevat asiat ei näy // ulospäin};

26

Sääntöjä

Oletusarvoinen näkyvyysmääre on private

Saatat nähdä koodia, missä jäsenmuuttujat on määritelty luokassa ensimmäisenä ilman näkyvyysmäärettä (=private:). Huonoa tyyliä! Selkeämpää kirjoittaa luokka public:,

protected:, private: -järjestyksessä

27

Public:

Julkinen rajapinta. Kaikki voivat käyttää

Luokka: Koira•murise•syö•kerroPaino•kerroNimi•oletkoVesikauhuinen

28

Public: Ohjeita

Ole huolellinen julkisen rajapinnan suunnittelussa!

Rajapinta on lupaus luokan tarjoamista palveluista.

Rajapintaan kuuluvien asioiden tulisi säilyä muuttumattomina

Rajapinnan tulisi olla “minimaalinen, mutta täydellinen”

Ei ylimääräistä tavaraa “varmuuden vuoksi” Jos jotain puuttuu, niin luokan käyttäjällä ei mitään

mahdollisuuksia korjata puutettaMinun luokka tekee tätä eikä mitään muuta

29

Public: Ohjeita

Jäsenmuuttujat on syytä pitää visusti piilossa!

Tiedon piilottaminen on yksi olioajattelun perusajatuksista

Voi tulla tarve siirtää jäsenmuuttuja muualle tai korvata se jollain toisella rakenteella Et voi tehdä tätä jos olet jo julkaissut muuttujasi

On parempi, että oliolla on täysi kontrolli tietoonsa Jos muuttuja on julkinen, olio ei voi mitenkään

tietää milloin arvoa luetaan tai sitä muutetaanEt pääse “kopeloimaan” tietojani!

30

Protected: Käytetään perittäessä

luokkia Muulloin toimii samoin

kuin private: Sallii lapsiluokkien

käyttää yläluokan jäseniä Lapsiluokka ei pääse

suoraan käsiksi yläluokalta perimiinsä private jäseniin

31

Private: Kaikkein rajoittavin näkyvyysmääre Vain luokka itse voi käyttää private

jäseniä (myös ystäväfunktiot ja –luokat, mutta tästä lisää myöhemmin)

samantyyppinen olio pääsee myös käsiksi toisen olion privaatteihin tietoihin

Julista privaatiksi: jäsenmuuttujat apufunktiot

32

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet Periytyminen Toiminnan korvaaminen Moniperiytyminen Yhteenveto

33

Periytymisen määrittely#include "CDog.h“ CPoodle.h

class CPoodle: public CDog {public:CPoodle(int x, string y);

};

Tässä suoritetaan periytyminen CDog -luokasta

34

Periytymisen määrittely class CPoodle: public CDog

C++:ssa on oletuksena yksityinen periytyminen class B : A {…} tarkoittaa samaa kuin class B : private A {…}

Kaikki isäluokasta perittävät asiat saavat private näkyvyysmääreen lapsiluokassa, riippumatta alkuperäisestä määrityksestä (eli poistuvat lapsiluokan julkisesta rajapinnasta)

Tämä on hieman outoa, sillä julkinen (public:) on kuitenkin yleisintä

35

Periytymisen tavoistaEsimerkki (Hannu Peltosen kirjasta Johdatus C++

ohjelmointikieleen)

Haluamme rakentaa yleiskäyttöisen luokan, jota apuna käyttäen voimme toteuttaa erilaisia 2-suuntaisia listojanextprevdata

nextprevdata

nextprevdata

36

2-suuntainen lista (Deque)

Ensimmäiseksi määrittelemme Deque luokan jonka alkioihin voi tallentaa mitä tahansa tietoa

Luokkaa ei käytetä suoraan, vaan siitä johdetaan uusia luokkia erityyppisten alkioiden tallentamista varten.

nextprevdata

nextprevdata

nextprevdata

37

2-suuntainen lista (Deque)Määritellään ensin jäsenmuuttujat

class Deque{

…private:

struct Item{

Item *prev;Item *next;void *data;

};Item *first;Item *last;Item *curr;

};

nextprevdata

nextprevdata

nextprevdata

38

2-suuntainen lista (Deque)Määritellään julkinen rajapinta

void Deque::goBeforeFirst(){

curr = first;}void Deque:: goAfterLast(){

curr = last;}void Deque:: forth(){

if (curr != last)curr = curr->next;

}void Deque:: back(){

if (curr != first)curr = curr->prev;

}int Deque:: isBeforeFirst() const{

return curr == first;}int Deque:: isAfterLast() const{

return curr == last;}next

prevdata

nextprevdata

nextprevdata

class Deque{public:

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;...

};

39

2-suuntainen lista (Deque)Rakentaja/muodostin

nextprevdata

nextprevdata

nextprevdata

Deque oli tarkoitettu yleiskäyttöiseksi luokaksi, joka ei voi esiintyä yksinään.

Miten voidaan varmistua siitä, että ohjelmassa ei pysty määrittelemään suoraan tyyppiä Deque olevia muuttujia?

40

2-suuntainen lista (Deque)Rakentaja

Deque::Deque(){

first = new Item; last = new Item; first->prev = NULL;first->next = last;First->data = NULL; last->prev = first;last->next = NULL;Last->data = NULL;curr = first;

};

nextprevdata

nextprevdata

nextprevdata

class Deque{...

protected:Deque();...

};

Kun rakentaja on protected, ei sitä voi kutsua muualta kuin periytetystä luokasta

41

2-suuntainen lista (Deque)

Valmista?

Puuttuuko vielä jotain?

42

2-suuntainen lista (Deque)Alkion lisäys

class Deque{

...protected:

void insertBeforeCurrent(void*);void insertAfterCurrent(void*);

...};

void Deque::insertBeforeCurrent(void *p){

if (curr != first){

Item *newItem = new Item;newItem->data = p;newItem->next = curr;newItem->prev = curr->prev;curr->prev->next = newItem;curr->prev = newItem;curr = newItem;

}}void Deque::insertAfterCurrent(void *p){

if (curr != last){

forth();insertBeforeCurrent (p);

}}

nextprevdata

nextprevdata

nextprevdata

43

2-suuntainen lista (Deque)Alkion poisto

class Deque{

...protected:

void *removeCurrentAndGoBack();void *removeCurrentAndGoForth();

private:void *remove(Item *newCurr);...

};

void * Deque::removeCurrentAndGoBack(){

return remove(curr->prev);}

void * Deque::removeCurrentAndGoForth(){

return remove(curr->next);}

void * Deque::remove (Item *newCurr){

if (curr == first || curr == last )return NULL;

else{

void *res = curr->data;curr->prev->next = curr->next;curr->next->prev = curr->prev;delete curr;curr = newCurr;return res; //palautetaan data

}}next

prevdata

nextprevdata

nextprevdata

44

2-suuntainen lista (Deque)Nykyisen alkion saanti ja listan tuhoaminen

class Deque{

...protected:

void *current () const;~Deque(); ...

};

void * Deque::current() const{

return (curr == first || curr == last) ?NULL : curr->data;

}

Deque:: ~Deque (){

Item *p, *next;for (p = first; p != NULL; p = next){

next = p->next;delete p->data;delete p;

}}

nextprevdata

nextprevdata

nextprevdata

45

Hurraa!!!

VIHDOINKIN VALMISTA!

46

Mitä tuli tehtyä?

Loimme luokan joka on elegantti ja yleiskäyttöinen Ei käytetä yksin vaan tästä

johdetaan helposti erilaisia listaratkaisuja minimaallisella työllä

Koodin uudelleenkäyttö! Hyvä esimerkki

oliopohjaisuudesta ja perinnästä!

47

Listoja liukuhihnalta!(IntDeque)Otetaanpa Deque luokka hyötykäyttöön! Luodaan Int-pohjainen lista

class IntDeque: public Deque{public:

IntDeque();void insert (int);void remove();int current () const; ~IntDeque();

};

nextprevdata

nextprevdata

nextprevdata

IntDeque

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntDeque();void insert (int);void remove();int current () const; ~IntDeque();

Deque-luokasta peritty

Uusi toiminnallisuus

48

IntDeque toteutus

IntDeque::IntDeque(){ //kutsutaan isäluokan }//oletusmuodostinta automaattisesti

void IntDeque::insert (int n){

int *ptr = new int;*ptr = n;insertAfterCurrent (ptr);

}

void IntDeque::remove (){

//Kutsutaan delete myös data:lledelete (int*) removeCurrentAndGoBack();

}

int IntDeque::current() const{

int *ptr = (int*)Deque::current();return (ptr != NULL )? *ptr : -1;

}

IntDeque:: ~IntDeque(){

goBeforeFirst();forth();while (!isAfterLast()){

delete (int*)Deque::current();forth();

}}

49

IntDequeMitä opimme? Koodin uudelleenkäyttö on

helppoa ja mukavaa! Kun perit public: -määreellä perit

isäluokan rajapinnan ja saat sen public- ja protected –jäsenet käyttöösi

50

Listoja liukuhihnaltaIntStack

Seuraavaksi haluamme tehdä pinon

Jotain pielessä! Mitä?IntStack

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntStack();int empty () const; void push (int);int top () const; int pop (); ~IntStack();

Deque-luokasta peritty

Uusi toiminnallisuus

51

IntStackjulkinen periytyminen

IntStack

void goBeforeFirst();void goAfterLast();void forth();void back();int isBeforeFirst() const;int isAfterLast() const;IntStack();int empty () const; void push (int);int top () const; void pop (); ~IntStack();

Deque-luokasta peritty

Uusi toiminnallisuus

Ku emmie haluu noin paljon tavaraa miun

julkiseen rajapintaan!!!!!

Mitäs nyt? Haluamme vain käyttää hyväksi olemassa olevaa

toiminnallisuutta. Emme halua periä isäluokan julkista rajapintaa

52

IntStack- yksityinen periytyminen class IntStack : private IntDeque

Perittiin toiminnallisuus, mutta ei rajapintaa Yksityisesti peritty luokka voi käyttää

kantaluokan suojattuja jäseniä aivan kuin julkisestikin johdettu luokka

Luokkien ulkopuolella IntStack:lla ja IntDequella ei näytä olevan mitään tekemistä keskenään.

Mitä saimme aikaan yksityisellä perinnällä?

53

protected periytyminen class IntStack : protected IntDeque

Yhteneväisyydet: Molemmat sallivat kantaluokan toiminnan korvaamisen Kumpikaan ei tunnusta sukulaisuuttaan isäluokkaan

Erot: protected periytyminen sallii lastenlasten tietävän

periytymissuhteesta. protected johtamastasi luokasta perityt luokat näkevät sisältösi.

protected perinnän hyöty: sallii lapsiluokkiesi käyttävän hyväksi isäluokkasi

toiminnallisuutta protected perinnän haitta:

protected perinnän muuttaminen saattaa rikkoa jotain lapsiluokissasi.

Kuinka protected periytyminen eroaa private periytymisestä?

54

private- ja protected periytymiseen liittyvät näkyvyyssäännöt

Tarkastellaan seuraavia esimerkkejä:   class B                 { /*...*/ }; class D_priv : private   B { /*...*/ }; class D_prot : protected B { /*...*/ }; class D_publ : public    B { /*...*/ }; class UserClass  { B b; /*...*/ };

Mikään perityistä luokista ei pääse B-luokan yksityisiin jäseniin

B-luokan public- ja protected jäsenet ovat: D_priv- luokassa private D_prot –luokassa protected, näkyvät D_publ –luokassa samalla lailla kuin B-luokassakin

UserClass-luokka näkee vain B:n julkiset jäsenet

55

Jäsenkohtainen näkyvyyksien määrittely

Tarkastellaan edelleen seuraavaa esimerkkiä: class B                 { /*...*/ }; class D_priv : private   B { /*...*/ }; class D_prot : protected B { /*...*/ }; class D_publ : public    B { /*...*/ }; class UserClass  { B b; /*...*/ };

On mahdollista palauttaa private tai protected perinnässä muuttuneet jäsenten näkyvyydet. Suojausta ei voi muuttaa vapaammaksi tai tiukemmaksi kuin mikä se on kantaluokassa. Esimerkki:

Halutaan tietty B:n public jäsen näkyvän julkisena myös D_priv tai D_prot –luokassa.

     class D_prot : protected B {    public:       B::f;      };

Tällaista kikkailua pitää välttää!!!

56

Takaisin IntStack:n pariin Päätimme siis käyttää privaattia periytymistä. Uudelleenkäyttö on taas nopeaa ja helppoa. Ei

paljon mitään kirjoitettavaa:

IntStack::IntStack(){ //isäluokan rakentaja riittää}

int IntStack::empty() const{

return isBeforeFirst();}

void IntStack::push(int n) {

insert(n);}

int IntStack::top() const{

return current();}

void IntStack ::pop(){

remove();}

IntStack :: ~ IntStack(){ //isäluokan purkaja riittää}

57

On toinenkin tapa…

Usein yksityisen perinnän kaltainen lopputulos saadaan aikaan liittämällä kantaluokan olio toisen luokan jäsenmuuttujaksi.

Deque

IntStack

<<private>> IntDeque

IntStack

Deque

58

Muistatko vielä? AggregaatioAggregaatiot ovat erikoistuneita assosiaatioita kuvaamaan olion koostumista muista olioista

Kokonaisuutta kuvaavaa puolta kutsutaan aggregaatiksi (aggregate)

Aggregaatio kuvataan assosiaation päässä olevalla timantilla.

****

****** Region

VehiclePart

Country

Vehicle

59

IntStack aggregaatiota hyväksi käyttäen

class IntStack

{

public:

IntStack();

int empty () const;

void push (int);

int top () const;

void pop ();

~IntStack();

private:

IntDeque q;

};

IntStack::IntStack(){ //q alustetaan IntDequen rakentajassa}

int IntStack::empty() const{

return q.isBeforeFirst();}

void IntStack::push(int n) {

q.insert(n);}int IntStack::top() const{

return q.current();}

void IntStack ::pop(){

q.remove();}

IntStack :: ~ IntStack(){ //isäluokan purkaja riittää}

60

IntStack (private periytyminen) vs. IntStack (aggregaatio)

Yhteistä: Molemmat kuvaavat (has-a) koostetta. Kummassakin tapauksessa yhteys Deque-luokkaan on piilotettu

Eroja: Kooste voi sisältää useita olioita Yksityisessä perinnässä johdettu luokka voi käyttää

kantaluokan suojattuja jäseniä Kumpaa tapaa kannattaa käyttää?

Käytä aggregaatiota aina kun pystyt (=AINA) Käytä yksityistä periytymistä kun on pakko Tyypillisesti et halua päästä käsiksi muiden luokkien

sisälmyksiin. Yksityinen periytyminen antaisi sinulle tällaista ylimääräistä voimaa (ja vastuuta)

Yksityinen periytyminen on rakaampaa ylläpitää, sillä silloin on suurempi vaara, että joku muuttaa koodia siten, että se menee rikki

61

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet periytyminen Toiminnan korvaaminen Moniperiytyminen Yhteenveto

62

Toiminnan korvaaminen

GRRRRYIP Joskus aliluokan olion on tarpeen

suorittaa kantaluokasta perimänsä palvelu hieman kantaluokasta poikkeavalla tavalla

Aliluokka haluaa siis periä rajapinnan, mutta ei toteutusta

C++ tarjoaa mahdollisuuden uudelleenmääritellä (override) kantaluokasta poikkeava toiminto. Määrittelet kyseisen funktion kantaluokassa vain virtuaaliseksi (avainsanalla virtual)

63

GRRR? YIP? VIRTUAL?? Hetkinen, eihän esimerkissä

määritelty growl –funktiota virtuaaliseksi!

Ei niin, esimerkissä oli itse asiassa kyseessä funktion peittäminen

Funktion peittäminen tapahtuu kirjoittamalla lapsiluokkaan täsmälleen saman niminen funktio

Funktion peittämisen yhteydessä lapsiluokka peittää kaikki samannimiset kantaluokan funktiot

Toiminnon peittäminen ja korvaaminen käyttäytyvät eri tavalla riippuen kutsutavasta.

64

Toiminnan peittäminen

Mitä tapahtuu?CPoodle *myPoodle;

myPoodle = new CPoodle();

myPoodle->growl();

((CDog*)myPoodle)->growl();

CDogbool rabidOrNot

int weight

string name

void growl()void eat()

CPoodlevoid growl() Vastaus:

YIPGRRRRRRRR

65

Toiminnan korvaaminen

Mitä tapahtuu?CPoodle *myPoodle;

myPoodle = new CPoodle();

CPoodle->growl();

((CDog*)myPoodle)->growl();

CDogbool rabidOrNot

int weight

string name

virtual void growl()void eat()

CPoodlevoid growl() Vastaus:

YIPYIP

66

Toiminnan korvaamisesta

Kutsutilanteesta riippuva jäsenfunkiton valinta ei ole toivottavaa

Tällainen tapahtuu helposti vahingossa silloin, kun kantaluokan jäsenfunktion esittelystä unohtuu avainsana virtual Silloin ei auta vaikka lapsiluokassa virtual

löytyisikin

67

Noniin… takaisin toiminnan korvaamisen pariin. Toiminta määritellään korvattavaksi siis virtual

avainsanaa käyttäen. Tämän jälkeen kantaluokasta periytettävillä aliluokilla on kaksi mahdollisuutta:

Hyväksyä kantaluokan tarjoama jäsenfunktion toteutus. Tällöin aliluokan ei tarvitse tehdä mitään

kantaluokan toteutus periytyy automaattisesti myös aliluokkaan

Kijoitaa oma toteutuksensa perimälleen jäsenfunktiolle. Tässä tapauksessa aliluokan esittelyssä esitellään jäsenfunktio uudelleen, ja sen jälkeen aliluokan toteutuksessa kirjoitetaan jäsenfunktiolle uusi toteutus aivan kuin normaalille aliluokan jäsenfunktiolle. Aliluokan esittelyssä avainsanan virtual toistaminen ei ole pakollista, mutta kylläkin hyvän ohjelmointityylin mukaista

68

Toiminnan korvaamisestaHuomioitavaa

On tärkeää, että korvaava funktio tarjoaa kantaluokan kannalta saman palvelun kuin alkuperäinenkin funktio.

Aliluokka voi muuttaa vain toteutusta, ei rajapintaa.

69

Päivitetään CDog.h:

Jäsenmuuttujille oma private

lohko

Growl funktiosta virtuaalinen

CDogbool rabidOrNot

int weight

string name

virtual void growl()void eat()

CPoodlevoid growl()

#ifndef CDog_H #define CDog_H#include <iostream.h>class CDog {public:

CDog (int x, std::string y); ~CDog(); //tuhoajan esittely

bool getRabid ( )const;void setRabid (bool x);std::string getName ( )const;void setName (std::string z);int getWeight ( )const;void setWeight (int x);void eat( );virtual void growl( )const;

private: int weight;

bool rabidOrNot;std:string name;

}; #endif /* CDog_H */

70

Ei muutoksia CDog.cpp:

#include <string.h>#include “CDog.h”

using namespace std;

// ConstructorCDog::CDog (int x, string y) {

rabidOrNot = false;weight = x;name = y;

}// destructorCDog::~CDog(){}void CDog::eat ( ) {

cout << name << “ is eating”<< endl;weight++;}void CDog::growl ( ) const{

cout << “Grrrr”;}

bool CDog::getRabid ( ) const{ return rabidOrNot;}void CDog::setRabid (bool x) { rabidOrNot = x;}int CDog::getWeight ( ) const{ return weight;}void CDog::setWeight (int y) { weight = y;}string CDog::getName ( ) const{ return name;}void setName (string z) { name = z;}

71

Myös puudeliin virtuallinen growl metodi:

YIP

CPoodle.cpp#include <iostream>

#include "CPoodle.h"

using namespace std;

CPoodle::CPoodle(int x, string y) : CDog (x,y){}

void CPoodle::growl( ) const{

cout << "Yip!" << endl;}

#include "CDog.h“ CPoodle.h

class CPoodle:public CDog {

public:

CPoodle(int x, std::string y);

virtual void growl() const;

};

72

Ja taas esimerkkiKoirat Kennelissä: Zoo.cpp

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

GRRRR

Mitä tämä ohjelma tekee?

YIP

73

Koirat Kennelissä Syöte:12211

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

Tuloste:GRRRYIPYIPGRRRGRRR

74

No mitäs kivaa tuossa oli? Esimerkki esitteli virtuaalifunktoiden

toiminnan puhtaimmillaan Täsmälleen sama koodirivi:

(kennel[i]->growl();) tuotti erilaisia tuloksia Kääntäjä ei kaikissa tapauksissa pysty vielä

käännösaikana päättelemään mitä rajapintafunktion totetusta on tarkoitus kutsua

Päätös tästä siirtyykin ajonaikaiseksi. Tästä käytetään nimitystä dynaaminen sitominen (dynamic binding)

Dynaaminen sitominen mahdollistaa siis sen, että sama jäsenfunktiokutsu käyttäytyy eri tavalla riippuen siitä, minkä tyyppinen olio osoittimen tai viittauksen päässä on

75

Dynaaminen sitominen (dynamic binding)

Koska growl-funktio on virtuaalinen, voidaan sen toteutus määritellä uudelleen missä tahansa periytetyssä luokassa.

Niinpä kääntäjä tietää vain, että siinä kutsutaan jotain jäsenfunktion growl toteutusta.

Kääntäjä tuottaa kyseiseen ohjelman kohtaan koodin, joka ensin tarkastaa osoittimen päässä olevan olion todellisen luokan ja vasta sen jälkeen kutsuu sille sopivaa jäsenfunktion toteutusta

void main(){

CDog *kennel[5];CDog *valittuTyyppi;int valinta;

for (int i=0; i<5; i++){ cout<<“(1)Koira (2)Puudeli”; cin >> valinta; switch (valinta) { case 1: valittuTyyppi=new CDog(1,”koira”); break; case 2: valittuTyyppi=new CPoodle(1,”puudeli”); break; }

kennel[i]=valittuTyyppi;}for (i=0; i<5; i++)

kennel[i]->growl();}

76

Virtuaalifunktoiden hyödyt

Virtuaalifunktiot ja dynaaminen sitominen tekevät mahdollisiksi todella joustavat ohjelmarakenteet jäsenfunktion kutsujan ei tarvitse tietää

yksityiskohtia siitä, mitä jäsenfunktion toteutusta kutsutaan

Ohjelman ylläpidettävyys, laajennettavuus ja luettavuus paranee

77

Virtuaalifunktioiden hinta Ohjelmakoodin täytyy aina

virtuaalifunktioiden yhteydessä: tarkastaa olion todellinen luokka valita oikea versio jäsenfunktion toteutuksesta

Em. tehtävät jää lähes aina ajonaikaiseksi. valinnan tekeminen hidastaa jäsenfunktion

kutsumista. Käytännön kokemusten mukaan

virtuaalifunktioiden käyttö on n. 4% hitaampaa

78

Virtuaalifunktioiden hinta Virtuaalifunktiot lisäävät myös muistin

kulutusta: Mikäli luokassa tai sen kantaluokassa on yksikin

virtuaalifunktio, täytyy luokan olioihin tallettaa jonnekkin tieto siitä, minkä luokan olioita ne ovat

Tähän käytetään yleensä virtuaalitaulua (v-taulu) Jokaista luokkaa kohden on yksi v-taulu ja jokaisella

luokan tyyppisellä oliolla on osoitin v-tauluun (virtuaalitaulujen toteutus riippuu kääntäjistä)

osoitin v-tauluun lisää olion muistin kulutusta n. 4 tavun verran. Ylimääräisten virtuaalifunktioiden lisääminen ei kasvata v-taulun osoittimien määrää.

79

Missä mennään Johdanto

Kertausta Esimerkki Yhteenveto

Luokkien näkyvyysmääreet periytyminen Toiminnan korvaaminen Moniperiytyminen Yhteenveto

80

Moniperiytyminen

Kertausta: Periytymisessä uusi luokka

luodaan periyttämällä siihen jonkin olemassa olevan luokan ominaisuudet.

Miksei sitten periytetä kerralla ominaisuuksia useammasta luokasta?

81

Moniperiytyminen C++ mahdollistaa moniperinnän. Syntaksi:

class Pegasus : public Bird, public Horse Moniperiytyminen on hyvin kiistelty

mekanismi: Kaikki oliopohjaiset kielet ei tue moniperiytymistä

Pyri välttämään moniperinnän käyttöä moniperinnän käyttö johtaa varsin usein ongelmiin asiat voidaan yleensä ratkaista muillakin tavoilla.

Joskus moniperiytyminen on vain vähemmän työläämpää

82

Moniperiytyminen -Esimerkki

KirjastonTeos

Hyllyluokka, lainaaika, yms.

KirjastonKirja

(Hyllyluokka, lainaaika, yms.)( Nimi, tekijä, yms.)Mahd. uudet ominaisuudet

Kirja

Nimi, tekijä, yms.

class KirjastonKirja : public KirjastonTeos, public Kirja{

…};

83

Moniperiytyminen -käyttökohteita Rajapintojen yhdistäminen.

Halutaan oman luokan toteuttavan useiden eri rajapintojen toiminnallisuus

Luokkien yhdistäminen. Halutaan esimerkiksi käyttää hyväksi muutamaa yleiskäyttöistä

luokkaa oman luokan kehitystyössä. Luokkien koostaminen valmiista ominaisuuskokoelmista.

Esimerkki: Kaikki lainaamiseen liittyvät toiminnot on kirjoitettu Lainattava-

luokkaan. Vastaavasti kaikki tuotteen myymiseen liittyvät aisat ovat

luokassa Myytävät. Voimme luoda KirjastonKirja –luokan perimällä sen Kirja-

kantaluokasta ja maustamalla sen Lainattava-luokasta saaduilla ominaisuuksilla

Voimme yhtä lailla luoda KaupallinenCD-ROM-luokan perimällä sen CD-ROM kantaluokasta ja ottaa käyttöön ominaisuudet Myytävä-luokasta

84

Moniperitytymisen vaaroja Suuri osa moniperiytymisen vaaroista johtuu siitä, että se

on houkuttelevan helpontuntuinen vaihtoehto sellaisissakin tapauksissa, joissa se ei olioajattelun kannalta ole perusteltua.

Moniperityn luokan täytyy olla kaikein aikaa perittyjen kantaluokkiensa ilmentymä

Se ei vaan käy että välillä ollaan yhtä ja välillä toista Esim: Vesitaso ei pysty olemaan yhtäaikaa vene ja lentokone.

Tekee luokkarakenteen vaikeaselkoisiksi Aiheuttaa helposti ongelmia kuten rajapintojen

moniselitteisyyttä ja vaikeuksia elinkaaren hallinnassa. Älä siis käytä moniperiytyminenä ellei sille ole

painavia perusteita

85

Moniperiytyminen ja moniselitteisyys

Kumpaa funktiota kutsutaan kun KirjastonKirjaa pyydetään tulostamaan tiedot?

KirjastonTeos

Hyllyluokka, lainaaika, yms.

KirjastonKirja

(Hyllyluokka, lainaaika, yms.)( Nimi, tekijä, yms.)Mahd. uudet ominaisuudet

Kirja

Nimi, tekijä, yms.

tulostaTiedot()tulostaTiedot()

86

Moniperiytyminen ja moniselitteisyys Yritys kutsua kahdesta eri kantaluokasta periytynyttä

jäsenfunktiota aiheuttaa C++:ssa käännösaikaisen virheilmoituksen siitä, että jäsenfunktion kutsu on moniselitteinen (ambiguous)

Jos kummankin kantaluokan funktiot tekevät suunnilleen saman asian voidaan ongelma kiertää määrittelemällä samannimiset funktiot virtuaalisiksi (tästä puhutaan myöhemmin). Tällöin kutsutaa aliluokassa toteutettua funktiota

Jos kantaluokan funktiot taas ovat sisällöltään vahvasti erilaisia ajaudumme suurempiin ongelmiin. Tällöin on vaikea saada peritty luokka käyttäytymään siten, että se tyydyttää molemman kantaluokan tarpeet.

87

Moniperiytyminen ja moniselitteisyys Jos moniselitteisille jäsenfunktioille ei

ole tarkoitus antaa uusia toteutuksia moniperiytetyssä aliluokassa, muodostuu ainoaksi ongelmaksi jäsenfunktion kutsuminen. Tämäkin vain silloin kun kutsutaan suoraan

aliluokan rajapinnan kautta Kantaluokkaosoittimien kautta

moniselitteisyyttä ei ole. Kullakin kantaluokalla on vain yksi

mahdollinen toteutus jäsenfunktiolle

88

Moniperiytyminen ja moniselitteisyysRatkaisuja Kutsutaan moniselitteistä jäsenfunktiota aina

kantaluokkaosoittimien kautta tarvittaessa vaikkapa väliaikaisia osoitinmuuttujia käyttäen

helpoin, mutta kömpelöin ratkaisu Moniselitteisen jäsenfunktion kutsun yhteydessä on

mahdollista erikseen kertoa, minkä kantaluokan versiota halutaan kutsua. Tämä onnistuu ::-syntaksilla. Esimerkiksi Kirja-luokan tulostaTiedot-jäsenfunktiota voi kutsua syntaksilla:

KirjastonKirja k;k.Kirja::tulostaTiedot()

Tämä syntaksi on kuitenkin myös ehkä hieman oudon näköinen ja vaatii kantaluokan nimen kirjoittamista näkyviin kutsun yhteyteen

Kolmas vaihtoehto on kirjoittaa aliluokkaan uudet keskenään erinimiset jäsenfunktiot, jotka kutsuvat kunkin kantaluokan toteutusta moniselitteiselle jäsenfunktiolle ::-syntaksilla.

89

Esimerkki (3. vaihtoehto)

classs KirjastonKirja : public KirjastonTeos, public Kirja {public:

.

.

.void tulostaKTeostiedot (std::ostrea& virta) const;void tulostaKKirjatiedot (std::ostrea& virta) const;

}void KirjastonKirja::tulostaKTeostiedot (std::ostrea& virta) const{

KirjastonTeos::tulostaTiedot(virta);}void KirjastonKirja::tulostaKirjatiedot (std::ostrea& virta) const{

Kirja::tulostaTiedot(virta);}

90

Mitä tällä luennolla opimme? Puudeli-esimerkin avulla opimme laajentamaan

olemassa olevaa toteutusta periytyminenä käyttämällä

Kuinka jäsenmuuttujien ja –funktioiden näkyvyyttä voidaa hallita luokan sisällä (public, protected, private

2-suuntainen linkitetty lista taas opetti koodin tekemisestä uudelleenkäytettäväksi uudelleenkäytettävän koodin hyväksikäyttöä mitä eroa on public- ja private perinnällä kuinka aggregaatio eroaa private perinnästä

Koirat kennelissä esimerkin avulla opimme dynaamisesta sidonnasta ja virtaalifunktioista

Moniperiytyminen

top related