olio-ohjelmoinnin perusteet luento 4: periytymisestä
Post on 20-Jan-2016
43 Views
Preview:
DESCRIPTION
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