====== Olio-ohjelmointi ======
Erittäin hyvä olio-ohjelmointia käsittelevä kirja on Kai Koskimiehen Pieni Oliokirja.
===== Perusteita =====
Perinteisen (top-down) ohjelmistosuunnittelu on toimintosuuntautunutta. Lähtökohtana on korkeimman tason toiminta, jota asteittain tarkennetaan, kunnes päädytään ohjelmointikielen ilmaisujen tasolle.
Pienikin muutos ylätason toiminnallisuudessa voi aiheuttaa suuria muutoksia koko ohjelmistoon. Tämä vaikeuttaa ohjelmiston ylläpitoa ja ohjelmistokomponentit ovat heikosti uudelleenkäytettäviä.
Olio-ohjelmoinnissa pyritään simuloimaan ohjelmallisesti mallinnettavaa tapahtumaa. Tästä saadaan se etu, että ohjelmisto on samankaltainen itse sovelluksen käsitemaailman kanssa.
===== Olio =====
Olio on kokonaisuus, jolla
* on oma identiteetti
* on sisäinen rakenne
* on suhteet tiettyyn ympäristöön
Käytännössä tämä tarkoittaa, että olio on ohjelman rakenteen perusyksikkö, joka
* ei ole puhtaasti toiminnallinen (ei ole siis aliohjelma)
* ei ole puhtaasti tietoa säilyttävä (ei ole siis tietue) vaan sisältää piirteitä kummastakin piirteistä
Oliolla on seuraavat ominaisuudet:
* Se pystyy pyydettäessä suorittamaan tietyt tälle oliolle ominaiset toiminnot
* Pystyy tallettamaan tietoa olion attribuutteihin (eli muuttujiin)
* Olio on suojattu kokonaisuus (olion muuttujia ei voida muuttaa ulkopuolelta)
* Oliolla on identifioiva tunniste
Jos ajatellaan vaikkapa ihmistä oliona, niin ihmisellä on ominaisia toimintoja. Ihmiset puhuvat, vanhenevat, oppivat, jne. Ihmisen attribuutteja voi olla vaikkapa ikä, sukupuoli, hiusten väri, jne. Ihmisen identifioi sosiaaliturvatunnus.
Jos alkaisimme tekemään tietokoneelle mallia ihmisestä, niin hahmottelisimme ensin ihmistä.
{{ java:ihminen.gif }}
===== Metodit ja muuttujat =====
Ihmisen toiminnot on kuvattu vihreällä ja muuttujat punaisella. Koodiksi tulisi siis jotain tämän tyyppistä:
class Ihminen
{
int sosiaaliturvatunnus;
String nimi;
String hiusten_vari;
int pituus;
int paino;
void tervehdi()
{
//koodia
}
void opi()
{
//koodia
}
void vanhene()
{
//koodia
}
void puhu()
{
//koodia
}
}
Huomaa, että koska kyseessä on olio, niin luokan määrittelyssä ei ole ''public static void main''-lohkoa ollenkaan. ''Public''-litaniallahan aloitettiin pääohjelma, ja olio ei koskaan ole oman itsensä herra. Esimerkki on täysin toimiva luokka, joka ei kylläkään tee yhtään mitään. Luokasta on luotava ensin ilmentymä (instanssi), joka voi tehdä jotain.
Ihmistä voitaisiin käyttää seuraavasti:
class Paaohjelma
{
public static void main(String[] args)
{
Ihminen luotu = new Ihminen();
luotu.nimi = "Aatami";
luotu.sosiaaliturvatunnus = 0;
...
}
}
Seuraava olento selventää palautustyyppejä.
public class Laskuri
{
public int tulo(int eka, int toka)
{
return (eka * toka);
/* metodi ottaa kaksi kokonaislukua
ja palauttaa kokonaisluvun */
}
public int summa(int eka, int toka)
{
return (eka + toka);
}
public double osamaara(int eka, int toka)
{
return ((double)eka / (double)toka);
/* metodi ottaa kaksi kokonaislukua
ja palauttaa double-tyyppisen vastauksen */
}
}
Laskuria voidaan käyttää seuraavasti:
class Piaohjelma
{
public static void main(String[] args)
{
Laskuri matikainen = new Laskuri();
int luku1 = 5, luku2 = 10, summa, tulo;
double osamaara;
summa = matikainen.summa(luku1, luku2);
tulo = matikainen.tulo(luku1, luku2);
osamaara = matikainen.osamaara(luku1, luku2);
System.out.println("Summa on " + summa);
System.out.println("Tulo on " + tulo);
System.out.println("Osamaara on " + osamaara);
}
}
Seuraavat esimerkit ovat peräisin Kai Koskimiehen pienestä oliokirjasta.
class Henkilo
{
String nimi; //Muuttuja
int ika=0; //Muuttuja
void ristiminen(String n) //Toiminto, jolla annetaan nimi
{
nimi = new String(n);
}
void tervehdi() //Toiminto nimen edessä palautustyyppi
{
System.out.println("Terve, olen "+nimi);
System.out.println("Olen " + ika + " vuotta vanha");
}
void vanhene() //Toiminto
{
ika++;
}
}
Listauksessa on siis esitelty ihmisen prototyyppi. Ihmistä voidaan käyttää vaikkapa seuraavasti:
class Paaohjelma
{
public static void main(String args[])
{
Henkilo aatami; //Esitellään Henkilö matti
Henkilo eeva = new Henkilo(); //Esitellään Henkilo eeva ja alustetaan se
aatami = new Henkilo(); //alustetaan aatami
aatami.ristiminen("Aatami");
eeva.ristiminen("Eeva");
for (int i=1; i<=100; i++)
{
aatami.tervehdi();
eeva.tervehdi();
aatami.vanhene();
eeva.vanhene();
}
eeva = null; //tuhotaan olioiden ilmentymät
aatami = null;
}
}
==== Muuttujat ====
Sellaiset muuttujat, jotka liittyvät koko luokkaan, esitellään luokan alussa. Näitä muuttujia voidaan käyttää kaikissa toiminnoissa (eli metodeissa) mitä luokassa on.
Muuttujia voidaan esitellä myös metodin sisällä. Tällöin muuttujaa voi käyttää vain siinä metodissa. Eli luokan
class A
{
int B, C;
void D()
{
int E, F;
}
}
muuttujia B ja C voidaan käyttää koko ohjelmassa, myös metodissa D. Muuttujia E ja F voidaan käyttää vain metodissa D.
==== Näkyvyysmääreet ====
Edellä olleessa henkilön määrittelyssä on mätää. Esimerkiksi seuraava ohjelma on täysin sallittu:
class Paaohjelma2
{
public static void main(String args[])
{
Henkilo eeva = new Henkilo(); //Esitellään Henkilo eeva ja alustetaan se
for (int i=1; i<=100; i++)
{
eeva.vanhene();
System.out.println(eeva.ika);
eeva.ika=18;
}
}
}
Eevan käsketään vanhentua (ja eeva vanhenee kiltisti), mutta ilkeä pääohjelma muuttaa sen jälkeen eevan takaisin 18 vuotiaaksi. Eli pääohjelmasta päästään käsiksi olioiden muuttujiin. Jos kurkkaat pari sivua taaksepäin, niin huomaat, että olion piti olla oma suojattu kokonaisuus. Meillä on siis jotain todella pahasti pielessä.
Ongelma saadaan ratkaistuksi näkyvyysmääreillä. Javassa on käytössä neljä määrettä. Esittely tarkemmin oheisessa taulukossa.
^ Nimi ^ Luokka ^ Perillinen ^ Paketti ^ Maailma |
|private | X | | | | |
|protected | X | X | X | |
|public | X | X | X | X |
|package | X | | | X | |
Taulukko kertoo seuraavaa:
''private''-määritteellä suojattu muuttuja näkyy vain omassa luokassaan. Muuttujaa saa muuttaa vain luokan omat metodit.
''public''-määritteellä suojattuun pääsevät kaikki käsiksi. Tämä on oletuksena.
''protected''-määritteellä suojattuun pääsevät käsiksi vain luokan omat metodit ja perilliset.
Henkilön muuttujien suojaaminen käy siis seuraavasti:
class Henkilo2
{
private String nimi; //Muuttuja
private int ika=0; //Muuttuja
void ristiminen(String n) //Toiminto, jolla annetaan nimi
{
nimi = new String(n);
}
void tervehdi() //Toiminto
{
System.out.println("Terve, olen "+nimi);
System.out.println("Olen " + ika + " vuotta vanha");
}
void vanhene() //Toiminto
{
ika++;
}
}
Jolloin edellisen pääohjelman
class Paaohjelma2
{
public static void main(String args[])
{
Henkilo2 eeva = new Henkilo2(); //Esitellään Henkilo eeva ja alustetaan se
for (int i=1; i<=100; i++)
{
eeva.vanhene();
System.out.println(eeva.ika);
eeva.ika=18;
}
}
}
suorittaminen aiheuttaa virheilmoituksen:
{{ java:virhe.jpg }}
==== Konstruktori ====
Konstruktori on metodi, jolla on sama nimi kuin luokalla. Tämä tekee metodista erityisen. Tämä metodi suoritetaan aina, kun luokasta luodaan ilmentymä. Henkilön konstruktoriin voitaisiin liittää suoraan olion nimen antaminen seuraavasti:
class Henkilo3
{
String nimi;
int ika;
Henkilo3(String n) //Konstruktori!!!
{
nimi = new String(n);
ika = 0;
}
void tervehdi()
{
System.out.println("Terve, olen "+nimi);
}
void vanhene()
{
ika++;
}
}
Käyttö olisi seuraavan kaltaista:
class Paaohjelma3
{
public static void main(String args[])
{
Henkilo3 eeva = new Henkilo3("Eeva"); //Esitellään Henkilo eeva ja alustetaan se
for (int i=1; i<=100; i++)
{
eeva.vanhene();
eeva.tervehdi();
}
}
}
==== Perintä ====
Olio-ohjelmoinnin parhaita puolia on perintä. Luokka saa isäluokan kaikki muuttujat ja metodit. Esimerkiksi
public class Otokka
{
String nimi;
public void kavele()
{
System.out.println("viuh viuh");
}
}
public class Koira extends Otokka
{
public void aantele()
{
System.out.println("Tämä on " + nimi + "n reviiriä! HAU HAU!!!");
}
public void kavele()
{
System.out.println("Raps raps!");
//Koira kävelee äänekkäästi, ylikirjoitetaan kavelymetodi
}
}
public class Kissa extends Otokka
{
public void aantele()
{
System.out.println("Hrrrrrrrrrrr olen " + nimi);
}
}
class Paaohjelma
{
public static void main(String[] args)
{
Kissa mirri = new Kissa();
Koira musti = new Koira();
Otokka nilviainen = new Otokka();
mirri.nimi = "mirri";
musti.nimi = "musti";
nilviainen.nimi = "nilviainen";
mirri.aantele();
musti.aantele();
mirri.kavele();
musti.kavele();
nilviainen.kavele();
}
}
Ohjelmassa voidaan käyttää myös sen luokan konstruktoria, mistä luokka on periytetty. Javassa voidaan periyttää vain yhdestä luokasta. Tämä vastaa sitä, että lapsi saisi vain isänsä (tai äitinsä) piirteet.
class Hlo extends Henkilo3
{
Hlo(String n)
{
super(n); //kutsutaan isäluokan konstruktoria
}
}
on ihan sallittu ja laillinen ohjelma. Luokka Hlo saa kaikki luokan henkilo metodit ja muuttujat. Siten ohjelma
class Paaohjelma
{
public static void main(String args[])
{
Hlo eeva = new Hlo("Eeva"); //Esitellään Henkilo eeva ja alustetaan se
for (int i=1; i<=100; i++)
{
eeva.vanhene();
eeva.tervehdi();
}
}
}
on ihan toimiva ja laillinen.
Periytymisellä saadaan nopeasti aikaan vaikkapa graafinen käyttöliittymä. Javassa on käytössä yksittäisperintä, jolloin luokka voi periä vain yhden luokan ominaisuudet. Siis luokka perii joko isän tai äidin piirteet, eikä yhdistele kummankin piirteitä!
this ja super!!!