Esittely latautuu. Ole hyvä ja odota

Esittely latautuu. Ole hyvä ja odota

Olio-ohjelmoinnin perusteet luento 4: Perinnästä Sami Jantunen LTY/Tietotekniikan osasto.

Samankaltaiset esitykset


Esitys aiheesta: "Olio-ohjelmoinnin perusteet luento 4: Perinnästä Sami Jantunen LTY/Tietotekniikan osasto."— Esityksen transkriptio:

1 Olio-ohjelmoinnin perusteet luento 4: Perinnästä Sami Jantunen LTY/Tietotekniikan osasto

2 Sisältö Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

3 Perintä Muistatko vielä….? Perintä 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 Perintä Muistatko vielä….? Perinnän idea: Luokat voidaan organisoida hierarkkisiin perintäpuihin Lapsiluokka perii vanhempiensa tiedon ja toiminnallisuuden Abstrakti isäluokka on sellainen, mistä ei voidan tehdä omaa oliota, mutta mitä käytetään lapsiluokkien määrittelyssä

5 Perintä Muistatko vielä….?

6 Eläin... NiveljalkainenSelkäjänteinen HämähäkkieläinHyönteinenMatelijaNisäkäsLintu... LeppäkerttuKissaIhminen...

7 Luokkahierarkia Mammal Land-Mammal int weight int numLegs Dog boolean 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 attribuuttia weight, numLegs ja rabid kaksi 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. //CDog.h class CDog { public: int weight; bool rabid; char name [ ]; CDog (int x, char y[ ]); bool getRabid ( ); void setRabid (bool x); char [ ] getName ( ); void setName (char z[ ]); int getWeight ( ); void setWeight (int x); void bark( ); void growl( ); }; CDog bool rabidOrNot int weight string name void growl() void eat()

10 Muistatko vielä? Koirat eläintarhassa CDog.cpp #include // Constructor CDog::CDog (int x, char y[ ]) { rabid = false; weight = x; strcpy(name, y); } void CDog::eat ( ) { cout << name << “ is eating”; } void CDog::growl ( ) { cout << “Grrrr”; } bool CDog::getRabid ( ) { return rabid; } void CDog::setRabid (bool x) { rabid = x; } int CDog::getWeight ( ) { return weight; } void CDog::setWeight (int y) { weight = y; } char[ ] CDog::getName ( ) { return name; } void setName (char 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 Perintä: Puudeli on koira Puudeli on koira (muistathan “is-a” testin) Käytetään hyväksi CDog-luokan toteutus perimällä siitä CPoodle-luokka CDog bool 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, char y[ ]); }; Tässä suoritetaan perintä CDog - luokasta CPoodle.cpp CPoodle::CPoodle(int x, char y[ ]) : CDog (x,y){ cout << “Tuli muuten tehtyä puudeli" << endl; }

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, char 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äsLintu 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, char y[ ]) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; } void CPoodle::growl( ) { cout << "Yip!" << endl; } #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, char y[ ]); void growl(); };

21 Mitä juuri opimme? Näkyvyysmääreet class CPoodle { public: //Tänne tulevat asiat näkyvät //luokasta ulos protected: //Tänne tulevat asiat näkyvät //vain aliluokille private: //Tänne tulevat asiat ei näy // ulospäin }; Puhumme näkyvyysmääreistä hetken kuluttua lisää!

22 Mitä juuri opimme? Perinnän määrittely #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, char y[ ]); }; Tässä suoritetaan perintä CDog - luokasta Puhumme perinnästä hetken kuluttua lisää!

23 Mitä juuri opimme? Rakentajien käyttö perinnän yhteydessä Isäluokan rakentajaa kutsutaan aina!* CPoodle.cpp CPoodle::CPoodle(int x, char y[ ]) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; } Normaalia rakentaja tavaraa Luokkia perittäessä on rakentajien ja purkajien käytössä on paljon huomioitavaa Puhumme tästä hetken kuluttua lisää!

24 Mitä juuri opimme? Toiminnan korvaaminen GRRRRYIP Toiminnan korvaaminen on oleellinen osa perintää. Puhumme tästä hetken kuluttua lisää!

25 Missä mennään Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

26 Luokan näkyvyysmääreet (Syventävät opinnot ) class CPoodle { public: //Tänne tulevat asiat näkyvät //luokasta ulos protected: //Tänne tulevat asiat näkyvät //vain aliluokille private: //Tänne tulevat asiat ei näy // ulospäin };

27 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ä

28 Public: Julkinen rajapinta. Kaikki voi käyttää Luokka: Koira murise syö kerroPaino kerroNimi oletkoVesikauhuinen

29 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 puutetta Minun luokka tekee tätä eikä mitään muuta

30 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ä muutetaan Et pääse “kopeloimaan” tietojani!

31 Protected: Käytetään perittäessä luokkia Muulloin toimii samoin kuin private: Sallii lapsiluokkien käyttää jäseniään

32 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

33 Missä mennään Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

34 Perinnän määrittely (Syventävät opinnot ) #include "CDog.h“ CPoodle.h class CPoodle:public CDog { public: CPoodle(int x, char y[ ]); }; Tässä suoritetaan perintä CDog - luokasta

35 Perinnän määrittely class CPoodle:public CDog C++:ssa on oletuksena yksityinen perintä class B : A {…} tarkoittaa samaa kuin class B : private A {…} Tämä on hieman outoa, sillä julkinen (public:) on kuitenkin yleisintä

36 Perintätavoista Esimerkki (Hannu Peltosen kirjasta Johdatus C++ ohjelmointikieleen) Haluamme rakentaa yleiskäyttöisen luokan, jota apuna käyttäen voimme toteuttaa erilaisia 2-suuntaisia listoja next prev data next prev data next prev data

37 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. next prev data next prev data next prev data

38 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; }; next prev data next prev data next prev data

39 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 prev data next prev data next prev data class Deque { public: void goBeforeFirst(); void goAfterLast(); void forth(); void back(); int isBeforeFirst() const; int isAfterLast() const;... };

40 2-suuntainen lista (Deque) Rakentaja next prev data next prev data next prev data 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?

41 2-suuntainen lista (Deque) Rakentaja Deque::Deque() { first = new Item; last = new Item; first->prev = NULL; first->next = last; last->prev = first; last->next = NULL; curr = first; }; next prev data next prev data next prev data class Deque {... protected: Deque();... };

42 2-suuntainen lista (Deque) Valmista? Puuttuuko vielä jotain?

43 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; newIteam->next = curr; newItem->prev = curr->prev; curr->prev->next = newItem; curr->prev = newItem; curr = newItem; } void insertAfterCurrent(void *p) { if (curr != last) { forth(); insertBeforeCurrent (p); } next prev data next prev data next prev data

44 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 { voic *res = curr->data; curr->prev->next = curr->next; curr->next->prev = curr->prev; delete curr; curr = newCurr; return res; } next prev data next prev data next prev data

45 2-suuntainen lista (Deque) Nykyisen alkion saanti ja listan tuhoaminen class Deque {... protected: void *current () const; void ~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; } next prev data next prev data next prev data

46 Hurraa!!! VIHDOINKIN VALMISTA!

47 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ä!

48 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(); }; next prev data next prev data next prev data 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

49 IntDeque toteutus IntDeque::IntDeque() { } void IntDeque::insert (int n) { int *ptr = new int; *ptr = n; insertAfterCurrent (ptr); } void IntDeque::remove () { delete (int*) removeCurrentAndGoBack(); } void IntDeque::current() const { int *ptr = (*int)Deque::current(); return (ptr != NULL )? *ptr : - 1; } IntDeque:: ~IntDeque() { goBeforeFirst(); forth(); while (!isAfterLast()) { delete Deque::current(); forth(); }

50 IntDeque Mitä 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

51 Listoja liukuhihnalta IntStack Seuraavaksi haluamme tehdä pinon Jotain pielessä! Mitä? IntDeque 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

52 IntStack julkinen perintä IntDeque 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 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

53 IntStack- yksityinen perintä 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ä?

54 protected perintä class IntStack : protected IntDeque Yhteneväisyydet: Molemmat sallivat kantaluokan toiminnan korvaamisen Kumpikaan ei tunnusta sukulaisuuttaan isäluokkaan Erot: protected perintä sallii lastenlasten tietävän perintäsuhteesta.  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 hajoittaa jotain lapsiluokissasi. Kuinka protected perintä eroaa private perinnästä?

55 private- ja protected perintää 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

56 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; };

57 Takaisin IntStack:n pariin Päätimme siis käyttää privaattia perintää. 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ää }

58 On toinenkin tapa… Usein yksityisen perinnän kaltainen lopputulos saadaan aikaan liittämällä kantaluokan olio toisen luokan jäsenmuuttujaksi. Deque IntStack >  IntDeque IntStack Deque

59 Muistatko vielä? Aggregaatio Aggregaatiot ovat erikoistuneita assosiaatioita kuvaamaan luokan koostumista muista luokista Kokonaisuutta kuvaavaa puolta kutsutaan aggregaatiksi (aggregate) Aggregaatio kuvataan assosiaation päässä olevalla timantilla. **** ****** Region VehiclePart Country Vehicle

60 IntStack aggregaatiota hyväksi käyttäen class IntStack { public: IntStack(); int empty () const; void push (int); int top () const; int 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ää }

61 IntStack (private perintä) vs. IntStack (aggregaatio) Yhteistä: Molemmat kuvaavat (has-a) koostetta. Kummassakin tapauksessa yhtyes 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 Käytä yksityistä perintää kun on pakko Tyypillisesti et halua päästä käsiksi muiden luokkien sisälmyksiin. Yksityinen perintä antaisi sinulle tällaista ylimääräistä voimaa (ja vastuuta) Yksityinen perintä on rakaampaa ylläpitää, sillä silloin on suurempi vaara, että joku muuttaa koodia siten, että se menee rikki

62 Rakentajien käyttö perinnän yhteydessä Isäluokan rakentajaa kutsutaan aina!* CPoodle.cpp CPoodle::CPoodle(int x, char y[ ]) : CDog (x,y) { cout << “Tuli muuten tehtyä puudeli" << endl; } Normaalia rakentaja tavaraa PUHUTAAN TÄSTÄ LISÄÄ SEURAAVALLA LUENNOLLA!

63 Missä mennään Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

64 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 )

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

66 Toiminnan peittäminen Mitä tapahtuu? CPoodle *myPoodle; myPoodle = new CPoodle(); CPoodle->growl(); ((CDog*)myPoodle)->growl(); CDog bool rabidOrNot int weight string name void growl() void eat() CPoodle void growl() Vastaus: YIP GRRRRRRRR

67 Toiminnan korvaaminen Mitä tapahtuu? CPoodle *myPoodle; myPoodle = new CPoodle(); CPoodle->growl(); ((CDog*)myPoodle)->growl(); CDog bool rabidOrNot int weight string name virtual void growl() void eat() CPoodle void growl() Vastaus: YIP

68 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

69 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

70 Toiminnan korvaamisesta Huomioitavaa On tärkeää, että korvaava funktio tarjoaa kantaluokan kannalta saman palvelun kuin alkuperäinenkin funktio. Aliluokka voi muuttaa vain toteutusta, ei rajapintaa.

71 Ja taas esimerkki Koirat Kennelissä YIP 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: valittyTyyppi=new CDog; break; case 2: valittyTyyppi=new CPoodle; break; } kennel[i]=valittuTyyppi; } for (int i=0; i<5; i++) kennel[i]->growl(); } GRRRR Mitä tämä ohjelma tekee?

72 Koirat Kennelissä 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: valittyTyyppi=new CDog; break; case 2: valittyTyyppi=new CPoodle; break; } kennel[i]=valittuTyyppi; } for (int i=0; i<5; i++) kennel[i]->growl(); } Syöte: 1 2 1 Tuloste: GRRR YIP GRRR

73 No mitäs kivaa tuossa oli? Esimerkki esitteli virtuaalfunktoiden 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

74 Dynaaminen sitominen 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: valittyTyyppi=new CDog; break; case 2: valittyTyyppi=new CPoodle; break; } kennel[i]=valittuTyyppi; } for (int i=0; i<5; i++) kennel[i]->growl(); }

75 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

76 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

77 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ää.

78 Missä mennään Johdanto Kertausta Esimerkki Yhteenveto Luokkien näkyvyysmääreet Perintä Toiminnan korvaaminen Moniperintä Yhteenveto

79 Moniperintä Kertausta: Periytymisessä uusi luokka luodaan periyttämällä siihen jonkin olemassa olevan luokan ominaisuudet. Miksei sitten periytetä kerralla ominaisuuksia useammasta luokasta?

80 Moniperintä C++ mahdollistaa moniperinnän. Syntaksi: class Pegasus : public Bird, public Horse Moniperintä on hyvin kiistelty mekanismi: Kaikki oliopohjaiset kielet ei tue moniperintää Pyri välttämään moniperinnän käyttöä moniperinnän käyttö johtaa varsin usein ongelmiin asiat voidaan yleensä ratkaista muillakin tavoilla. Joskus moniperintä on vain vähemmän työläämpää

81 Moniperintä -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 { … };

82 Moniperintä -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

83 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ä moniperintää ellei sille ole painavia perusteita

84 Moniperintä 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()

85 Moniperintä 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.

86 Moniperintä 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 Kantaluokkaosoittimienkautta moniselitteisyyttä ei ole. Kullakin kantaluokalla on vain yksi mahdollinen toteutus jäsenfunktiolle

87 Moniperintä ja moniselitteisyys Ratkaisuja 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.

88 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); }

89 Mitä tällä luennolla opimme? Puudeli-esimerkin avulla opimme laajentamaan olemassa olevaa toteutusta perintää 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 eroa private perinnästä Koirat kennelissä esimerkin avulla opimme dynaamisesta sidonnasta ja virtaalifunktioista Moniperintä


Lataa ppt "Olio-ohjelmoinnin perusteet luento 4: Perinnästä Sami Jantunen LTY/Tietotekniikan osasto."

Samankaltaiset esitykset


Iklan oleh Google