Johdanto olio-ohjelmointiin

Olio-ohjelmoinnin perusta

Luokka

1. Jo muinaiset room.. eikun kreikkalaiset pohtivat olioiden ideaa
2. Platonin mielestä olentojen idea (luokka) oli yksittäistä olentoa tärkeämpi (olio) Platonin oliot
3. Sen sijaan, että joudumme sanomaan “Tuo tuossa on nelijalkainen ihmisen korkuinen karvainen kavioeläin, jolla voi ratsastaa”, voimme sanoa “Tuo tuossa on hevonen.” Tehokkaampaa." :)
4. Ihminen luokittelee asioita, koska: aivojen työmuistiin mahtuu kerrallaan vain 4-7 asiaa. Luokittelemalla on mahdollista hanskata isompia ja vaikeampia kokonaisuuksia
5. Ihminen luokittelee kaikkea, eläimiä, kasveja, autoja, tietokoneita,...
6. miksei myös tietokoneohjelmassa tarvittavia asioita

check_circle Luokittelu helpottaa ihmisen ajattelua kaikilla tasoilla, myös ohjelmoinnissa

Luokkien keksiminen ohjelmoinnissa

1. Auttaa asioiden hahmottamisessa -> Ongelmasta osaongelmiin
2. Hyvien luokkien keksiminen erittäin haastava työ
3. Useita välineitä ja menetelmiä luokkien graafiseen kuvaamiseen
4. Itse luokkajaot on kuitenkin loppupelissä keksittävä itse

Olio

1. Olio on luokan ilmentymä (edustaja, instanssi, ...)
2. Esimerkiksi Musti (tietty olio) on koirien (luokka) edustaja
3. Saman luokan edustajilla eli olioilla samanlainen rakenne (neljä jalkaa, kuono, ...), mutta ominaisuudet erilaisia (jalkojen pituus).
4. Saman luokan edustajilla eli olioilla samat toiminnot: hengittää, haukkuu, juoksee, ...

Yleistä

Olio-ohjelmointi on yksi eri ohjelmointiparadigmoista (paradigma on ajattelumalli). Ohjelmoinnissa eri paradigmojen tarvoite on kuvata järjestelmien rakennetta ja toimintaa. Olio-ohjelmoinnin keskeisenä tavoitteena on toteuttaa ohjelmointia siten, että saavutetaan mahdollisimman hyvä ohjelmoinnin uudelleenkäytettävyys. Kertaalleen kirjoitettua koodia ei kirjoiteta uudelleen. Oliopohjainen koodi kirjoitetaan ja kootaan pienistä helposti hahmotettavista ohjelman osista, joiden toiminnallisuus määritellään erittäin tarkasti. Järkevästi suunniteltu ja jaoteltu ohjelmointi on helposti hallittavissa. Näin ollen hyvin toteutetusta ohjelmoinnista tulee automaattisesti myös helposti ylläpidettävää, koska tiettyyn kohtaan tehdyt muutokset eivät pääse vaikuttamaan sovelluksen joka puolelle.

C#:n primitiivityypit (int, double, bool, jne..) antavat melko rajoittuneet mahdollisuudet toteuttaa laajempia ohjelmakokonaisuuksia, koska niillä pysytytään tallentamaan ainoastaan lukuja ja yksittäisiä kirjaimia. Onneksemme C# on oliopohjainen ohjelmointikieli ja se tarjoaa näin ollen huikeat puitteet erimuotoisten tietojen käsittelyyn oliorakenteiden avulla.

Siirtyminen oliomaisempaan ajatteluun

Perinteiset proseduraaliset lausekieliset ohjelmat toteutetaan peräkkäisillä käskyillä. Tällainen lausekielinen ongelmien ratkaisutapa on suoraviivainen ja soveltuu pienehköihin tehtäviin. Tämän päivän tietokoneiden käyttöliittymät ja ohjelmistot ovat kuitenkin kehittyneet valtaviksi kokonaisuuksiksi ja on tarvittu uusia tehokkaampia keinoja toteuttaa monimutkaisempia asioita.

Lausekieli

Pohditaan yhdessä esimerkiksi pankkiautomaatin toimintasuunnitelmaa lausekielellä:


    1. Odota korttia
    2. Jos kortti annettiin, kysy tunnusluku
    3. Jos tunnusluku on oikein siirry rahan nostamiseen
    3.1 Jos tunnusluku on väärin palaa kohtaan 2
    3.2 Jos tunnusluku syötettiin väärin kolmannen kerran, ota kortti talteen. Palaa kohtaan 1.
    4. Kysy nostettava summa
    5. Jos tilillä on euroja riittävästi: anna rahat ja siirry kortin palautuskohtaan
    5.1. Jos tilillä ei ole riittävästi katetta: palauta kortti tai siirry kohtaan 4.
    6. Palauta kortti
    7. Palaa kohtaan 1.
    

Yllä olevan ohjelman toteuttaminen pelkästään "tee sitä ja sitten tätä" -tyylisellä proseduraalisella ohjelmoinnilla alkaa muodostua kohtalaisen hankalaksi. Siirtyminen oliomaisempaan ajatteluun - Apua olioista?

Oliomaisemmin

Pankkiautomaatti voidaan myös kuvata oliomaisena:


    itse automaatti on olio (tosin voidaan ajatella myös pienempinä olioina!)
    kortin vastaanottolaitteisto - huolehtii kortin otosta ja palautuksesta
    näppäimistö - huolehtii käyttäjän valinnoista
    rahan säilytyslaitteisto - huolehtii rahan säilytyksestä
    rahan antolaitteisto - huolehtii rahojen jakelusta
    verkkolaitteisto - huolehtii yhteyksistä keskuspankkiin (tunnusluvut, saldot, yms)
    näyttölaitteisto - huolehtii viesteistä käyttäjälle
    jne...
    

Jokainen yllä mainituista pankkiautomaatin itsenäisistä osista eli olioista on tietyssä tilassa. Esimerkiksi kortinvastaanottolaitteen on tiedettävä onko käyttäjä syöttänyt kortin vai ei. Jos kortti on annettu, välittää kortinvastaanottolaitteisto viestin näytölle (kertoo käyttäjälle, että tunnusluku on syötettävä) ja näppäimistölle (ottaa vastaa kortin tunnusluvun). Rahan antolaitteistolla voisi olla AnnaRahat-toiminto, jota käytetään ainoastaan rahanantolaitteisto-oliossa.

Tärkeää on ymmärtää asioita yksittäisinä kokonaisuuksina eli olioina.

Peruskäsitteitä

Olio (object)

Olio-ohjelmoinnissa olio kuvaa yleensä jotain reaalimaailman käsitettä. Olion tärkein tehtävä on sisältää sen ominaisuudet (property) sekä toiminnot (method). Olio-ohjelmoinnin periaatteiden mukaisesti olion tulee suojata ominaisuutensa siten, että sen tilaa voidaan hallitusti muuttaa joko ominaisuuksien muutoksen tai toimintojen kautta. C#-ohjelmoinnissa yleisesti olion tilaa muutetaan suoraan olion ominaisuuksien kautta, mutta esim. Java-ohjelmoinnissa yleisesti olion toiminnallisuuksien kautta.

Esimerkki: Opiskelija-olio

* ominaisuuksia: Etunimi, Sukunimi, Ryhmä, Sotu, Ikä, Osoite, ....
* toimintoja: Opiskele, Nuku, Kävele, ...

Esimerkki: Auto-olio

* ominaisuuksia: Merkki, Malli, Väri, Nopeus, Paino, Teho, ....
* toimintoja: Aja, Jarruta, Kiihdytä, Tööttää, ...

Olion toiminnallisia osia kutsutaan metodeiksi:
* metodien voidaan ajatella vastaavan tavallisen lausekielisen ohjelmoinnin aliohjelmia eli funktioita
* metodit on kiinnitetty aina johonkin olioon (tai luokkaan)

Luokka (class)

Olion luominen pohjautuu aina johonkin luokkaan. Luokka määrittelee olion ominaisuudet ja toiminnot. Olio on luodun luokan ilmentymä eli instanssi. Luokka on siis tavallaan olion pohjapiirrustus. Olio on tästä pohjapiirrustuksesta luotu todellinen ilmentymä.

Esimerkkejä:

* piparkakkumuotti on luokka, jolla voidaan tehdä piparkakkuja (oliota)
* Auto on luokka ja esimerkiksi silloin Ford Fiesta voisi olla olio Auto-luokasta
* Kissa on luokka ja esimerkiksi silloin Mirri on olio
* Kirja on luokka ja esimerkiksi silloin voisi olla Aapinen olio Kirja-luokasta

Oliot vs reaalimaailma

Olio-paradigman yksi tarkoitus on, että luokat ja oliot vastaavat paremmin reaalimaailman asioita. Luokka (class) edustaa jotain asiaa tai käsitettä yleensä ja olio (object) on ilmentymä luokasta. Olio edustaa jotain, joka voidaan yksilöidä samanlaisista olioista. Olio on siis aina "yksilö", jolloin oliolla on siis identiteetti.

Alla olevassa esimerkissä esitellään hyvin simppeli Dog-luokka ja kuinka siitä voidaan tehdä instanssi eli Dog-olio.


       

Seuraavassa esimerkissä Dog-luokkaa on hieman muokattu ja lisätty toiminnallisuutta. Siitä on tehty pari eri Dog-oliota.


	
	

 

check_circle Olio-ohjelmointiin kuuluu lukuisia muita peruskäsitteitä, näitä käsitellään seuraavissa demoissa lisää!

Esimerkki: Auto-luokka ja auto-oliot

Pohdintaan yhdessä tarkemmin Auto-luokkaa ja siitä luotavia auto-olioita. Auto-luokan edustajia eli oliota voisivat olla esimerkiksi Datsun, Porsche ja Toyota. Näillä voisi olla esimerkiksi seuraavat ominaisuudet:

Datsun Porsche Toyota
Merkki: Datsun 100A Merkki: Porsche 911 Merkki: Toyota Corolla
Väri: punainen Väri: keltainen Väri: harmaa
Moottori: 1.0 Moottori: 5.5 Moottori: 1.5
Nopeus: 120 Nopeus: 200 Nopeus: 180
Karvanopat: on Karvanopat: ei Karvanopat: ei
Ovimäärä: 2 Ovimäärä: 2 Ovimäärä: 4

Yllä olevasta taulukosta voidaan selkeästi jo alkaa nähdä millainen pohjapiirustus Auto-luokalla voisi olla. Luokkien ja olioiden yhteydessä puhutaan ns. UML-mallinnuksesta, jonka yksi osa-alue on mm. luokkakaavioiden suunnittelu. Tähän käsitteeseen palataan opintojaksolla vielä myöhemmin.

Auto-luokka UML-luokkakaaviona: kaaviossa on ylhäällä näkyvissä luokan nimi, keskellä ominaisuudet ja alhaalla toiminnot.

Luokan sisältö

Yleisesti

Olio-ohjelmoinnissa luokan sisälle määritellään ensin luokan käyttämät jäsenmuuttujat ja ominaisuudet. Tämän jälkeen määritellään mahdolliset luokan alustajat eli konstruktorit, joiden avulla itse olio luodaan. Viimeisimmäksi tulevat luokan toteuttamat metodit eli toiminnot, indeksoijat ja mahdollinen olion tuhoaja eli destruktori. Tämä ei ole mikään kiveen kirjattu sääntö, mutta aika useiden olio-ohjelmointikielien kohdalla hyväksi todettu menettely.



    

Jäsenmuuttujat (field)

Luokan jäsenmuuttujina määritellään muuttujat, jotka ovat käytössä vain luokan sisällä (nimet aloitetaan pienellä kirjaimella, huomaa myös että jäsenmuuttujien nimen alussa ei käytetä alaviivaa niinkuin joissakin muissa kielissä). Luokan jäsenmuuttuja voi olla myös luokkakohtainen, jolloin sen esittelyssä on käytetty static-avainsanaa (tällöin muuttuja on yhteinen kaikkien sen instanssien kesken). Jäsenmuuttujia voidaan käyttää luokan sisällä tarvittavien toteutuksien laadinnassa tai sitten niissä voi olla tietoa, jota muutetaan luokan ominaisuuksien kautta.


    

Jäsenmuuttujalle voidaan määritellä myös pelkästään get , jolloin se on ainoastaan luettavissa oleva arvo eikä sen arvoa pysty muuttamaan/asettamaan eli niin sanotusti "read-only".

    
	

Esimerkki Person-luokasta

  
    

Jäsenmuuttujien arvo voi olla myös ns. "readonly"-tilassa, eli sille voidaan asettaa arvo vain kerran määrittelylauseen tai konstruktorin yhteydessä.

  
    

Jäsenmuuttujien yhteydessä voidaan käyttää myös const-avainsanaa, muuttuja on siis varsinaisesti vakio eli tällöin jäsenmuuttujan arvo voidaan määritellä vain sen esittelyn yhteydessä.

  
    

check_circle Hyvään ohjelmointitapaan kuuluu, että muuttujat muuttuvat ja vakiot pysyvät vakioina, eikä toistepäin niin kuin joskus valitettavasti tuppaa käymään

Ominaisuudet

Luokan ominaisuuksina kuvataan kaikki ne asiat mitä arvoja olio voi sisältää ja tarjoaa olion ominaisuuksina käyttöön toisille luokille. Tässä vaiheessa ominaisuudet pidetään vielä täysin julkisina (public). Olion tilaa suojaaviin suojamääreisiin tutustutaan myöhemmissä demoissa. Ominaisuudet näyttävät luokan ulkopuolelle jäsenmuuttujilta, mutta todellisuudessa niitä voidaan käsitellä luokan sisällä kuten toimintoja eli metodeja (huomaa get- ja set-määrittelyt).

check_circleC#:ssa on käytössä niin sanottu Auto-Implemented Properties. Tällöin propertyn määritteleminen on nopeaa ja helppoa. Auto-Implemented propertyn taustalla käytetään sisäistä piilotettua muuttujaa, johon ei pääse käsiksi eikä tarvikaan päästä.

Car-luokan tapauksessa ominaisuudet olisivat seuraavaa muotoa:

  
    

Olio-ohjelmoinnissa julkisia jäsenmuuttujia pidetään huonona ratkaisuna. Hyvää ohjelmointitapaa on käyttää ominaisuuksia. Esimerkiksi yllä olevassa voitaisiin auton julkiseen Speed-muuttujaan asettaa mikä tahansa arvo, vaikkapa negatiivinenkin, eikä sitä voitaisi tutkia ja/tai rajoittaa mitenkään.

Luokan ominaisuuksien avulla yhdistetään luokan jäsenmuuttujien (kentän) ilmaisuvoima sekä toimintojen turvallisuus. Tällöin ominaisuudet määritellään kuten aikaisemmen esitetyt jäsenmuuttujat ja ominaisuuden sen arvon muutosta/palatusta säädellään ns. aksessoreilla. Tähän palataan myöhemmissä demoissa.

Alla on kuitenkin näkyvissä jo pienimuotoinen teaseri tulevasta. Get-aksessori palauttaa auton nopeuden ja set-aksessori rajaa autoon sijoitettavaa speed-arvoa.

  
        
    

Konstruktorit eli luokan alustajat

Ominaisuuksien jälkeen esitellään luokan käyttämät alustajat eli konstruktorit, joiden avulla luokasta voidaan luoda olioita. Konstruktoreiden avulla oliot voidaan alustaa haluttuun tilaan sen saamien parametrien mukaisesti. Konstruktoreita voi olla useita, ne eroavat toisistaan parametrien erojen mukaisesti. Konstuktoria ei ole pakko määritellä, tällöin käytetääm pelkästään oletuskonstruktoria, joka on määritelty kaikille luokille automaattisesti, koska luokka perii automaattisesti kantaluokan konstruktorin. Tällöin olio alustuu oletustilaan. Oletuskonstruktoria ei voida käyttää silloin kun parametrillinen konstruktori on luotu. Jos halutaan tässä tapauksessa käyttää myös oletuskonstruktoria, se täytyy konkreettisesti kirjoittaa luokan sisälle ja määritellä alustukset halutusti.

Ei konstruktoria eli käytetään oletuskonstruktoria

Tässä tapauksessa luokan sisälle ei määritellä konstruktoria ollenkaan.

 
    

Tällöin Car-oliota voidaan luoda kutsumalla oletuskonstruktoria seuraavasti:

 
    

Tässä tapauksessa olion ominaisuudet alustuvat oletusarvoihin : Default Values Table (C# Reference).

Käytetään parametrillista konstruktoria

Ohjelmoinnissa on tällöin päätetty, ettei oletuskonstuktori riitä, vaan jokin olion ominaisuus halutaan luokan sisällä alustaa eri tilaan, kuin mitä oletuksena eri tietotyypeille määritellään. Tämä on siis ohjelmoijan päätös luokkaa tehtäessä. Car-luokalle voitaisiin määritellä esim. seuraavat parametrilliset konstruktorit. Tällöin puhutaan konstruktoreiden ylikuormittamisesta (overload).

Huom! Konstruktorien eroavaisuus määritellään parametrien kautta. (engl. If methods, constructors are overloaded then signatures must be unique).

 
    

Tällöin Car-oliota voidaan luoda seuraavasti:

 
    
    

Toiminnallisuudet eli metodit

Luokan tulee määritellä se mitä olio osaa tehdä. Nämä toiminnallisuudet määritellään omina metodeinaan. Car-luokan esimerkissä oli määritelty kaksi eri toimintoa: Accelerate ja Brake, molempien tehtävänä on muuttaa auton Speed-ominaisuutta.


	

Metodien kuormittaminen (overload methods)

Luokan metodit voidaan tarvittaessa kuormittaa (overload). Se tarkoittaa sitä että metodista on monta toteutusta. Edellytyksenä kuormittamiselle on että metodien 'allekirjoitus' (signature) eroavat toisistaan. Allekirjoitus muodostuu metodien parametrien määrästä ja tyypistä. Alla olevasassa esimerkki koodissa on kuormitettu metodi MakeFoo();


    

Indeksoija (indexer)

Indeksoija näyttää luokan ulkopuolelta katsottuna taulukolta, mutta on todellisuudessa luokan sisällä metodi eli toiminto. Indeksoijaa käytetään silloin, kun on luontevaa käsitellä oliota indeksoidusti eli samoin kuin taulukkoa käytetään []-merkeillä. Indeksoijaksi määritellyn metodin nimi on aina this ja se saa hakasuluissa sille määritellyt parametrit. Parametrejä voi olla useita ja niiden ei tarvitse olla int-tyyppisiä. Indeksoijan toteuttamassa this-metodissa esitellään get- ja set-metodit kuten ominaisuuksien kohdalla.

Alla olevassa esimerkissä on määritelty StringCollector-luokka, joka käyttää sisäisesti taulukkoa ja indeksoijaa.


    

Luokkaa voitaisiin käyttää seuraavasti:


    
    

Destruktorit eli luokan tuhojat

Destruktorit ovat konstruktorien vastakohtia eli niitä kutsutaan kun olio halutaan tuhota. Destruktoreiden tarkoitus on vapauttaa/poistaa käyttämättömät oliot ja resurssit. C# kutsuu destruktoreita automaattisesti, ohjelmoijan ei tarvitse siitä huolehtia. Destruktorilla on sama nimi kuin luokalla, kuten konstruktorilla. Destruktorin edessä käytetään ~ etuliitettä. Destruktori on myös aina parametritön. Destruktori jätetään aika useasti kirjoittamatta, ellei mitään erityisiä resursseja olion suhteen ole vapautettavana.


    
    

Esimerkki: Auto-luokan toteuttaminen ja käyttäminen pääohjelmasta

Car-luokka

Toteutetaan opettajan ohjeiden mukaisesti CarApplication-projekti konsoliohjelmana, joka käyttää apunaan Car-luokkaa luomaan muutamia auto-olioita.
1. Käynnistä Visual Studio ja tee uusi projekti nimelle CarApplication
2. Tee uusi luokka nimelle Car (valitse CarApplication oikealla hiiren painikkeella)
2.1 Valitse Add > Class... ja valitse Class
2.2 Anna nimeksi Car ja paina Add-painiketta

check_circle Nyt projekti sisältää sovelluksen pääohjelmatiedoston Program.cs ja vielä tällä hetkellä tyhjän Car.cs Car-luokkatiedoston.

Car-luokan ohjelmointi

Toteutetaan opettajan ohjeita noudatellen Auto-luokan ohjelmointi (käytetään apuna yllä määriteltyä Auto-luokan UML-kaaviota).



    

Car-luokan käyttäminen Program-luokassa

Nyt Car-luokka on käytettävissä sovelluksen Program-pääluokalle. Luodaan yksi Car-luokan olio ja tulostetaan sen tiedot näyttölaitteelle.


    

check_circle Käännä ja suorita sovellus Visual Studiosta käsin ja datsun-olion tiedot tulisi olla näkyvillä näyttölaitteella.

Toteuta pääohjelmaan myös Auto-luokan avulla porsche- ja toyota-oliot. Kokeile käyttää myös parametrillista konstruktoria Car-luokassa.

Luokan static-jäsenet

Static-avainsanalla määritellyt luokan jäsenkentät ja/tai metodit kuuluvat luokalle eivät luokasta tehdylle olio-instanssille. Tässä tapauksessä määriteltyjä jäseniä/toimintoja voidaan kutsua luokan ulkopuolelta ainoastaan luokan nimen kautta, ei sen olio-instanssin kautta.

Car-luokka esimerkkinä

Alla olevassa Car-luokassa on määritelty MaxSpeed-jäsenmuuttuja staattiseksi static-avainsanalla:


    

Nyt voisimme käyttää Car-luokkaa seuraavasti:


    

Saavuttamamme etu olisi se, että Car-luokassa määritelty MaxSpeed-ominaisuutta ei olisi erikseen alustettu jokaisessa car-luokan instanssissa viemään muistia, vaan car-luokan instanssit käyttäisivät yhtä ja samaa MaxSpeed-ominaisuutta. Huomaa, että yllä olevassa esimerkissä static-ominaisuutta käytetty tavallaan rajaaman auton maksiminopeutta ja ominaisuutta ei muuten käytetty. Staattinen ominaisuus voi olla myös "käytetty" ominaisuus eli sellainen, jonka arvo muuttuisi sovelluksen suorituksen aikana. Tällöin arvon muutos olisi näkyvissä kaikissa ko. luokan toteuttavissa instanssessa.

TemperatureConverter-luokka esimerkkinä

Luokalle voidaan määritellä myös static-määreellä esiteltyjä toimintoja (tai jopa kokonainen luokka voidaan määritellä staattiseksi). Tällöin luokan toimintoja käytetään suoraan luokan nimen kautta. Yleisesti tällaisesta ratkaisusta on hyötyä silloin, kun halutaan esimerkiksi tehdä jotain luokkakirjastoja, jotka suorittavat vain jotain tiettyjä tehtäviä (ja ei ole tarvetta luoda erikseen oliota).


    

Nyt voisimme käyttää TemperatureConverter-luokkaa seuraavasti:


    

C#:ssa luokan alustaja eli konstruktori voi olla myös staattinen. Asiaa ei käsitellä nyt tarkemmin, mutta voit tutustua tilanteeseen täältä: Static Constructors (C# Programming Guide)