Olio-ohjelmoinnin peruskäsitteitä

Monimuotoisuus (polymorphism)

Monimuotoisuus on olion kykyä muuntautua ohjelman suorituksen aikana käyttämään tilanteen mukaan omia tai jälkeläisensä ominaisuuksia ja/tai toimintoja. Tämä tarkoittaa yksinkertaisuudessaan sitä, että eri luokille voidaan määritellä saman nimisiä ominaisuuksia ja/tai toimintoja.

Esimerkki: Animals

Tutkitaan yhdessä alla olevaa tilannetta, jossa on luotu Animal-kantaluokka, josta on peritty Dog-, Cat- ja Chicken-luokat. Huomaa kuinka sekä Legs-ominaisuus että Talk()-toiminto on jätetty virtuaaliseksi. Huom: C#:ssa pitää käyttää Virtual-määrettä, jotta perityssä luokassa voidaan ylikirjoittaa metodi tai ominaisuus.


    

Animal-luokkaan on määritelty virtuaalinen Legs-ominaisuus, jolle voidaan perityssä luokassa ylikirjoittaa eläinkohtaisesti uusi arvo. Samoin Animal-luokalla on yksi virtuaalinen Talk-metodi, jolloin sille voidaan ylikirjoittaa perityssä luokassa uusi toteutus käyttämällä override-määrettä.

Nyt Dog-, Cat ja Chicken-luokkia voidaan käyttää esimerkiksi seuraavasti. Huomaa kuinka Animal-luokan tyyppiseen olioon on sijoitettu aliluokan olio. Polymofismi mahdollistaa sen että sijoitamme kantaluokka-tyyppiseen olioon perityn luokan olion, koska peritty luokka sisältää kaiken (ja joskus vähän enemmänkin) mitä kantaluokkakin.


	

Polymorfismia käytetään usein esimerkiksi kokoelmarakenteissa esim tyypitetyssä Listassa. Listan tyypiksi määritellään kantaluokka, ja siihen sijoitetaan kuitenkin perittyjen luokkien olioita. Alla olevassa esimerkissä Animal-luokan olioita ja perittyjen luokkien tehdä useita ja sisällytetään ne List-rakenteeseen:


    

Ohjelma tulostaa silmukkarakenteesta:


    Wuw!
    Miau!
    CotCot!
    Wuw!
    Miau!
    CotCot!
    Wuw!
    Miau!
    CotCot!
    

Yhteenveto

Monimuotoisuus tarjoaa keinon käyttää perintärakenteita siten, että olioille voidaan määritellä samannimisiä toimintoja. Edellisessä esimerkissä ei siis tarvittu kahta erinimistä toimintoa kertomaan miten koira ja kissa ääntelevät. Näin ollen esimerkiksi suurempaa joukkoa eläimiä voidaan käydä kätevästi läpi toistorakenteella ja käskeä kaikkia eläimiä ääntelemään omalla tavallaan.

Toinen esimerkki voisi olla vaikka piirrustusohjelma, jossa käyttäjä voi piirrellä useita erilaisia kuvioita: ympyröitä, neliöitä, viivoja, suorakulmioita, ... ja ne kaikki tulisi ottaa talteen ja piirtää näytölle myöhemmin. Kaikilla kuvioilla voisi olla yhteinen kantaluokka, jossa olisi kuvioille yhteisiä ominaisuuksia ja virtuaalinen Piirra()-metodi, joka olisi ylikirjoitettu perityissä luokissa piirtämään kyseessä oleva kuvio näytölle. Kuvio-luokka ei sitä tiedä miten kuvio piirrettäisiin, mutta jokainen peritty luokka (Nelio,Viiva,Ympyrä) osaisi sitten piirtää itsensä matemaattisesti oikein.

new- ja override-määreiden erot

Aikaisemmissa esimerkeissä käsiteltiin vain virtuaalisten ominaisuuksien ja toimintojen ylikirjoittamista perityssä luokassa override-määreellä. Tuolloin kantaluokan ominaisuudet ja toiminnot ylikirjoitettiin perityssä luokassa ja niitä käytetään olipa käyttävä objekti todellisuudessa kanta- tai perityn luokan objekti (monimuotoisuus). Jos perityssä luokassa käytetään new-määrettä ominaisuuden tai toiminnon kohdalla, niin silloin perityssä luokassa ei ylikirjoiteta ominaisuutta/metodia, vaan piilotetaan sen. Kantaluokan ominaisuuksia käytetään mikäli olion tyyppi viittaa kantaluokkaan. Tosin peritystä luokasta pääsee aina kantaluokan metodiin käyttämällä base-avainsanaa: base.KantaLuokanMetodi().

Chicken-luokan muutos, Talk()-metodilla new-määre

Jos muuttaisimme aikaisemmin esitetyn Chicken-luokan Talk()-metodin käyttämään new-määrettä override-määreen sijasta:


    

Ja käyttäisimme monimuotoisuuden perusteella Animal-tunnisteessa Chicken-luokan oliota:


    

Tällöin Chicken-luokan Talk()-metodin käyttäminen Animal-tunnisteen sisällä käyttäisi Animal-luokan Talk()-metodia eikä Chicken-luokan Talk()-metodia.


    Talk!
    animal3 has 2 legs.
    animal1 type is PolymorphismApplication.Chicken.
    

Kana ei enää kotkottele, vaan puhuu!

Tutkitaan asiaa vielä toisella esimerkillä

Alla olevassa esimerkissä on käytössä A-luokka, josta peritään luokat B ja C. Luokalla A on virtuaalinen Test()-methodi, joka ylikirjoitetaan override- ja new-määreillä aliluokissa B ja C. Luokkien olioita käytetään oman tunnisteen sisällä, jolloin ne käyttävät omia Test-metodejaan (rivit 25-27). Monimuotoisuuden nojalla luokan A tunnisteeseen on sijoitettu B-luokan olio, joka käyttää Test-metodissa new-määrettä (rivi 33,34). Tällöin olion käyttäminen piilottaa metodin ja käytetään todellisuudessa kantaluokan metodia. Rivillä 36-37 luokan A tunnisteeseen sijoitetaan perityn luokan eli luokan C olio, joka ylikirjoittaa A-luokassa olevan Test()-metodin. Tuolloin käytetään siis C-luokan Test()-metodia.


    
    

Yhteenveto

Virtual-määrettä käytetään kantaluokan ominaisuuksissa ja/tai toiminnoissa silloin, kun halutaan toteuttaa uusi toteutus perityissä luokissa.

Override-määrettä käytetään laajentamaan/muuttamaan kantaluokan ominaisuuksia/toimintoja perityssä luokassa. Sidonta tapahtuu sovelluksen suorituksen aikana ("runtime polymorphism" / "late binding")

New-määrettä käytetään piilottamaan kantaluokan ominaisuuksia/toimintoja perityssä luokassa. Sidonta tapahtuu käännöksen aikana ("compile-time polymorphism" / "early binding").

Lisätietoa

Polymorphism (C# Programming Guide)