812347A Olio-ohjelmointi, 2015 syksy 2. vsk IV Periytyminen ja monimuotoisuus
Sisältö 1. Periytyminen 2. Johdanto monimuotoisuuteen 3. Ylikuormittaminen 4. Uudelleenmäärittely 5. Muuttujien monimuotoisuus 6. Multimetodit ja double dispatch monimuotoisuus 2
IV.1 Periytyminen Uudelleenkäytetään olemassaolevan luokan ominaisuuksia määriteltäessä uutta luokkaa Periytyvää luokkaa kutsutaan kantaluokaksi, perivää luokkaa aliluokaksi Periytymissuhteista muodostuu luokkahierarkia C++ tukee moniperiytymistä Perivä luokka perii useamman kantaluokan C++:ssa kolmen tyyppistä periytymistä Julkinen periytyminen Suojattu periytyminen Yksityinen periytyminen monimuotoisuus 3
IV.1.1 Julkinen periytyminen Tulee käyttää, kun perivää tyyppiä voidaan käyttää aina samassa yhteydessä kuin kantaluokkaa IS-A, WORKS-LIKE-A - periytyminen Tyyppiperiytyminen Perii sekä liittymän että toteutustavan Periytyminen näkyy luokkahierarkiasta ulospäin Yleisin periytymisen muoto Javassa ainoa monimuotoisuus 4
IV.1.1.1 Julkinen periytyminen ja näkyvyys Kantaluokan yksityiset (private) jäsenet eivät näy aliluokassa Periytyvät kuitenkin Ei voi siis käyttää suoraan Kantaluokan suojatut (protected) ja julkiset (public) jäsenet näkyvät aliluokassa Voidaan siis käyttää suoraan Jäsenten näkyvyys myös säilyy samana periytymisessä monimuotoisuus 5
Esimerkki julkisesta periytymisestä class A { public: void f(int); }; class B: public A // B perii luokan A { public: void f(double); }; monimuotoisuus 6
Esimerkki julkisesta periytymisestä (2) A a; B b; double x = 1.0; a.f(x); b.f(x); // Kutsuu A::f(int) // Kutsuu B::f(double) monimuotoisuus 7
Periytyminen graafisesti ColoredPoint Point -double x_coord -double y_coord -int color ColoredPoint perii Point-luokan ColoredPoint-olio sisältää Pointluokan olion monimuotoisuus 8
IV.1.1.2 Periytyminen ja muodostimet Kantaluokan jotakin muodostinta kutsutaan aina ensimmäiseksi kun aliluokan muodostinta kutsutaan Olio rakennetaan kantaluokasta aliluokkaa kohti Ellei aliluokan muodostimessa eksplisiittisesti kutsuta kantaluokan muodostinta, niin kutsutaan automaattisesti kantaluokan oletusmuodostinta Jos luokalla ei ole julkisia tai suojattuja muodostimia niin sitä ei voida käyttää kantaluokkana monimuotoisuus 9
IV.1.1.2 Periytyminen ja hajotin Kun aliluokan olio tuhotaan, aliluokan hajotinta kutsutaan ensin ja sitten kantaluokan hajotinta Olio tuhotaan aliluokasta kantaluokkaa kohti monimuotoisuus 10
Esimerkki muodostimista class Kanta{ public: Kanta() {...} protected: Kanta(int i) {...} private: }; class Ali : public Kanta{ public: Ali(); Ali(int i); ); monimuotoisuus 11
Esimerkki muodostimista (2) Ali::Ali() // implisiittinen Kanta() -kutsu { } Ali::Ali(int i) : Kanta(i) // eksplisiittinen Kanta(int) -kutsu { } monimuotoisuus 12
IV.1.2 Toteutustavan periytyminen Joko yksityinen tai suojattu periytyminen Yksityinen periytyminen Kantaluokan (kantaluokkien) kaikki jäsenet ovat yksityisiä alaluokassa. public -> private protected -> private Kantaluokan yksityiset jäsenet eivät ole käytettävissä Suojattu periytyminen Kantaluokan (kantaluokkien) kaikki jäsenet ovat yksityisiä alaluokassa. public -> protected protected -> private Kantaluokan yksityiset jäsenet eivät ole käytettävissä monimuotoisuus 13
IV.1.3. Moniperiytyminen Moniperiytymisessä luokka perii useamman kantaluokan täsmälleen samoin kuin yksinkertaisessa perinnässä -> aliluokalla on useampi kantaluokka Johdettu luokka sisältää kantaluokan aliolion jokaisesta sen kantaluokasta Ensimmäisiä lisäominaisuuksia C++:aan C++:ssa olioilla ei yhteistä kantaluokkaa -> moniperintä katsottiin välttämättömäksi säiliöiden toteuttamiseksi monimuotoisuus 14
IV.1.3. Moniperiytyminen (2) Kantaluokkien muodostimia kutsutaan siinä järjestyksessä kuin ne on määritelty kantaluokiksi Hajottimien kutsujärjestys on päinvastainen A B C A B C A B Muodostin C C B A Hajotin monimuotoisuus 15
IV.1.3. Moniperiytyminen: Esimerkki class ZooAnimal{ public: virtual ~ZooAnimal() {} }; class Bear : public ZooAnimal{}; class Endangered {}; class Panda: public Bear, public Endangered{}; int main() { Panda p; // Muodostimien kutsut: // ZooAnimal, Bear, Endangered, Panda } // Hajottimien kutsut: // ~Panda, ~Endangered, ~Bear, ~ZooAnimal monimuotoisuus 16
IV.1.3.1 Erotteleva ja yhdistävä moniperiytyminen Oletusarvoisesti C++:ssa moniperiytyminen erottelevaa: class Kanta { // JNE class EkaSolmu: public Kanta { // JNE class TokaSolmu: public Kanta { // JNE class Ali: public EkaSolmu, public TokaSolmu { //JNE Ali-luokka sisältää kaksi ilmentymää Kanta-luokan tiedoista monimuotoisuus 17
IV.1.3.1 Erotteleva ja yhdistävä moniperiytyminen (2) Voidaan käyttää myös virtuaalista eli yhdistävää moniperiytymistä: class Kanta { // JNE class EkaSolmu: public virtual Kanta { // JNE class TokaSolmu: public virtual Kanta { // JNE class Ali: public EkaSolmu, public TokaSolmu { //JNE Nyt Ali-luokassa vain yksi ilmentymä Kanta-luokan tiedoista monimuotoisuus 18
IV.1.3.2 Virtuaalinen moniperiytyminen graafisesti B C A A A B C D Tavallinen, erotteleva moniperiytyminen D Virtuaalinen, yhdistävä moniperiytyminen monimuotoisuus 19
IV.1.3.3 Vertailua Javaan Javassa ei moniperiytymistä Javassa luokkien lisäksi rajapintoja (interface) Rajapinnat sisältävät vain metodien esittelyjä, ei toiminnallisuutta Javan luokka voi periä yhden luokan mutta toteuttaa (implements) sen lisäksi useita rajapintoja Luokkaan toteutetaan rajapinnan esittelemät metodit C++:ssa ei rajapinnan käsitettä Voidaan toteuttaa tekemällä luokka, jossa vain puhtaasti virtuaalisia metodeja monimuotoisuus 20
IV.2. Johdanto monimuotoisuuteen Olio-ohjelmoinnin kolme tukipilaria: 1. Tiedon kapselointi ja kätkentä 2. Periytyminen 3. Monimuotoisuus Monimuotoisuus (polymorfismi): Olion kyky reagoida samaan viestiin eri lailla tilanteesta riippuen Termillä ei yhtä yksikäsitteistä määritelmää Ehkä tyypillisin olio-ohjelmointiparadigman ominaisuus monimuotoisuus 21
IV.2.1 Monimuotoisuuden ilmenemismuodot 1. Operaation ylikuormittaminen Tavallisimmin: Useampia samannimisiä funktioita samassa moduulissa 2. Operaation uudelleenmäärittely Sama metodi yli- ja aliluokissa 3. Muuttujien monimuotoisuus Sama muuttuja voi viitata eri tyyppisiin olioihin 4. Geneerisyys Tyyppiriippumattomuus, tyypit parametreina Käsitellään myöhemmin monimuotoisuus 22
IV.3. Ylikuormittaminen (Overloading) Metodin (funktion) nimi on monimuotoinen, ts. samannimisellä metodilla useita toteutuksia Poikkeavat parametrilistaltaan tai Ovat eri näkymäalueessa Kääntäjä päättelee kutsun parametrien perusteella, mihin toteutuksen sidotaan Staattinen monimuotoisuuden muoto Toteuttamiseen ei tarvita oliokieltä monimuotoisuus 23
IV.3.1 Ylikuormittaminen näkymäalueen suhteen Luokka muodostaa näkymäalueen -> eri luokissa voi olla samannimisiä ja samantyyppisiä metodeja Ei edellytetä semanttista yhteyttä metodien välillä class DialogWindow { public: void draw() {} }; class Artist { public: void draw(){} }; Näkymäalueen suhteen ylikuormitetut metodit Tästä eteenpäin käsitellään ylikuormittamista parametrien suhteen! monimuotoisuus 24
IV.3.2 Konstruktorin ylikuormittaminen Erittäin yleistä, esim. Javan String-luokan konstruktoreita: String()... an empty character sequence. String(char[] value)... represents the sequence of characters currently contained in the character array argument. String(String original)...initializes a newly created String object so that it represents the same sequence of characters as the argument. -> Voidaan luoda String-olioita mm. seuraavasti: String s = new String(); // s = char merkit[] = { h, e, i }; String t = new String(merkit); // t = hei String u = new String(t); // u = hei monimuotoisuus 25
IV.3.3 Operaattoreiden ylikuormittaminen Kielen operaattoreiden laajentaminen omille tyypeille Parantaa ohjelman luettavuutta, mikäli operaattorin semantiikkaa ei muuteta Esimerkki: Kirjoitettu luokka Piste, joka mallintaa tason pisteitä. Halutaan käyttää operaattoria + pisteiden yhteenlaskuun: Piste p(2,3); Piste q(4,5); Piste r = p+q; // r = (6,8); Onnistuuko? Javassa ei sallittu monimuotoisuus 26
IV.3.3 Operaattoreiden ylikuormittaminen (2) C++:ssa operaattorit funktioita tai luokkien metodeja, jotka voidaan ylikuormittaa Lähes kaikki operaattorit ylikuormitettavia Operaattoreiden ylikuormittamisella ei pystytä muuttamaan Operaattoreiden sidontajärjestystä Tulkintasuuntaa Uusia operaattoreita ei voi tehdä monimuotoisuus 27
IV.3.3.1 Esimerkki, operaattorit + ja += class Piste{ public: Piste& operator+=(const Piste &p); //... }; Piste operator+(const Piste& p1, const Piste& p2); Piste& Piste::operator+=(const Piste &p){ x_coord = x_coord+p.x_coord; y_coord = y_coord+p.y_coord; return *this; } Piste operator+(const Piste& p1, const Piste& p2){ Piste p(p1); p += p2; return p; } Tiedosto Piste.h Tiedosto Piste.cpp monimuotoisuus 28
IV.4 Uudelleenmäärittely (Overriding): Aluksi Staattinen sidonta = aikainen sidonta Metodin kutsu liitetään käännöksen aikana tiettyyn aliohjelmaan Dynaaminen sidonta = myöhäinen sidonta Kutsuttava metodi päätellään vasta ohjelman suorituksen aikana Alityyppiperiaate ( Liskov substitution principle ): Aliluokan olio voi esiintyä kaikissa yhteyksissä, joissa yliluokan olio esiintyy C++:ssa ei yleisesti voimassa Javassa aina voimassa monimuotoisuus 29
IV.4 Uudelleenmäärittely Aliluokan metodi saman niminen, sillä on sama parametrilista ja paluuarvo kuin yliluokan metodilla Yliluokan metodin toteutus peittyy Javassa näkyvyysmääre ei saa olla aliluokassa tiukempi, C++:ssa voi vaihtaa mielivaltaisesti Dynaaminen sidonta + alityyppiperiaate tekevät uudelleenmäärittelystä vahvan monimuotoisuuden työkalun monimuotoisuus 30
IV.4.1 Virtuaalioperaatiot Operaatio (metodi), johon voidaan soveltaa myöhäistä sidontaa, on virtuaalioperaatio Virtuaalioperaatio, jolle määritellään yliluokassa myös toteutus, on kiinnitetty virtuaalioperaatio Toteutus on oletus, voidaan syrjäyttää aliluokissa Avoin virtuaalioperaatio = abstrakti metodi Yliluokka ei määrittele toteutusta lainkaan Luokka, jolla on abstrakteja metodeja, on abstrakti luokka. Sellaisesta ei voida luoda olioita Jos luokka ei ole abstrakti, se on konkreettinen monimuotoisuus 31
IV.4.1 Virtuaalioperaatiot: Haittoja Metodien virtuaalisuus tekee ohjelmasta vaikeammin ymmärrettävän ja heikentää luokkahierarkian seurattavuutta Virtuaalioperaatioiden ajonaikainen sitominen myös hidastaa ohjelman suorittamista monimuotoisuus 32
IV.4.2 Virtuaalioperaatiot eri kielissä Smalltalk ja Eiffel: Kaikki operaatiot virtuaalisia Java: Operaatiot virtuaalisia, mutta ohjelmoija voi estää korvaamisen aliluokissa (final) C++ ja Simula: Ohjelmoija päättää, mitkä operaatiot virtuaalisia C++: oletus ei-virtuaalinen monimuotoisuus 33
IV.4.2.1 Esimerkki class Person{ private: string name; public: virtual void print() const; virtual void tmeth(){}; Person(string str):name(str){}; }; class Employee : public Person { private: string status; public: }; void print() const; Employee(string n, string s):person(n),status(s){}; Sidotaan dynaamisesti; virtual pois -> sidotaan staattisesti monimuotoisuus 34
IV.4.3 Dynaamisen sidonnan toteutuksesta Miten löydetään kutsuttaessa oikea metodi? Suoraviivaisin tapa: Tallennetaan jokaiseen olioon ylimääräinen tietokenttä, joka osoittaa oikeaan metodiin Haaskaa tilaa, ei yleensä käytetä Useimmissa käännettävissä oliokielissä käytetään virtuaalitauluja Virtuaalitaulun alkiot ovat osoittimia metodeihin Jos aliluokka ylimäärittelee virtuaalisen metodin, osoitin tähän metodiin. Muussa tapauksessa yliluokan metodiin Saman luokan instansseilla yhteinen taulu monimuotoisuus 35
IV.4.3 Dynaamisen sidonnan toteutuksesta (2) EE Person Attributes Methods Virtual methods print = &Person::print tmeth = &Person::tMeth Employee Attributes Methods Virtual methods print = &Employee::print tmeth = &Person::tMeth p1 p2 Person-luokan virtuaalitaulu Employee-luokan virtuaalitaulu Person* p1 = new Person(); Person* p2 = new Employee(); Person::print Person::tMeth Employee::print Person::tMeth monimuotoisuus 36
IV.4.4 Uudelleenmäärittelyn toteutuksesta Korvaaminen (replacement) Aliluokan metodi korvaa täysin yliluokan metodin Amerikkalainen semantiikka Tarkentaminen (refinement) Aliluokan metodissa suoritetaan ensin yliluokan metodi + muut määritellyt operaatiot Skandinaavinen semantiikka monimuotoisuus 37
III.4.4 Toteutuksesta: Java ja C++ Korvaaminen yleisempi tapa nykykielissä Tarkentamista käytetään konstruktoreissa Sekä Java että C++: Aliluokan oliota luotaessa kutsutaan aina jotakin yliluokan konstruktoria Java ja C++: Konstruktoreita lukuunottamatta käytetään korvaamista Tarkentaminen voidaan määritellä tehtäväksi aliluokan metodissa C++: Yliluokka::metodi() Java: super-viite monimuotoisuus 38
III.4.5 Kovarianssi (Covariant change) Uudelleenmääritellyn metodin paluuarvo tai parametrit muuttuvat samaan suuntaan luokkahierarkian kanssa Ovat siis tarkempia kuin yliluokassa Parametreissa kovarianssi ei yleensä onnistu class Kanta { }; public: virtual boolean equals(kanta& k){/*vrt.*/} class Kovar: public Kanta { }; public: boolean equals(kovar& k) { // EI KORVAA Kanta-luokan // metodia equals(kanta k) return true; } monimuotoisuus 39
IV.4.5 Kovarianssi (2) Paluuarvossa kovarianssi semanttisesti mahdollinen Myös Javan uudemmissa versioissa onnistuu class Yli { }; // Yli-luokan alityyppi class Ali: public Yli { }; class Kovar { public: virtual Yli* teeolio(){ return new Yli(); } }; class KovarAli: public Kovar { public: Ali* teeolio() { return new Ali(); } }; Paluuarvo muuttuu alaspäin luokkahierarkiassa Korvaa Kovar-luokan metodin teeolio() monimuotoisuus 40
IV.4.6 Kontravarianssi (Contravariant change) Uudelleenmääritellyn metodin paluuarvo tai parametrit muuttuvat päinvastaiseen suuntaan luokkahierarkian kanssa Ovat siis yleisempiä kuin yliluokassa Paluuarvossa hankala toteuttaa Ei C++:ssa eikä Javassakaan käy Parametreissa mahdollista ainakin kääntää Eiffel-kielessä tapahtuu korvaaminen C++:ssa ja Javassa ei tapahdu korvaamista monimuotoisuus 41
IV.4.6.1 Kontravarianssi paluuarvossa Alla oleva C++-koodi ei käänny! class Yli { }; // Yli-luokan alityyppi class Ali: public Yli { }; class Kontravar { public: virtual Ali* teeolio(){ return new Ali(); } }; class KontravarAli: public Kontravar { public: Yli *teeolio() { return new Yli(); } }; Paluuarvo muuttuu ylöspäin luokkahierarkiassa monimuotoisuus 42
IV.4.6.2 Kontravarianssi parametreissa C++:ssa voi käyttää syntaktisesti, ei aiheuta metodin uudelleenmäärittelyä class Yli { } // Yli-luokan alityyppi class Ali: public Yli { } class Kontravar { public: virtual void oper(ali &a) { } } class KontravarAli extends Kontravar { public void oper(yli &y) { } } Parametri muuttuu ylöspäin luokkahierarkiassa Ei korvaa Kontravarluokan metodia oper()! monimuotoisuus 43
IV.5 Muuttujien monimuotoisuus Muuttuja voi viitata monentyyppiseen olioon Dynaamisesti tyypitetyissä kielissä kaikki muuttujat monimuotoisia Monet skriptikielet Staattisesti tyypitetyissä kaikilla muuttujilla tyyppi, joka annetaan määrittelyn yhteydessä Esim. Java, C++ Muuttujan monimuotoisuus: Voi viitata myös tyyppinsä alityyppisiin olioihin Tyypillinen käyttötapa: monimuotoiset säiliöt monimuotoisuus 44
IV.5.1 Downcasting Tyyppimuunnos, joka tapahtuu alaspäin luokkahierarkiassa Muunnos ylöspäin yleensä aina sallittu Tarvitaan joskus, kun muuttuja yliluokan tyyppinen, mutta halutaan kutsua aliluokan operaatiota, jota ei yliluokassa ole Varmistettava, että muunnos laillinen! Yleensä kielessä mekanismi, jolla voidaan tarkistaa onko sallittu Java: instanceof-operaattori tarkistaa olion tyypin C++: dynamic_cast-operaattori tarkistaa onko muunnos laillinen monimuotoisuus 45
IV.6 Multi-metodit ja double dispatch Ongelma: Ylikuormitettujen metodien parametrit voivat olla alityyppejä ja ylikuormitetun metodin sitominen on käännösaikaista. Miten metodikutsu saadaan toimimaan tarkoitetulla tavalla, ts. kutsu tehtäisiin parametrien ajonaikaisten tyyppien perusteella? (Multi-metodi) Esim. class Yli{ }; class Ali: public Yli { }; Metodeja: void fun(yli& x, Yli& y) void fun(yli& x, Ali& y) void fun(ali& x, Yli& y) void fun(ali& x, Ali& y) Ohjelmassa: Yli* a =... Yli* b =... Voivat viitata sekä Yliettä Ali-luokan olioihin // Mitä metodia kutsutaan? fun(*a, *b); monimuotoisuus 46
IV.6.1 Multi-metodit ja double dispatch (2) Edellinen ongelma hankala staattisesti tyypitetyissä kielissä. Yksi ratkaisu: olio tietää itse oman tyyppinsä, viesti ohjataan sen mukaan Luokkiin kirjoitettava dynaamisesti sidottavat metodit, jotka korvaavat toiminnan class Yli{ void fun(yli& x){ x.funyli(*this);} void funyli(yli& x){ //fun(yli&,yli&);} void funali(yli& x){ // fun(ali&,yli&);} }; Pääohjelmassa: Yli *a = new Ali(); Yli *b = new Ali(); a->fun(*b); class Ali: public Yli { void fun(yli& x){ x.funali(*this);} void funyli(yli& x){ // fun(yli&,ali&)} void funali(yli& x){ // fun(ali&,ali&)} }; monimuotoisuus 47