Perintä (ohjelmointi)

Perintä (engl. inheritance) on olio-ohjelmoinnissa tapa muodostaa uusia luokkatyyppejä laajentamalla jo aiemmin määriteltyjä luokkia.[1] Uusi luokkatyyppi saa käyttöönsä kaikki yliluokkien ominaisuudet, joita ei ole yliluokasta käsin estetty perimästä. Aliluokan on myös mahdollista uudelleenmääritellä joitain yliluokan ominaisuuksia ja metodeita. Uudelleenmäärittelyn toteutus vaihtelee eri ohjelmointikielissä. Esimerkiksi C#:ssa vain yliluokan merkatut metodit voidaan uudelleenmääritellä, mutta Javassa metodi voidaan asettaa uudelleenmäärittelemään aikaisemmat metodit. Perinnän tarkoituksena on vähentää koodin uudelleenkirjoittamista.

Lisäksi, koska aliluokkaan voidaan lisätä ominaisuuksia, joita ei yliluokassa ollut, voidaan aliluokkaa pitää yliluokan erikoistapauksena. Esimerkiksi yliluokka voi olla Hedelmä, jonka aliluokkana on Omena. Tällöin Hedelmä-luokassa määritellään ominaisuudet, jotka ovat yhteisiä kaikille hedelmille, kuten paino ja Omena luokassa määritellään ominaisuudet, jotka kuuluvat kaikille omenoille, mutta eivät kuulu esimerkiksi päärynöille. Luokkien periytymisessä voidaan siis ajatella olevan "is-a"-suhde. Aliluokan oliot ovat siis tyypiltään myös yliluokan olioita. Tämä ei kuitenkaan päde toiseen suuntaan, esimerkiksi luokan Hedelmä-olio ei välttämättä ole luokan Päärynä-olio.

Moniperinnässä aliluokalla voi olla monta yliluokkaa. Esimerkiksi aliluokalla Tutkimusapulainen voi olla yliluokat Tutkija ja Opiskelija, joista molemmista aliluokka perii ominaisuuksia. Kaikki ohjelmointikielet eivät tue moniperintää. Yleisimmistä kielistä C++ tukee moniperintää. Sen sijaan esimerkiksi Java ja C# eivät tue moniperintää, mutta niissä luokka voi toteuttaa (implement) usean rajapinnan (interface) ominaisuuksia.

Periytyminen voi myös tapahtua monitasoisesti. Esimerkiksi yliluokalla Hedelmä voi olla aliluokka Omena, jolla edelleen voi olla aliluokka Royal Gala. Tällöin siis luokka Royal Gala perii kaikki molempien yliluokkien ominaisuudet.

Tyypit muokkaa

Perinnässä on erilaisia ​​tyyppejä, jotka perustuvat paradigmaan ja tiettyyn kieleen.[2]

Yksittäinen perintö muokkaa

  • Alaluokat perivät yhden yläluokan ominaisuudet.
  • Luokka hankkii toisen luokan ominaisuudet.

Moniperintö muokkaa

  • Yhdellä luokalla voi olla useampi kuin yksi yläluokka.
  • Perii kaikkien yläluokkien ominaisuudet.

»Moniperintö ... oletettiin laajasti olevan hyvin vaikea toteuttaa tehokkaasti. Esimerkiksi Brad Coxin kirjassa Objective C: stä hän väitti, että usean perinnön lisääminen C ++: aan oli mahdotonta. Näin ollen moniperintö vaikutti enemmän haasteelta. Koska olin harkinnut moniperintöä jo vuodesta 1982 ja löysin yksinkertaisen ja tehokkaan toteutustekniikan vuonna 1984, en voinut vastustaa haastetta. Epäilen tämän olevan ainoa tapaus, jossa muoti vaikutti tapahtumien kulkuun.»
(Bjarne Stroustrup[3])

Monitasoinen perintö muokkaa

  • Aliluokka peritään toiselta aliluokalta.

Ei ole harvinaista, että luokka johdetaan toisesta johdetusta luokasta. Luokka A toimii perusluokkana johdetulle luokalle B, joka puolestaan ​​toimii perusluokkana johdetulle luokalle C. Luokka B tunnetaan väliluokkana, koska se tarjoaa linkin perinnälle A: n ja C: n välillä. Ketju ABC tunnetaan perintöpolkuna.

Monitasoinen perintö on määritetty seuraavasti:

class A { ... };      // Perusluokka
class B : public A { ... };   // B johdettu A: sta
class C : public B { ... };   // C johdettu B: stä

Tätä prosessia voidaan jatkaa mihin tahansa määrään tasoja.

Hierarkkinen perintö muokkaa

  • Yksi luokka toimii yläluokkana (perusluokka) useammalle alaluokalle.

Esimerkiksi vanhempi-luokka A voi olla kahden aliluokan B ja C perusluokka. Sekä B: n että C: n vanhempi-luokka on A, mutta B ja C ovat kaksi erillistä aliluokkaa.

Hybridi-perintö muokkaa

  • Kahden tai useamman edellä mainitun perintätyypin ominaisuudet yhdistyvät.

Esimerkiksi, jos luokalla A on aliluokka B, jolla on kaksi aliluokkaa, C ja D, niin tämä on monitasoisen ja hierarkkisen perinnön sekoitus.

Esimerkki muokkaa

Esimerkkinä yliluokka Vihannes Java-kielessä.

public interface Vihannes {

	private byte palautaPaino();
}

Aliluokka Kurkku, joka perii yliluokan ominaisuudet.

public class Kurkku implements Vihannes {

	private static final byte PAINO = 60;

	@Override
	private byte palautaPaino() {
		return PAINO;
	}
	
	public void tulostaPaino() {
		System.out.println("Kurkun paino on: "+paino+"g.");
	}
}

Periytyvän luokan jäsenten näkyvyys muokkaa

Periytyvän luokan jäsenten näkyvyys määrittelee sen, missä laajuudessa ne ovat käytettävissä perivän luokan ja sen instanssien koodissa. Jäsenten näkyvyys vaikuttaa siis siihen, kuinka ne ovat käytettävissä perivässä luokassa ja sen instansseissa. Jäsenten näkyvyyttä määritellään käyttämällä C++ -kielen terminologian näkyvyysmääreitä, joita ovat private, protected ja public. Alla oleva taulukko näyttää, mitkä muuttujat ja metodit periytyvät riippuen luokassa annetusta näkyvyydestä.[4]

Kantaluokan näkyvyys Perityn luokan näkyvyys
Private Protected Public
  • Private →
  • Protected →
  • Public →
  • Ei perintää
  • Private
  • Private
  • Ei perintää
  • Protected
  • Protected
  • Ei perintää
  • Protected
  • Public

Private on kaikkein rajoittavin näkyvyysmääre. Jäsenet ovat näkyvissä vain luokan sisällä, eikä niitä voi käyttää sen instansseissa tai perivissä luokissa. Tämä tarkoittaa sitä, että private-jäsenet ovat siis piilossa muulta ohjelmakoodilta ja niihin pääsee käsiksi vain luokan omista jäsenistä. Esimerkiksi peritty luokka ei pääse käsiksi perivän luokan private-jäseniin. Luokan jäsenmuuttujat on yleisesti kannattavaa asettaa privaateiksi olioajattelun tiedon piilottamisen perusajatuksen myötä, jonka mukaan jäsenmuuttujat on tarkoitus pitää piilossa.

Protected-näkyvyydessä jäsenet ovat näkyvissä perivässä luokassa ja sen instansseissa, mutta eivät muussa ohjelmakoodissa. Näin ollen protected-liitettä käytetäänkin yleisesti perinnän yhteydessä. Protected-jäseniin pääsee siis käsiksi perivän luokan koodissa sekä sen instansseissa, mutta suojattuihin jäseniin ei pääse käsiksi perivän luokan käyttäjän koodista.

Public-näkyvyydessä jäsenet ovat näkyvissä kaikkialla ohjelmakoodissa ja public-jäseniin pääsee käsiksi sekä perivän luokan koodista, että sen instansseista, mutta myös perivän luokan käyttäjän koodista. Koska public määreellä määritellyt jäsenmuuttujat ja funktiot ovat kaikkien käytettävissä, luovat ne julkista rajapintaa. Täten niiden suunnittelussa on oltava huolellinen. Olio-ohjelmoinnin ajatukseen kuuluu, ettei ulospäin tarjota kaiken varalta ylimääräistä tavaraa. Jos jatkokehityksessä tulee tarve esimerkiksi muovata jäsenmuuttujia tai korvata niiden rakenne, ei tätä voi tehdä huolettomasti julkiseksi luotujen jäsenmuuttujien takia. Täten olioajattelun mukaisen ohjelmoinnin rajapinnan tulisi olla “minimaalista, mutta täydellistä” sekä oliolla olisi hyvä olla täysi kontrolli tietoonsa, joka saavutetaan ei-julkisilla muuttujilla.

Alaluokat ja superluokat muokkaa

Alaluokat, johdetut luokat, perillisluokat tai aliluokat ovat modulaarisia johdannaisluokkia, jotka perivät yhden tai useamman kielientiteetin yhdestä tai useammasta muusta luokasta (kutsutaan superluokiksi, perusluokiksi tai yläluokiksi). Luokkaperinnön semantiikka vaihtelee kielittäin, mutta yleensä alaluokka perii automaattisesti superluokkiensa ilmentymämuuttujat ja jäsenfunktiot.

Jotkut kielet tukevat myös muiden konstruktien periytymistä. Esimerkiksi Eiffelissä sopimukset, jotka määrittelevät luokan määrittelyn, perivät myös perilliset. Suoperluokka muodostaa yhteisen rajapinnan ja perustoiminnallisuuden, joita erikoistuneet alaluokat voivat periä, muokata ja täydentää. Alaluokan perimä ohjelmisto katsotaan alaluokassa uudelleen käytettäväksi. Viittaus luokan ilmentymään voi itse asiassa viitata johonkin sen alaluokista. Viitattavan kohteen todellista luokkaa on mahdotonta ennustaa käännöshetkellä. Yhtenäistä käyttöliittymää käytetään useiden eri luokkien olioiden jäsenfunktioiden kutsumiseen. Alaluokat voivat korvata superluokan funktiot kokonaan uusilla funnktioilla, joilla on oltava sama menetelmän allekirjoitus.

Ei-alaluokkaiset luokat muokkaa

Joissakin kielissä luokka voidaan ilmoittaa ei-aliluokittelevaksi lisäämällä tiettyjä luokkamääritteitä luokkailmoitukseen. Esimerkkejä ovat avainsana final Javassa ja C++ 11:stä eteenpäin tai sealed avainsana C#:ssa). Tällaiset modifikaattorit lisätään luokan määrittelyyn ennen kuin luokka-avainsanaa ja luokan tunnisteavainta määritetään. Tällaiset ei-alaluokkaiset luokat rajoittavat uudelleen käytettävyyttä, erityisesti silloin kun kehittäjällä on pääsy ainoastaan käännettyyn binääritiedostoon eikä lähdekoodiin.

Ei-aliluokittelevalla luokalla ei ole alaluokkia, joten käännöshetkellä voidaan helposti päätellä, että viittaukset tai osoittimet kyseisen luokan objekteihin viittaavat itse asiassa kyseisen luokan esiintymiin eivätkä alaluokkien esiintymiin (niitä ei ole olemassa) tai superluokkien esiintymiä. Koska viitatun olion tarkka tyyppi tiedetään ennen suoritusta, varhaista sidontaa (kutsutaan myös staattiseksi lähetykseksi) voidaan käyttää myöhäisen sidoksen (kutsutaan myös dynaamiseksi lähetykseksi) sijasta, mikä vaatii yhden tai useamman virtuaalisen menetelmätaulukon haun riippuen siitä, onko useita perintöjä. Vain yksittäistä periytymistä tuetaan käytettävässä ohjelmointikielessä.

Ei-ylikirjoitettavat metodit muokkaa

Aivan kuin luokat voivat olla aliluokkaamattomia, voivat metodien määrittelyt sisältää metodi modifioija, mitkä estävät metodin ylikirjoittamisen (Eli korvataan uudella funktiolla, jolla on sama nimi ja tyyppiallekirjoitus aliluokassa).

Yksityistä metodia ei voida ylikirjoittaa siksi, että se ei ole saatavilla muille luokille kuin luokalle johon se kuuluu jäsenfunktiona. Tämä ei kuitenkaan pidä paikkaansa C++ kohdalla. Lopullinen (engl. final) metodi javassa, suljettu (engl. sealed) C# tai jäädytetty (engl. frozen) ominaisuus Eiffelissä eivät voi olla ylikirjoitettavissa.

Virtuaaliset metodit muokkaa

Jos superluokan metodi on virtuaalinen metodi, niin superluokan metodin kutsut ohjataan dynaamisesti. Jotkut kielet vaativat, että metodit on määritelty nimenomaan virtuaalisiksi (esim. C++), kun taas muiden kielien metodeissa ne ovat virtuaalisia (esim. Java). Ei-virtuaalisen metodin kutsu ohjautuu aina staattisesti (eli funktiokutsun osoite määritetään käännösaikana). Staattinen ohjaus on nopeampi kuin dynaaminen ohjaus ja se mahdollistaa optimointeja, kuten sisäisten laajennusten tekemisen.

Historia muokkaa

Vuonna 1966 tietojenkäsittelytieteilijä Tony Hoare esitti idean tietueiden alaluokista, siis tietuetyypeistä, joilla on yhteisiä ominaisuuksia, mutta jotka ovat erotettuina toisistaan omilla tunnisteillaan, ja joilla on tiettyjä itselleen erityisiä ominaisuuksia. Tämän vaikutuksesta norjalaiset tietojenkäsittelytieteilijät Ole-Johan Dahl ja Kristen Nygaard esittivät mallin, joka mahdollisti eritellä olioita, jotka kuuluivat eri luokkiin, mutta joilla oli yhteisiä ominaisuuksia. Yhteiset ominaisuudet sitten koottiin yhteen yliluokkaan, jolla itsellään oli mahdollisesti myös yliluokka. Aliluokan ominaisuudet olivat täten yhdistelmä sen kaikkien yliluokkien ominaisuuksista, sekä aliluokan omista ominaisuuksista. Ensimmäinen ohjelmointikieli, johon tämä idea implementoitiin, oli Simula 67.

Lähteet muokkaa

  1. Wheeler, David A.: Section 7.2 - Object-Oriented Programming in Ada: Inheritance dwheeler.com. Viitattu 27.8.2017.
  2. C++ Inheritance cs.nmsu.edu. 2003. Viitattu 23.2.2023.
  3. Pearson: The Design and Evolution of C++ (PDF) citeseer.ist.psu.edu. 1999. Viitattu 23.2.2023.
  4. E. Balagurusamy: Object oriented programming with C++. New Delhi: Tata McGraw-Hill, 2008. 278412134. ISBN 978-0-07-066907-9, 0-07-066907-4. Teoksen verkkoversio (viitattu 28.2.2023).